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.

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]

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}

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}

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 }

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}


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 .


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}

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"

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.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.