Array zu Hash Ruby

192

Okay, hier ist der Deal, ich habe lange gegoogelt, um eine Lösung dafür zu finden, und obwohl es viele gibt, scheinen sie nicht den Job zu machen, den ich suche.

Grundsätzlich habe ich ein Array wie folgt aufgebaut

["item 1", "item 2", "item 3", "item 4"] 

Ich möchte dies in einen Hash konvertieren, damit es so aussieht

{ "item 1" => "item 2", "item 3" => "item 4" }

Das heißt, die Elemente in den "geraden" Indizes sind die Schlüssel und die Elemente in den "ungeraden" Indizes sind die Werte.

Irgendwelche Ideen, wie man das sauber macht? Ich nehme an, eine Brute-Force-Methode wäre, einfach alle geraden Indizes in ein separates Array zu ziehen und sie dann zu durchlaufen, um die Werte hinzuzufügen.

djhworld
quelle

Antworten:

357
a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }

Das ist es. Der *wird als Splat- Operator bezeichnet.

Eine Einschränkung pro @Mike Lewis (in den Kommentaren): "Seien Sie sehr vorsichtig damit. Ruby erweitert Splats auf dem Stapel. Wenn Sie dies mit einem großen Datensatz tun, müssen Sie damit rechnen, Ihren Stapel auszublasen."

Für die meisten allgemeinen Anwendungsfälle ist diese Methode also großartig. Verwenden Sie jedoch eine andere Methode, wenn Sie die Konvertierung für viele Daten durchführen möchten. Zum Beispiel bietet @ Łukasz Niemier (auch in den Kommentaren) diese Methode für große Datenmengen an:

h = Hash[a.each_slice(2).to_a]
Ben Lee
quelle
10
@tester, das *heißt der Splat- Operator. Es nimmt ein Array und konvertiert es in eine Literalliste von Elementen. Also *[1,2,3,4]=> 1, 2, 3, 4. In diesem Beispiel entspricht das oben Gesagte dem Ausführen Hash["item 1", "item 2", "item 3", "item 4"]. Und Hashhat eine []Methode, die eine Liste von Argumenten akzeptiert (wobei gerade Indexschlüssel und ungerade Indexwerte erstellt werden), aber Hash[]kein Array akzeptiert, also teilen wir das Array mit *.
Ben Lee
15
Sei sehr vorsichtig damit. Ruby erweitert Splats auf dem Stapel. Wenn Sie dies mit einem großen Datensatz tun, müssen Sie damit rechnen, Ihren Stapel auszublasen.
Mike Lewis
9
Auf Big-Data-Tabellen können Sie verwenden Hash[a.each_slice(2).to_a].
Hauleth
4
Was bedeutet "Blow out your Stack"?
Kevin
6
@ Kevin, der Stapel verwendet einen kleinen Speicherbereich, den das Programm für bestimmte Operationen reserviert und reserviert. Am häufigsten wird es verwendet, um einen Stapel der bisher aufgerufenen Methoden zu behalten. Dies ist der Ursprung des Begriffs Stapelverfolgung , und dies ist auch der Grund, warum eine unendlich rekursive Methode einen Stapelüberlauf verursachen kann . Die Methode in dieser Antwort verwendet auch den Stapel. Da der Stapel jedoch nur ein kleiner Speicherbereich ist, füllt er den Stapel, wenn Sie diese Methode mit einem großen Array ausprobieren, und verursacht einen Fehler (einen Fehler in der gleichen Richtung wie) ein Stapelüberlauf).
Ben Lee
103

Ruby 2.1.0 hat eine to_hMethode für das Array eingeführt, die genau das tut, was Sie benötigen, wenn Ihr ursprüngliches Array aus Arrays von Schlüssel-Wert-Paaren besteht: http://www.ruby-doc.org/core-2.1.0/Array.html#method -i-to_h .

