Jaki jest najlepszy sposób konwersji tablicy na skrót w Rubim


123

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:


91

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}

15
To to samo, co Hash [a3], ponieważ a3 == a3.map {| k, v | [k, v]} jest prawdą, w rzeczywistości jest odpowiednikiem a3.dup.
Klaster

2
Zamiast używać mapy, dlaczego po prostu nie określić głębokości spłaszczenia? Na przykład: h3 = Hash[*a3.flatten(1)]zamiast tego h3 = Hash[*a3.flatten]zgłosiłby błąd.
Jeff McCune,

3
Ta odpowiedź nie jest skuteczna. Jest również nieaktualny. Zobacz moją odpowiedź.
Marc-André Lafortune

1
Tak, myślę, że Marc-André to_hjest lepszy.
B Seven

1
@ Marc-André Lafortune dziękuję, zaktualizowałem moją odpowiedź, aby kierować użytkowników do Twojej.
Gulasz

145

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 Arrayklucze i wartości działały zgodnie z oczekiwaniami.


4
Och, elokwencja! Dlatego uwielbiam Ruby
iGbanam,

11
OSTRZEŻENIE: odpowiedzi przy użyciu spłaszczania spowodują problemy, jeśli chcesz użyć kluczy lub wartości tablic.
Gulasz

Poniżej zamieściłem alternatywne rozwiązanie, które pozwoli uniknąć problemów z kluczami lub wartościami tablicy.
Gulasz

5
Lepiej nie próbować znaleźć uniwersalnego rozwiązania. Jeśli twoje klucze i wartości są sparowane, jak w [[klucz1, wartość1], [klucz2, wartość2]], po prostu przekaż je do Hash [] bez tuczenia. Hash [a2] == Hash [* a2.flatten]. Jeśli tablica jest już spłaszczona, jak w, [klucz1, wartość1, klucz2, wartość2], po prostu poprzedz zmienną zmienną znakiem *, Hash [* a1]
Cluster

8
FWIW, jeśli naprawdę chcesz (bardziej) jednej uniwersalnej wersji dla wszystkich, możesz również użyć Hash[*ary.flatten(1)], która zachowa klucze i wartości tablic. To rekurencja flattenniszczy te, których łatwo jest uniknąć.
brymck

79

Najlepszym sposobem jest użycie Array#to_h:

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

Zauważ, że to_hakceptuje również blok:

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

Uwaga : to_hakceptuje blok w Rubim 2.6.0+; za wczesne rubiny możesz użyć mojego backportsklejnotu 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.


4
Dzięki za prostotę nowej metody .to_h!
uzależniony od kodowania

3
to_hMetoda podoba mi się bardziej niż powyższe odpowiedzi, ponieważ wyraża zamiar konwersji po operacji na tablicy.
B Seven

1
@BSeven Ani Array#to_hteż nie Enumerable#to_hjest w rdzeniu Ruby 1.9.
Iron Saviour

Co się stanie, jeśli mam tablicę jako [[apple, 1], [banana, 2], [apple, 3], [banana, 4]]i chcę, aby dane wyjściowe były {"apple" =>[1,3], "banana"=>[2,4]}?
nishant

@NishantKumar to inne pytanie.
Marc-André Lafortune


9

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 }

2
+1 za a.inject({})jedną linijkę, która pozwala na bardziej elastyczne przypisywanie wartości.
Chris Bloom,

Możliwe jest również usunięcie 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 }
lindes

Możesz to zrobića.each_slice(2).to_h
Conor O'Brien

6

Możesz także po prostu przekonwertować tablicę 2D na hash za pomocą:

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

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

1.9.3p362 :006 > h = Hash[a]

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

4

Podsumowanie i TL; DR:

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.


Konfiguracja: zmienne

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:

Na podstawie tego, co było bezpośrednio w pytaniu, jak a1i a2:

(Uwaga: zakładam, że tak jest applei bananamiał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

Klucze i / lub wartości wielowartościowe, jak 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] ],
     ]

Niezbalansowana tablica, jak 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!
     ]

A teraz do pracy:

Zaczynając od początkowo płaskiej tablicy 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.

Z tablicą tablic par klucz / wartość 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_hdziała również teraz:

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

A więc dwie proste odpowiedzi na prosty przypadek tablicy zagnieżdżonej.

Jest to prawdą nawet w przypadku tablic podrzędnych jako kluczy lub wartości, jak w przypadku 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]}

Ale duriany mają kolce (anomalne struktury powodują problemy):

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

Spłaszczanie - przy użyciu nowych zmiennych a5ia6

Wspomniano kilka innych odpowiedzi flatten, z 1argumentami 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ć a4jako 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.

flattenbez 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 3do klucza i duriando 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.flattennie jest dla nas przydatny, chcielibyśmy po prostu użyćHash[a4]

Obudowa flatten(1)( a6):

Ale co z tylko częściowym spłaszczeniem? Warto zauważyć, że wywołanie Hash::[]using splatna częściowo spłaszczonej tablicy ( a6) nie jest tym samym, co wywołanie Hash[a4]:

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

Tablica wstępnie spłaszczona, nadal zagnieżdżona (alternatywny sposób pobierania 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 nilwartości?

W takiej sytuacji nadal istnieje sposób, aby to zrobić, używając Enumerable#each_slicedo 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]

Ale jest problem!

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_slicerozwiązanie niezbalansowanego płaskiego wejścia działa tylko wtedy, gdy niezbalansowany bit znajduje się na samym końcu.


Dania na wynos:

  1. Jeśli to możliwe, ustaw dane wejściowe dla tych rzeczy jako [key, value]pary (podtablica dla każdego elementu w tablicy zewnętrznej).
  2. Kiedy rzeczywiście możesz to zrobić, jedno #to_hlub Hash::[]drugie zadziała.
  3. Jeśli nie możesz tego zrobić, w Hash::[]połączeniu ze splat ( *) zadziała, o ile wejścia są zbalansowane .
  4. W przypadku niezbalansowanej i płaskiej tablicy jako danych wejściowych jedyny sposób, w jaki będzie to działać w rozsądny sposób, to to, że ostatni 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.


3

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}

Działa to również bez splat ( *) 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.
Lindes

0

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

0

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

-1

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}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.