Frage

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

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

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

{apple => 1, banana => 2}
War es hilfreich?

Lösung

NOTIZ:Eine prägnante und effiziente Lösung finden Sie unter Die Antwort von Marc-André Lafortune unten.

Diese Antwort wurde ursprünglich als Alternative zu Ansätzen mit Flatten angeboten, die zum Zeitpunkt des Verfassens dieses Artikels am meisten positiv 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! Lösungen mit ebnen behält keine Array-Schlüssel oder -Werte bei!

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

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

Dies löst 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']).Was noch schlimmer ist, ist, dass ein anderes Array, das auf eine gleichmäßige Länge reduziert wurde, uns stillschweigend einen Hash mit falschen Werten liefern würde.

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

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}

Andere Tipps

Einfach nutzen Hash[*array_variable.flatten]

Zum Beispiel:

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}"

Benutzen Array#flatten(1) schränkt die Rekursion so ein Array Schlüssel und Werte funktionieren wie erwartet.

Der beste Weg ist die Verwendung Array#to_h:

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

Beachten Sie, dass to_h akzeptiert auch einen Block:

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

Notiz: to_h akzeptiert einen Block in Ruby 2.6.0+;Für frühe Rubine können Sie meine verwenden backports Juwel und require '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 die weniger lesbaren verwenden Hash[]:

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

Seien Sie schließlich vorsichtig bei allen Lösungen, die Folgendes verwenden flatten, könnte dies zu Problemen mit Werten führen, die selbst Arrays sind.

Aktualisieren

Ruby 2.1.0 wird heute veröffentlicht.Und ich komme mit Array#to_h (Versionshinweise Und ruby-doc), was das Problem der Konvertierung von an löst Array zu einem Hash.

Beispiel für Ruby-Dokumente:

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

Bearbeiten:Als ich die Antworten sah, die ich beim Schreiben gepostet habe, scheint Hash[a.flatten] der richtige Weg zu sein.Ich muss diesen Teil in der Dokumentation übersehen haben, als ich über die Antwort nachgedacht habe.Ich 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 akkumulieren), i = Element im Array

Der beste Weg, den ich mir vorstellen kann, ist etwa so:

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

Sie können ein 2D-Array auch einfach in einen Hash umwandeln, indem Sie Folgendes verwenden:

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

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

1.9.3p362 :006 > h = Hash[a]

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

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

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

Nehmen Sie diese Antwort auseinander und beginnen Sie von innen:

  • "a,b,c,d" ist eigentlich eine Zeichenfolge.
  • split über 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]]

flatten 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 aus"a",1,"b",2,"c",3,"d",4

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

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

was ergibt:

{"a"=>1, "b"=>2, "c"=>3, "d"=>4}

Zusammenfassung & TL;DR:

Diese Antwort soll eine umfassende Zusammenfassung der 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.


Aufstellen:Variablen

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

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

(Notiz:Das nehme ich an apple Und banana sollten Variablen darstellen.Wie andere es auch getan haben, werde ich von nun an Zeichenfolgen verwenden, damit Eingabe und Ergebnisse übereinstimmen können.)

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

Mehrwertige Schlüssel und/oder Werte, wie z a3:

In einigen anderen Antworten wurde eine andere Möglichkeit vorgestellt (auf die ich hier näher eingehen werde): Schlüssel und/oder Werte können eigenständige Arrays sein:

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

Unsymmetrisches Array, wie a4:

Der Sicherheit halber dachte ich, ich füge noch eine für den Fall hinzu, 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 zunächst flachen Array, a1:

Einige haben die Verwendung vorgeschlagen #to_h (was in Ruby 2.1.0 auftauchte und sein kann zurückportiert zu früheren Versionen).Für ein anfänglich flaches Array funktioniert dies nicht:

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

Benutzen Hash::[] kombiniert mit dem Splat-Operator tut:

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 aus Schlüssel/Wert-Paar-Arrays, a2:

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

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

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

Und dann auch #to_h funktioniert jetzt:

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

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

Dies gilt auch für Unterarrays als Schlüssel oder Werte, wie z 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 Durians haben Spitzen (anomale Strukturen verursachen Probleme):