[[:foo, :bar], [1, 2]].to_h
# => {:foo => :bar, 1 => 2}
Jochem Schulenklopper
quelle
1
Wunderschönen! Viel besser als einige der anderen Lösungen hier.
Dennis
3
Für Ruby-Versionen vor 2.1.0 können Sie die Hash :: [] -Methode verwenden, um ähnliche Ergebnisse zu erzielen, sofern Sie Paare eines verschachtelten Arrays haben. also a = [[: foo ,: 1], [bar, 2]] --- Hash [a] => {: foo => 1 ,: bar => 2}
AfDev
@AfDev, in der Tat, danke. Sie haben Recht (wenn Sie die kleinen Tippfehler ignorieren: barmuss ein Symbol sein, und das Symbol :2sollte eine Ganzzahl sein. Ihr korrigierter Ausdruck ist also a = [[:foo, 1], [:bar, 2]]).
Jochem Schulenklopper
28

Verwenden Sie einfach Hash.[]mit den Werten im Array. Beispielsweise:

arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}
Futter
quelle
1
Was bedeutet [* arr]?
Alan Coromano
1
@Marius: *arrKonvertiert arrin eine Argumentliste, daher wird die []Hash-Methode mit dem Inhalt von arr als Argument aufgerufen .
Chuck
26

Oder wenn Sie ein Array von [key, value]Arrays haben, können Sie Folgendes tun:

[[1, 2], [3, 4]].inject({}) do |r, s|
  r.merge!({s[0] => s[1]})
end # => { 1 => 2, 3 => 4 }
Erik Escobedo
quelle
2
Ihre Antwort hat nichts mit der Frage zu tun und in Ihrem Fall ist es immer noch viel einfacher, dieselbe zu verwendenHash[*arr]
Yossi
2
Nee. Es würde zurückkehren { [1, 2] => [3, 4] }. Und da der Titel der Frage "Array to Hash" lautet und die integrierte "Hash to Array" -Methode Folgendes tut: { 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]]Ich dachte, mehr als einer könnte hier enden und versuchen, die Umkehrung der integrierten "Hash to Array" -Methode zu erreichen. Eigentlich bin ich hier sowieso so gelandet.
Erik Escobedo
1
Entschuldigung, ich habe ein Ersatzsternchen hinzugefügt. Hash[arr]wird den Job für Sie erledigen.
Yossi
9
IMHO bessere Lösung: Hash [* array.flatten (1)]
Gast
2
Yossi: Tut mir leid, dass ich die Toten auferweckt habe, aber es gibt ein größeres Problem mit seiner Antwort, und das ist die Verwendung von #injectMethoden. Mit #merge!, #each_with_objectsollte verwendet worden sein. Wenn #injectdarauf bestanden wird, #mergeanstatt #merge!hätte verwendet werden sollen.
Boris Stitnicky
12

Das habe ich gesucht, als ich das gegoogelt habe:

[{a: 1}, {b: 2}].reduce({}) { |h, v| h.merge v } => {:a=>1, :b=>2}

Karl Glaser
quelle
Sie möchten nicht verwenden merge, es erstellt und verwirft einen neuen Hash pro Schleifeniteration und ist sehr langsam. Wenn Sie [{a:1},{b:2}].reduce({}, :merge!)stattdessen eine Reihe von Hashes haben, wird alles in demselben (neuen) Hash zusammengeführt.
Danke, das wollte ich auch! :)
Thanasis Petsas
Sie können auch tun.reduce(&:merge!)
Ben Lee
1
[{a: 1}, {b: 2}].reduce(&:merge!)bewertet zu{:a=>1, :b=>2}
Ben Lee
Dies funktioniert, weil injizieren / reduzieren eine Funktion hat, mit der Sie das Argument weglassen können. In diesem Fall wird das erste Argument des Arrays als Eingabeargument und der Rest des Arrays als Array verwendet. Kombinieren Sie das mit Symbol-to-Proc und Sie erhalten diese prägnante Konstruktion. Mit anderen Worten [{a: 1}, {b: 2}].reduce(&:merge!)ist das gleiche wie [{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) }das gleiche wie [{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) }.
Ben Lee
10

