Was ist der beste Weg, um ein Array in Ruby in einen Hash zu konvertieren?

123

In Ruby wird ein Array in einer der folgenden Formen angegeben ...

[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]

... was ist der beste Weg, um dies in einen Hash in Form von ...

{apple => 1, banana => 2}
Nathan Fritz
quelle

Antworten:

91

HINWEIS : Eine präzise und effiziente Lösung finden Sie in der Antwort von Marc-André Lafortune unten.

Diese Antwort wurde ursprünglich als Alternative zu Ansätzen mit Abflachung angeboten, die zum Zeitpunkt des Schreibens am besten bewertet wurden. Ich hätte klarstellen sollen, dass ich dieses Beispiel nicht als Best Practice oder effizienten Ansatz präsentieren wollte. Die ursprüngliche Antwort folgt.


Warnung! Bei Lösungen mit Flatten bleiben Array-Schlüssel oder -Werte nicht erhalten!

Aufbauend auf der beliebten Antwort von @John Topley versuchen wir Folgendes:

a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]

Dies wirft einen Fehler aus:

ArgumentError: odd number of arguments for Hash
        from (irb):10:in `[]'
        from (irb):10

Der Konstruktor erwartete ein Array mit gerader Länge (z. B. ['k1', 'v1,' k2 ',' v2 ']). Schlimmer ist, dass ein anderes Array, das auf eine gerade Länge abgeflacht ist, uns nur stillschweigend einen Hash mit falschen Werten gibt.

Wenn Sie Array-Schlüssel oder -Werte verwenden möchten, können Sie map verwenden :

h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"

Dadurch bleibt der Array-Schlüssel erhalten:

h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
Eintopf
quelle
15
Dies ist dasselbe wie Hash [a3], da a3 == a3.map {| k, v | [k, v]} ist wahr, es ist tatsächlich das Äquivalent zu a3.dup.
Cluster
2
Anstatt die Karte zu verwenden, geben Sie einfach die Tiefe der Abflachung an. Zum Beispiel: h3 = Hash[*a3.flatten(1)]Stattdessen h3 = Hash[*a3.flatten]würde ein Fehler ausgelöst.
Jeff McCune
3
Diese Antwort ist nicht effizient. Es ist auch veraltet. Siehe meine Antwort.
Marc-André Lafortune
1
Ja, ich denke Marc-André to_hist besser.
B Sieben
1
@ Marc-André Lafortune Danke, ich habe meine Antwort aktualisiert, um Benutzer an Ihre zu leiten.
Eintopf
145

Einfach benutzen Hash[*array_variable.flatten]

Beispielsweise:

a1 = ['apple', 1, 'banana', 2]
h1 = Hash[*a1.flatten(1)]
puts "h1: #{h1.inspect}"

a2 = [['apple', 1], ['banana', 2]]
h2 = Hash[*a2.flatten(1)]
puts "h2: #{h2.inspect}"

Durch Array#flatten(1)die Verwendung wird die Rekursion begrenzt, sodass ArraySchlüssel und Werte wie erwartet funktionieren.

John Topley
quelle
4
Oh, die Beredsamkeit! Deshalb liebe ich Ruby
iGbanam
11
WARNUNG: Antworten mit Flatten verursachen Probleme, wenn Sie Array-Schlüssel oder -Werte wünschen.
Eintopf
Ich habe unten eine alternative Lösung veröffentlicht, die Probleme mit Array-Schlüsseln oder -Werten vermeidet.
Eintopf
5
Es ist besser, nicht zu versuchen, eine umfassende Lösung dafür zu finden. Wenn Ihre Schlüssel und Werte wie in [[Schlüssel1, Wert1], [Schlüssel2, Wert2]] gepaart sind, übergeben Sie sie einfach ohne Mast an Hash []. Hash [a2] == Hash [* a2.flatten]. Wenn das Array bereits wie in [key1, value1, key2, value2] abgeflacht ist, stellen Sie dem var einfach *, Hash [* a1]
Cluster
8
FWIW, wenn Sie wirklich (eher eine) Einheitsversion wollen, können Sie auch verwenden Hash[*ary.flatten(1)], wodurch Array-Schlüssel und -Werte erhalten bleiben. Es ist die Rekursive flatten, die diese zerstört, was leicht zu vermeiden ist.
Brymck
79

Der beste Weg ist zu verwenden Array#to_h:

[ [:apple,1],[:banana,2] ].to_h  #=> {apple: 1, banana: 2}

Beachten Sie, dass to_hauch ein Block akzeptiert wird:

[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] } 
  # => {apple: "I like apples", banana: "I like bananas"}

Hinweis : to_hAkzeptiert einen Block in Ruby 2.6.0+. Für frühe Rubine kannst du meinen backportsEdelstein verwenden undrequire 'backports/2.6.0/enumerable/to_h'

to_h ohne Block wurde in Ruby 2.1.0 eingeführt.

Vor Ruby 2.1 konnte man das weniger lesbare verwenden Hash[]:

array = [ [:apple,1],[:banana,2] ]
Hash[ array ]  #= > {:apple => 1, :banana => 2}

Seien Sie vorsichtig bei Lösungen flatten, die Probleme mit Werten verursachen können, die selbst Arrays sind.

Marc-André Lafortune
quelle
4
Vielen Dank für die Einfachheit der neuen .to_h-Methode!
Codierung süchtig
3
Ich mag die to_hMethode besser als die obigen Antworten, weil sie die Absicht der Konvertierung nach dem Bearbeiten des Arrays ausdrückt .
B Sieben
1
@BSeven Weder Array#to_hnoch Enumerable#to_him Kern Ruby 1.9.
Iron Savior
Was ist, wenn ich ein Array als habe [[apple, 1], [banana, 2], [apple, 3], [banana, 4]]und die Ausgabe als möchte {"apple" =>[1,3], "banana"=>[2,4]}?
Nishant
@ NishantKumar das ist eine andere Frage.
Marc-André Lafortune
20

Aktualisieren

Ruby 2.1.0 wird heute veröffentlicht . Und ich komme mit Array#to_h( Versionshinweise und Ruby-Doc ), das das Problem der Konvertierung von a Arrayin a löstHash .

Beispiel für Ruby-Dokumente:

[[:foo, :bar], [1, 2]].to_h    # => {:foo => :bar, 1 => 2}
JanDintel
quelle
9

Bearbeiten: Habe die Antworten gepostet, während ich schrieb, Hash [a.flatten] scheint der richtige Weg zu sein. Muss dieses Bit in der Dokumentation verpasst haben, als ich über die Antwort nachdachte. Dachte, die Lösungen, die ich geschrieben habe, können bei Bedarf als Alternativen verwendet werden.

Die zweite Form ist einfacher:

a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }

a = Array, h = Hash, r = Rückgabewert-Hash (der, in dem wir uns ansammeln), i = Element im Array

Die beste Art und Weise, wie ich mir vorstellen kann, die erste Form zu machen, ist ungefähr so:

a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }
Daemin
quelle
2
+1 für den a.inject({})Einzeiler, der flexiblere Wertzuweisungen ermöglicht.
Chris Bloom
Es ist auch möglich, das h = {}aus dem zweiten Beispiel durch Verwendung von a.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
Inject zu löschen
Sie könnten tuna.each_slice(2).to_h
Conor O'Brien
6

Sie können ein 2D-Array auch einfach in Hash konvertieren, indem Sie:

1.9.3p362 :005 > a= [[1,2],[3,4]]

 => [[1, 2], [3, 4]]

1.9.3p362 :006 > h = Hash[a]

 => {1=>2, 3=>4} 
Priyanka
quelle
4

Zusammenfassung & TL; DR:

Diese Antwort soll eine umfassende Zusammenfassung von Informationen aus anderen Antworten sein.

Die sehr kurze Version, angesichts der Daten aus der Frage und einiger Extras:

flat_array   = [  apple, 1,   banana, 2  ] # count=4
nested_array = [ [apple, 1], [banana, 2] ] # count=2 of count=2 k,v arrays
incomplete_f = [  apple, 1,   banana     ] # count=3 - missing last value
incomplete_n = [ [apple, 1], [banana   ] ] # count=2 of either k or k,v arrays


# there's one option for flat_array:
h1  = Hash[*flat_array]                     # => {apple=>1, banana=>2}

# two options for nested_array:
h2a = nested_array.to_h # since ruby 2.1.0    => {apple=>1, banana=>2}
h2b = Hash[nested_array]                    # => {apple=>1, banana=>2}

# ok if *only* the last value is missing:
h3  = Hash[incomplete_f.each_slice(2).to_a] # => {apple=>1, banana=>nil}
# always ok for k without v in nested array:
h4  = Hash[incomplete_n] # or .to_h           => {apple=>1, banana=>nil}

# as one might expect:
h1 == h2a # => true
h1 == h2b # => true
h1 == h3  # => false
h3 == h4  # => true

Diskussion und Details folgen.


Setup: Variablen

Um die Daten anzuzeigen, die wir im Voraus verwenden werden, werde ich einige Variablen erstellen, um verschiedene Möglichkeiten für die Daten darzustellen. Sie passen in folgende Kategorien:

Basierend auf dem, was direkt in der Frage stand, als a1und a2:

(Anmerkung: Ich nehme das an apple und bananasollte Variablen darstellen. Wie andere auch, werde ich von nun an Zeichenfolgen verwenden, damit Eingabe und Ergebnisse übereinstimmen.)

a1 = [  'apple', 1 ,  'banana', 2  ] # flat input
a2 = [ ['apple', 1], ['banana', 2] ] # key/value paired input

Mehrwertige Schlüssel und / oder Werte wie folgt a3:

In einigen anderen Antworten wurde eine andere Möglichkeit vorgestellt (die ich hier erläutere) - Schlüssel und / oder Werte können Arrays für sich sein:

a3 = [ [ 'apple',                   1   ],
       [ 'banana',                  2   ],
       [ ['orange','seedless'],     3   ],
       [ 'pear',                 [4, 5] ],
     ]

Unausgeglichenes Array, wie a4:

Aus gutem Grund dachte ich, ich würde eine für den Fall hinzufügen, dass wir möglicherweise eine unvollständige Eingabe haben:

a4 = [ [ 'apple',                   1],
       [ 'banana',                  2],
       [ ['orange','seedless'],     3],
       [ 'durian'                    ], # a spiky fruit pricks us: no value!
     ]

Nun zur Arbeit:

Beginnend mit einem anfangs flachen Array, a1 :

Einige haben vorgeschlagen , mit #to_h(das zeigte sich in Ruby 2.1.0 und kann zurückportiert zu früheren Versionen). Bei einem anfangs flachen Array funktioniert dies nicht:

a1.to_h   # => TypeError: wrong element type String at 0 (expected array)

Die Verwendung in Hash::[]Kombination mit dem Splat-Operator bewirkt:

Hash[*a1] # => {"apple"=>1, "banana"=>2}

Das ist also die Lösung für den einfachen Fall, der durch dargestellt wird a1 .

Mit einem Array von Schlüssel / Wert-Paar-Arrays, a2 :

Mit einem Array von [key,value] Typ-Arrays gibt es zwei Möglichkeiten.

Erstens Hash::[]funktioniert immer noch (wie bei *a1):

Hash[a2] # => {"apple"=>1, "banana"=>2}

Und dann #to_hfunktioniert jetzt auch:

a2.to_h  # => {"apple"=>1, "banana"=>2}

Also zwei einfache Antworten für den einfachen Fall eines verschachtelten Arrays.

Dies gilt auch für Sub-Arrays als Schlüssel oder Werte, wie bei a3:

Hash[a3] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]} 
a3.to_h  # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]}

Aber Durian haben Spitzen (anomale Strukturen geben Probleme):

Wenn wir Eingabedaten erhalten haben, die nicht ausgeglichen sind, treten folgende Probleme auf #to_h:

a4.to_h  # => ArgumentError: wrong array length at 3 (expected 2, was 1)

Funktioniert aber Hash::[]immer noch, indem nur nilder Wert für durian(und jedes andere Array-Element in a4, das nur ein 1-Wert-Array ist) festgelegt wird:

Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}

Abflachen - mit neuen Variablen a5unda6

Einige andere Antworten flattenmit oder ohne 1Argument. Erstellen wir also einige neue Variablen:

a5 = a4.flatten
# => ["apple", 1, "banana", 2,  "orange", "seedless" , 3, "durian"] 
a6 = a4.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian"] 

Ich habe mich a4aufgrund des Gleichgewichtsproblems, das sich bei uns zeigte, für die Verwendung als Basisdaten entschieden a4.to_h. Ich glaube, ich rufe anflatten könnte ein Ansatz sein, mit dem jemand versuchen könnte, das zu lösen, der wie folgt aussehen könnte.

flattenohne Argumente ( a5):

Hash[*a5]       # => {"apple"=>1, "banana"=>2, "orange"=>"seedless", 3=>"durian"}
# (This is the same as calling `Hash[*a4.flatten]`.)

Auf einen naiven Blick scheint dies zu funktionieren - aber es hat uns mit den kernlosen Orangen auf den falschen Fuß gebracht und damit auch 3einen Schlüssel und durianeinen Wert geschaffen .

Und das a1funktioniert wie bei einfach nicht:

a5.to_h # => TypeError: wrong element type String at 0 (expected array)

Also a4.flattenist es für uns nicht nützlich, wir möchten es nur verwendenHash[a4]

Der flatten(1)Fall ( a6):

Aber was ist mit nur teilweiser Abflachung? Es ist erwähnenswert, dass das Aufrufen Hash::[]mit splatdem teilweise abgeflachten Array ( a6) nicht dasselbe ist wie das Aufrufen von Hash[a4]:

Hash[*a6] # => ArgumentError: odd number of arguments for Hash

Vorgeflachtes Array, immer noch verschachtelt (alternative Methode zum Abrufen a6):

Aber was wäre, wenn wir auf diese Weise das Array überhaupt bekommen hätten? (Das heißt, vergleichbar mit a1unseren Eingabedaten - nur dieses Mal können einige der Daten Arrays oder andere Objekte sein.) Wir haben gesehen, dass Hash[*a6]dies nicht funktioniert, aber was ist, wenn wir immer noch das Verhalten erhalten möchten, bei dem die Das letzte Element (wichtig! siehe unten) diente als Schlüssel für einen nilWert?

In einer solchen Situation gibt es immer noch eine Möglichkeit, dies zu tun, indem Enumerable#each_slicewir als Elemente im äußeren Array zu Schlüssel / Wert- Paaren zurückkehren :

a7 = a6.each_slice(2).to_a
# => [["apple", 1], ["banana", 2], [["orange", "seedless"], 3], ["durian"]] 

Beachten Sie, dass wir dadurch ein neues Array erhalten, das nicht " identisch " ist a4, aber dieselben Werte hat :

a4.equal?(a7) # => false
a4 == a7      # => true

Und so können wir wieder verwenden Hash::[]:

Hash[a7] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
# or Hash[a6.each_slice(2).to_a]

Aber es gibt ein Problem!

Es ist wichtig zu beachten, dass die each_slice(2)Lösung die Dinge nur dann wieder in Ordnung bringt, wenn der letzte Schlüssel derjenige war, dem ein Wert fehlt. Wenn wir später ein zusätzliches Schlüssel / Wert-Paar hinzufügen:

a4_plus = a4.dup # just to have a new-but-related variable name
a4_plus.push(['lychee', 4])
# => [["apple",                1],
#     ["banana",               2],
#     [["orange", "seedless"], 3], # multi-value key
#     ["durian"],              # missing value
#     ["lychee", 4]]           # new well-formed item

a6_plus = a4_plus.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian", "lychee", 4]

a7_plus = a6_plus.each_slice(2).to_a
# => [["apple",                1],
#     ["banana",               2],
#     [["orange", "seedless"], 3], # so far so good
#     ["durian",               "lychee"], # oops! key became value!
#     [4]]                     # and we still have a key without a value

a4_plus == a7_plus # => false, unlike a4 == a7

Und die beiden Hashes, die wir daraus erhalten würden, unterscheiden sich in wichtigen Punkten:

ap Hash[a4_plus] # prints:
{
                     "apple" => 1,
                    "banana" => 2,
    [ "orange", "seedless" ] => 3,
                    "durian" => nil, # correct
                    "lychee" => 4    # correct
}

ap Hash[a7_plus] # prints:
{
                     "apple" => 1,
                    "banana" => 2,
    [ "orange", "seedless" ] => 3,
                    "durian" => "lychee", # incorrect
                           4 => nil       # incorrect
}

(Hinweis: Ich verwende awesome_print's apnur, um die Darstellung der Struktur hier zu vereinfachen. Hierfür gibt es keine konzeptionellen Anforderungen.)

Die each_sliceLösung für einen unsymmetrischen flachen Eingang funktioniert also nur, wenn sich das unsymmetrische Bit ganz am Ende befindet.


Take-aways:

  1. Richten Sie die Eingabe für diese Dinge nach Möglichkeit [key, value]paarweise ein (ein Unterarray für jedes Element im äußeren Array).
  2. Wenn Sie das tatsächlich können, funktionieren beide #to_hoder Hash::[]beide.
  3. Wenn Sie nicht in der Lage sind, Hash::[]funktioniert die Kombination mit splat ( *), solange die Eingänge ausgeglichen sind .
  4. Mit einem unsymmetrischen und flachen Array als Eingabe funktioniert dies nur dann vernünftigerweise, wenn das letzte value Element das einzige ist, das fehlt.

Randnotiz: Ich poste diese Antwort, weil ich der Meinung bin, dass es einen Mehrwert gibt - einige der vorhandenen Antworten enthalten falsche Informationen, und keine (die ich gelesen habe) gab eine so vollständige Antwort, wie ich es hier zu tun versuche. Ich hoffe es ist hilfreich. Trotzdem danke ich denen, die vor mir kamen, von denen einige Inspiration für Teile dieser Antwort lieferten.

Lindes
quelle
3

An die Antwort anhängen, aber anonyme Arrays verwenden und Anmerkungen machen:

Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]

Nehmen Sie diese Antwort von innen heraus auseinander:

  • "a,b,c,d" ist eigentlich eine Zeichenfolge.
  • split auf Kommas in ein Array.
  • zip das zusammen mit dem folgenden Array.
  • [1,2,3,4] ist ein tatsächliches Array.

Das Zwischenergebnis ist:

[[a,1],[b,2],[c,3],[d,4]]

Abflachen wandelt das dann um in:

["a",1,"b",2,"c",3,"d",4]

und dann:

*["a",1,"b",2,"c",3,"d",4] rollt das in "a",1,"b",2,"c",3,"d",4

die wir als Argumente für die Hash[]Methode verwenden können:

Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]

was ergibt:

{"a"=>1, "b"=>2, "c"=>3, "d"=>4}
StevenJenkins
quelle
Dies funktioniert auch ohne splat ( *) und flatten: Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}. Weitere Details in einer Antwort, die ich hinzugefügt habe.
Lindes
0

Wenn Sie ein Array haben, das so aussieht -

data = [["foo",1,2,3,4],["bar",1,2],["foobar",1,"*",3,5,:foo]]

und Sie möchten, dass die ersten Elemente jedes Arrays die Schlüssel für den Hash werden und die restlichen Elemente zu Wert-Arrays, dann können Sie so etwas tun -

data_hash = Hash[data.map { |key| [key.shift, key] }]

#=>{"foo"=>[1, 2, 3, 4], "bar"=>[1, 2], "foobar"=>[1, "*", 3, 5, :foo]}
user3588841
quelle
0

Ich bin mir nicht sicher, ob es der beste Weg ist, aber das funktioniert:

a = ["apple", 1, "banana", 2]
m1 = {}
for x in (a.length / 2).times
  m1[a[x*2]] = a[x*2 + 1]
end

b = [["apple", 1], ["banana", 2]]
m2 = {}
for x,y in b
  m2[x] = y
end
Anders Sandvig
quelle
-1

Wenn die numerischen Werte seq-Indizes sind, könnten wir einfachere Möglichkeiten haben ... Hier ist meine Codeübermittlung, My Ruby ist ein bisschen verrostet

   input = ["cat", 1, "dog", 2, "wombat", 3]
   hash = Hash.new
   input.each_with_index {|item, index|
     if (index%2 == 0) hash[item] = input[index+1]
   }
   hash   #=> {"cat"=>1, "wombat"=>3, "dog"=>2}
Gishu
quelle