W Rubim, mając tablicę w jednej z następujących form ...
[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]
... jaki jest najlepszy sposób na przekształcenie tego w hash w postaci ...
{apple => 1, banana => 2}
W Rubim, mając tablicę w jednej z następujących form ...
[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]
... jaki jest najlepszy sposób na przekształcenie tego w hash w postaci ...
{apple => 1, banana => 2}
Odpowiedzi:
UWAGA : Aby uzyskać zwięzłe i wydajne rozwiązanie, zapoznaj się z odpowiedzią Marca-André Lafortune'a poniżej.
Ta odpowiedź była pierwotnie oferowana jako alternatywa dla podejść wykorzystujących spłaszczenie, które były najbardziej opiniotwórcze w momencie pisania. Powinienem był wyjaśnić, że nie zamierzałem przedstawiać tego przykładu jako najlepszej praktyki lub skutecznego podejścia. Następuje oryginalna odpowiedź.
Ostrzeżenie! Rozwiązania wykorzystujące spłaszczenie nie zachowują kluczy ani wartości tablicy!
Opierając się na popularnej odpowiedzi @John Topley, spróbujmy:
a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]
Powoduje to wyświetlenie błędu:
ArgumentError: odd number of arguments for Hash
from (irb):10:in `[]'
from (irb):10
Konstruktor oczekiwał tablicy o parzystej długości (np. ['K1', 'v1,' k2 ',' v2 ']). Co gorsza, inna tablica, która spłaszczyłaby się do równej długości, po cichu dałby nam Hash z nieprawidłowymi wartościami.
Jeśli chcesz użyć kluczy lub wartości tablicy, możesz użyć map :
h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"
To zachowuje klucz Array:
h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
h3 = Hash[*a3.flatten(1)]
zamiast tego h3 = Hash[*a3.flatten]
zgłosiłby błąd.
to_h
jest lepszy.
Po prostu użyj Hash[*array_variable.flatten]
Na przykład:
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}"
Użycie Array#flatten(1)
ogranicza rekursję, aby Array
klucze i wartości działały zgodnie z oczekiwaniami.
Hash[*ary.flatten(1)]
, która zachowa klucze i wartości tablic. To rekurencja flatten
niszczy te, których łatwo jest uniknąć.
Najlepszym sposobem jest użycie Array#to_h
:
[ [:apple,1],[:banana,2] ].to_h #=> {apple: 1, banana: 2}
Zauważ, że to_h
akceptuje również blok:
[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] }
# => {apple: "I like apples", banana: "I like bananas"}
Uwaga : to_h
akceptuje blok w Rubim 2.6.0+; za wczesne rubiny możesz użyć mojego backports
klejnotu irequire 'backports/2.6.0/enumerable/to_h'
to_h
bez bloku został wprowadzony w Rubim 2.1.0.
Przed Ruby 2.1 można było używać mniej czytelnych Hash[]
:
array = [ [:apple,1],[:banana,2] ]
Hash[ array ] #= > {:apple => 1, :banana => 2}
Wreszcie, uważaj na jakiekolwiek rozwiązania wykorzystujące flatten
, może to spowodować problemy z wartościami, które same są tablicami.
to_h
Metoda podoba mi się bardziej niż powyższe odpowiedzi, ponieważ wyraża zamiar konwersji po operacji na tablicy.
Array#to_h
też nie Enumerable#to_h
jest w rdzeniu Ruby 1.9.
[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]
i chcę, aby dane wyjściowe były {"apple" =>[1,3], "banana"=>[2,4]}
?
Aktualizacja
Dzisiaj został wydany Ruby 2.1.0 . I przychodzę z Array#to_h
( uwagi do wydania i ruby-doc ), co rozwiązuje problem konwersji pliku an Array
na plik Hash
.
Przykład dokumentacji Ruby:
[[:foo, :bar], [1, 2]].to_h # => {:foo => :bar, 1 => 2}
Edycja: Widziałem odpowiedzi opublikowane podczas pisania, Hash [a.flatten] wydaje się być właściwą drogą. Musiałem przegapić ten fragment w dokumentacji, kiedy zastanawiałem się nad odpowiedzią. Pomyślałem, że rozwiązania, które napisałem, można w razie potrzeby użyć jako alternatywy.
Druga forma jest prostsza:
a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }
a = tablica, h = hash, r = hash wartości zwracanej (ten, w którym gromadzimy), i = element w tablicy
Najładniejszy sposób, w jaki przychodzi mi do głowy wykonanie pierwszej formy, to coś takiego:
a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }
a.inject({})
jedną linijkę, która pozwala na bardziej elastyczne przypisywanie wartości.
h = {}
z drugiego przykładu przez użycie inject, kończąc naa.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
a.each_slice(2).to_h
Mam nadzieję, że ta odpowiedź będzie wyczerpującym podsumowaniem informacji uzyskanych z innych odpowiedzi.
Bardzo krótka wersja, biorąc pod uwagę dane z pytania oraz kilka dodatków:
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
Następuje dyskusja i szczegóły.
Aby z góry pokazać dane, których będziemy używać, utworzę zmienne reprezentujące różne możliwości danych. Pasują do następujących kategorii:
a1
i a2
:(Uwaga: zakładam, że tak jest apple
i banana
miały one reprezentować zmienne. Podobnie jak inni, odtąd będę używać ciągów znaków, aby dane wejściowe i wyniki mogły się zgadzać).
a1 = [ 'apple', 1 , 'banana', 2 ] # flat input
a2 = [ ['apple', 1], ['banana', 2] ] # key/value paired input
a3
:W niektórych innych odpowiedziach przedstawiono inną możliwość (którą tutaj rozwijam) - klucze i / lub wartości mogą same być tablicami:
a3 = [ [ 'apple', 1 ],
[ 'banana', 2 ],
[ ['orange','seedless'], 3 ],
[ 'pear', [4, 5] ],
]
a4
:Pomyślałem, że dodam jeden w przypadku, gdy możemy mieć niepełne dane wejściowe:
a4 = [ [ 'apple', 1],
[ 'banana', 2],
[ ['orange','seedless'], 3],
[ 'durian' ], # a spiky fruit pricks us: no value!
]
a1
:Niektórzy sugerowali użycie #to_h
(które pojawiło się w Ruby 2.1.0 i może zostać przeniesione do wcześniejszych wersji). W przypadku początkowo płaskiej tablicy to nie działa:
a1.to_h # => TypeError: wrong element type String at 0 (expected array)
Używanie w Hash::[]
połączeniu z operatorem splat powoduje:
Hash[*a1] # => {"apple"=>1, "banana"=>2}
To jest rozwiązanie dla prostego przypadku reprezentowanego przez a1
.
a2
:W przypadku tablicy [key,value]
tablic typów istnieją dwa sposoby.
Po pierwsze, Hash::[]
nadal działa (tak jak w przypadku *a1
):
Hash[a2] # => {"apple"=>1, "banana"=>2}
A potem #to_h
działa również teraz:
a2.to_h # => {"apple"=>1, "banana"=>2}
A więc dwie proste odpowiedzi na prosty przypadek tablicy zagnieżdżonej.
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]}
Jeśli otrzymaliśmy niezbilansowane dane wejściowe, napotkamy problemy z #to_h
:
a4.to_h # => ArgumentError: wrong array length at 3 (expected 2, was 1)
Ale Hash::[]
nadal działa, po prostu ustawiając nil
jako wartość dla durian
(i każdy inny element tablicy w a4, który jest po prostu tablicą z 1 wartością):
Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
a5
ia6
Wspomniano kilka innych odpowiedzi flatten
, z 1
argumentami lub bez , więc stwórzmy kilka nowych zmiennych:
a5 = a4.flatten
# => ["apple", 1, "banana", 2, "orange", "seedless" , 3, "durian"]
a6 = a4.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian"]
Zdecydowałem się użyć a4
jako bazy danych z powodu problemu z równowagą, który pojawił się z a4.to_h
. Wydaje mi się, flatten
że dzwonienie może być podejściem, którego ktoś mógłby użyć, aby rozwiązać ten problem, co może wyglądać następująco.
flatten
bez argumentów ( a5
):Hash[*a5] # => {"apple"=>1, "banana"=>2, "orange"=>"seedless", 3=>"durian"}
# (This is the same as calling `Hash[*a4.flatten]`.)
W naiwnej rzut oka wydaje do pracy - ale to ma nas na niewłaściwym stóp z pestek pomarańczy, dlatego też co 3
do klucza i durian
do wartości .
I to, podobnie jak w przypadku a1
, po prostu nie działa:
a5.to_h # => TypeError: wrong element type String at 0 (expected array)
Więc a4.flatten
nie jest dla nas przydatny, chcielibyśmy po prostu użyćHash[a4]
flatten(1)
( a6
):Ale co z tylko częściowym spłaszczeniem? Warto zauważyć, że wywołanie Hash::[]
using splat
na częściowo spłaszczonej tablicy ( a6
) nie jest tym samym, co wywołanie Hash[a4]
:
Hash[*a6] # => ArgumentError: odd number of arguments for Hash
a6
):Ale co, jeśli tak właśnie otrzymaliśmy tablicę? (Oznacza to, a1
że były to nasze dane wejściowe - tylko tym razem niektóre dane mogą być tablicami lub innymi obiektami). Widzieliśmy, że Hash[*a6]
to nie działa, ale co by było, gdybyśmy nadal chcieli uzyskać zachowanie, w którym ostatni element (ważny! patrz poniżej) działał jako klucz dla nil
wartości?
W takiej sytuacji nadal istnieje sposób, aby to zrobić, używając Enumerable#each_slice
do powrotu do par klucz / wartość jako elementów w tablicy zewnętrznej:
a7 = a6.each_slice(2).to_a
# => [["apple", 1], ["banana", 2], [["orange", "seedless"], 3], ["durian"]]
Zwróć uwagę, że w rezultacie otrzymujemy nową tablicę, która nie jest „ identyczna ” z a4
, ale ma te same wartości :
a4.equal?(a7) # => false
a4 == a7 # => true
I tak możemy ponownie użyć Hash::[]
:
Hash[a7] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
# or Hash[a6.each_slice(2).to_a]
Należy zauważyć, że each_slice(2)
rozwiązanie przywraca normalność tylko wtedy, gdy ostatni klucz nie zawiera wartości. Jeśli później dodaliśmy dodatkową parę klucz / wartość:
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
A dwa skróty, które otrzymamy z tego, są różne w ważnych aspektach:
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
}
(Uwaga: używam awesome_print
„s ap
. Tak aby łatwiej pokazać strukturę tutaj; nie ma koncepcyjnego wymóg ten temat)
Tak więc each_slice
rozwiązanie niezbalansowanego płaskiego wejścia działa tylko wtedy, gdy niezbalansowany bit znajduje się na samym końcu.
[key, value]
pary (podtablica dla każdego elementu w tablicy zewnętrznej).#to_h
lub Hash::[]
drugie zadziała.Hash::[]
połączeniu ze splat ( *
) zadziała, o ile wejścia są zbalansowane .value
element jest jedynym, którego brakuje.Na marginesie: zamieszczam tę odpowiedź, ponieważ czuję, że warto ją dodać - niektóre z istniejących odpowiedzi zawierają niepoprawne informacje, a żadna (którą przeczytałem) nie dała tak kompletnej odpowiedzi, jak staram się tutaj zrobić. Mam nadzieję, że jest to pomocne. Niemniej jednak dziękuję tym, którzy byli przede mną, a kilku z nich zainspirowało części tej odpowiedzi.
Dołączanie do odpowiedzi, ale przy użyciu anonimowych tablic i adnotacji:
Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]
Biorąc tę odpowiedź na bok, zaczynając od środka:
"a,b,c,d"
jest w rzeczywistości ciągiem.split
przecinkami do tablicy.zip
razem z następującą tablicą.[1,2,3,4]
jest rzeczywistą tablicą.Wynik pośredni to:
[[a,1],[b,2],[c,3],[d,4]]
spłaszcz, a następnie przekształca to w:
["a",1,"b",2,"c",3,"d",4]
i wtedy:
*["a",1,"b",2,"c",3,"d",4]
rozwija to do
"a",1,"b",2,"c",3,"d",4
których możemy użyć jako argumentów do Hash[]
metody:
Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]
co daje:
{"a"=>1, "b"=>2, "c"=>3, "d"=>4}
*
) i spłaszczenia: Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]
=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}
. Więcej szczegółów w odpowiedzi, którą dodałem.
jeśli masz tablicę, która wygląda tak -
data = [["foo",1,2,3,4],["bar",1,2],["foobar",1,"*",3,5,:foo]]
i chcesz, aby pierwsze elementy każdej tablicy stały się kluczami dla skrótu, a reszta elementów stała się tablicami wartości, możesz zrobić coś takiego -
data_hash = Hash[data.map { |key| [key.shift, key] }]
#=>{"foo"=>[1, 2, 3, 4], "bar"=>[1, 2], "foobar"=>[1, "*", 3, 5, :foo]}
Nie jestem pewien, czy to najlepszy sposób, ale to działa:
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
Jeśli wartości liczbowe są indeksami sekwencyjnymi, moglibyśmy mieć prostsze sposoby ... Oto moje przesłanie kodu, Mój Ruby jest trochę zardzewiały
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}