Enumeratorbeinhaltet Enumerable. Da 2.1hat Enumerableauch eine Methode #to_h. Deshalb können wir schreiben:

a = ["item 1", "item 2", "item 3", "item 4"]
a.each_slice(2).to_h
# => {"item 1"=>"item 2", "item 3"=>"item 4"}

Weil #each_slicewir ohne Block Enumeratorund gemäß der obigen Erklärung die #to_hMethode für das EnumeratorObjekt aufrufen können .

Arup Rakshit
quelle
7

Sie können dies für ein einzelnes Array versuchen

irb(main):019:0> a = ["item 1", "item 2", "item 3", "item 4"]
  => ["item 1", "item 2", "item 3", "item 4"]
irb(main):020:0> Hash[*a]
  => {"item 1"=>"item 2", "item 3"=>"item 4"}

für Array von Array

irb(main):022:0> a = [[1, 2], [3, 4]]
  => [[1, 2], [3, 4]]
irb(main):023:0> Hash[*a.flatten]
  => {1=>2, 3=>4}
Jenorish
quelle
6
a = ["item 1", "item 2", "item 3", "item 4"]
Hash[ a.each_slice( 2 ).map { |e| e } ]

oder, wenn Sie hassen Hash[ ... ]:

a.each_slice( 2 ).each_with_object Hash.new do |(k, v), h| h[k] = v end

oder, wenn Sie ein fauler Fan von defekter funktionaler Programmierung sind:

h = a.lazy.each_slice( 2 ).tap { |a|
  break Hash.new { |h, k| h[k] = a.find { |e, _| e == k }[1] }
}
#=> {}
h["item 1"] #=> "item 2"
h["item 3"] #=> "item 4"
Boris Stitnicky
quelle
Wenn Sie nicht ganz hassen, Hash[ ... ]aber es als verkettete Methode verwenden möchten (wie Sie es tun können to_h), können Sie Boris Vorschläge kombinieren und schreiben:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }
b-Studios
Um die Semantik des obigen Codes klarer zu machen: Dadurch wird ein "Lazy Hash" h erstellt , der anfangs leer ist , und bei Bedarf werden Elemente aus dem ursprünglichen Array a herausgezogen. Nur dann werden sie tatsächlich in h gespeichert!
Daniel Werner
1

Alle Antworten setzen voraus, dass das Startarray eindeutig ist. OP hat nicht angegeben, wie Arrays mit doppelten Einträgen behandelt werden sollen, die zu doppelten Schlüsseln führen.

Schauen wir uns an:

a = ["item 1", "item 2", "item 3", "item 4", "item 1", "item 5"]

Sie werden die verlieren item 1 => item 2Paar , wie es überschriebene bij ist item 1 => item 5:

Hash[*a]
=> {"item 1"=>"item 5", "item 3"=>"item 4"}

Alle Methoden, einschließlich des reduce(&:merge!)Ergebnisses, führen zum gleichen Entfernen.

Es könnte jedoch sein, dass dies genau das ist, was Sie erwarten. In anderen Fällen möchten Sie wahrscheinlich Arraystattdessen ein Ergebnis mit einem for-Wert erhalten:

{"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

Der naive Weg wäre, eine Hilfsvariable zu erstellen, einen Hash mit einem Standardwert, und diesen dann in eine Schleife zu füllen:

result = Hash.new {|hash, k| hash[k] = [] } # Hash.new with block defines unique defaults.
a.each_slice(2) {|k,v| result[k] << v }
a
=> {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

Es mag möglich sein, oben in einer Zeile zu verwenden assocund reducezu tun, aber es wird viel schwieriger, darüber nachzudenken und zu lesen.

berkes
quelle