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}
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}
Antworten:
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}
h3 = Hash[*a3.flatten(1)]
Stattdessen h3 = Hash[*a3.flatten]
würde ein Fehler ausgelöst.
to_h
ist besser.
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 Array
Schlüssel und Werte wie erwartet funktionieren.
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.
Der beste Weg ist zu verwenden Array#to_h
:
[ [:apple,1],[:banana,2] ].to_h #=> {apple: 1, banana: 2}
Beachten Sie, dass to_h
auch ein Block akzeptiert wird:
[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] }
# => {apple: "I like apples", banana: "I like bananas"}
Hinweis : to_h
Akzeptiert einen Block in Ruby 2.6.0+. Für frühe Rubine kannst du meinen backports
Edelstein 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.
to_h
Methode besser als die obigen Antworten, weil sie die Absicht der Konvertierung nach dem Bearbeiten des Arrays ausdrückt .
Array#to_h
noch Enumerable#to_h
im Kern Ruby 1.9.
[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]
und die Ausgabe als möchte {"apple" =>[1,3], "banana"=>[2,4]}
?
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 Array
in a löstHash
.
Beispiel für Ruby-Dokumente:
[[:foo, :bar], [1, 2]].to_h # => {:foo => :bar, 1 => 2}
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 }
a.inject({})
Einzeiler, der flexiblere Wertzuweisungen ermöglicht.
h = {}
aus dem zweiten Beispiel durch Verwendung von a.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
a.each_slice(2).to_h
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.
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:
a1
und a2
:(Anmerkung: Ich nehme das an apple
und banana
sollte 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
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] ],
]
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!
]
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
.
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_h
funktioniert jetzt auch:
a2.to_h # => {"apple"=>1, "banana"=>2}
Also zwei einfache Antworten für den einfachen Fall eines verschachtelten Arrays.
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]}
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 nil
der 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}
a5
unda6
Einige andere Antworten flatten
mit oder ohne 1
Argument. 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 a4
aufgrund 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.
flatten
ohne 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 3
einen Schlüssel und durian
einen Wert geschaffen .
Und das a1
funktioniert wie bei einfach nicht:
a5.to_h # => TypeError: wrong element type String at 0 (expected array)
Also a4.flatten
ist es für uns nicht nützlich, wir möchten es nur verwendenHash[a4]
flatten(1)
Fall ( a6
):Aber was ist mit nur teilweiser Abflachung? Es ist erwähnenswert, dass das Aufrufen Hash::[]
mit splat
dem teilweise abgeflachten Array ( a6
) nicht dasselbe ist wie das Aufrufen von Hash[a4]
:
Hash[*a6] # => ArgumentError: odd number of arguments for Hash
a6
):Aber was wäre, wenn wir auf diese Weise das Array überhaupt bekommen hätten? (Das heißt, vergleichbar mit a1
unseren 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 nil
Wert?
In einer solchen Situation gibt es immer noch eine Möglichkeit, dies zu tun, indem Enumerable#each_slice
wir 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]
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 ap
nur, um die Darstellung der Struktur hier zu vereinfachen. Hierfür gibt es keine konzeptionellen Anforderungen.)
Die each_slice
Lösung für einen unsymmetrischen flachen Eingang funktioniert also nur, wenn sich das unsymmetrische Bit ganz am Ende befindet.
[key, value]
paarweise ein (ein Unterarray für jedes Element im äußeren Array).#to_h
oder Hash::[]
beide.Hash::[]
funktioniert die Kombination mit splat ( *
), solange die Eingänge ausgeglichen sind .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.
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}
*
) 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.
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]}
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
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}