Wenn wir Eingabedaten erhalten, die nicht ausgewogen sind, werden wir auf Probleme stoßen #to_h:

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

Aber Hash::[] Funktioniert immer noch, nur Einstellung nil als Wert für durian (und jedes andere Array-Element in a4, das nur ein Array mit einem Wert ist):

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

Abflachung – Verwendung neuer Variablen a5 Und a6

Ein paar andere Antworten erwähnt flatten, mit oder ohne a 1 Argument, also erstellen wir 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 für die Verwendung entschieden a4 als Basisdaten aufgrund des Gleichgewichtsproblems, das wir hatten und das mit auftrat a4.to_h.Ich denke, ich rufe an flatten könnte ein Ansatz sein, mit dem jemand versuchen könnte, das Problem 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 den naiven Blick scheint das zu funktionieren – aber es hat uns mit den kernlosen Orangen auf den falschen Fuß erwischt und somit auch Erfolg gebracht 3 A Schlüssel Und durian A Wert.

Und das, wie mit a1, funktioniert einfach nicht:

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

Also a4.flatten ist für uns nicht nützlich, wir möchten es nur nutzen Hash[a4]

Der flatten(1) Fall (a6):

Aber was ist mit einer nur teilweisen Abflachung?Diese Berufung ist erwähnenswert Hash::[] verwenden splat auf dem teilweise abgeflachten Array (a6) Ist nicht das Gleiche wie anrufen Hash[a4]:

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

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

Aber was wäre, wenn wir auf diese Weise überhaupt an das Array gekommen wären?(Das heißt, vergleichbar mit a1, es waren unsere Eingabedaten – nur dieses Mal können einige der Daten Arrays oder andere Objekte sein.) Das haben wir gesehen Hash[*a6] funktioniert nicht, aber was wäre, wenn wir trotzdem das Verhalten erhalten wollten, bei dem die letztes Element (wichtig!siehe unten) fungierte als Schlüssel für a nil Wert?

In einer solchen Situation gibt es immer noch eine Möglichkeit, dies zu tun: Enumerable#each_slice um zum Schlüssel/Wert zurückzukehren Paare als Elemente im äußeren Array:

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

Beachten Sie, dass wir dadurch letztendlich ein neues Array erhalten, das nicht „identisch" Zu a4, hat aber das gleiche Werte:

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

Und so können wir es wieder nutzen 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) Die Lösung bringt die Dinge nur dann wieder in Ordnung, wenn die zuletzt Der Schlüssel war derjenige, dem ein Wert fehlte.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 dadurch 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
}

(Notiz:Ich benutze awesome_print'S ap nur um es einfacher zu machen, die Struktur hier darzustellen;hierfür gibt es keine konzeptionelle Anforderung.)

Also die each_slice Die Lösung für einen unsymmetrischen flachen Eingang funktioniert nur, wenn sich das unsymmetrische Bit ganz am Ende befindet.


Take-aways:

  1. Wenn möglich, richten Sie die Eingabe für diese Dinge ein als [key, value] Paare (ein Unterarray für jedes Element im äußeren Array).
  2. Auch wenn Sie das tatsächlich können #to_h oder Hash::[] wird beides funktionieren.
  3. Wenn Sie dazu nicht in der Lage sind, Hash::[] kombiniert mit dem Splat (*) wird funktionieren, solange die Eingänge ausgeglichen sind.
  4. Mit einem unausgeglichen Und Wohnung Array als Eingabe verwenden, funktioniert dies nur dann vernünftigerweise, wenn das zuletzt value Der Artikel ist der einzige, der fehlt.

Randnotiz:Ich veröffentliche 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) hat eine so vollständige Antwort gegeben, wie ich es hier versuche.Ich hoffe, dass es hilfreich ist.Dennoch danke ich denen, die vor mir kamen und von denen einige Teile dieser Antwort inspiriert haben.

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 zu Schlüsseln für den Hash werden und die restlichen Elemente zu Wertearrays werden, 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. Mein Ruby ist etwas eingerostet

   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}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top