Tablica do Hash Ruby


192

Dobra, więc oto oferta, od wieków szukam rozwiązania tego problemu i chociaż jest ich wielu, wydaje się, że nie wykonują pracy, której szukam.

Zasadniczo mam taką strukturę tablicy

["item 1", "item 2", "item 3", "item 4"] 

Chcę przekonwertować to na skrót, więc wygląda to tak

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

tzn. elementy, które znajdują się w indeksach „parzystych” są kluczami, a elementy w indeksach „nieparzystych” są wartościami.

Wszelkie pomysły, jak to zrobić czysto? Podejrzewam, że metodą brutalnej siły byłoby po prostu wyciągnięcie wszystkich parzystych indeksów do oddzielnej tablicy, a następnie zapętlenie ich w celu dodania wartości.

Odpowiedzi:


358
a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }

Otóż ​​to. *Nazywa się ikona operator.

Jedno zastrzeżenie dla @Mike Lewis (w komentarzach): „Bądź bardzo ostrożny. Ruby rozwija stosy na stosie. Jeśli zrobisz to z dużym zestawem danych, spodziewaj się wysadzenia stosu”.

Tak więc w większości ogólnych przypadków użycia ta metoda jest świetna, ale użyj innej metody, jeśli chcesz wykonać konwersję na wielu danych. Na przykład @ Łukasz Niemier (również w komentarzach) oferuje tę metodę dla dużych zestawów danych:

h = Hash[a.each_slice(2).to_a]

10
@tester, *nazywany jest operatorem splat . Pobiera tablicę i przekształca ją w dosłowną listę elementów. Więc *[1,2,3,4]=> 1, 2, 3, 4. W tym przykładzie powyższe jest równoważne z działaniem Hash["item 1", "item 2", "item 3", "item 4"]. I Hashma []metodę, która akceptuje listę argumentów (tworząc klucze indeksów nieparzystych i wartości indeksów nieparzystych), ale Hash[]nie akceptuje tablicy, więc używamy jej do splatania *.
Ben Lee,

15
Bądź z tym bardzo ostrożny. Ruby rozwija ikony na stosie. Jeśli zrobisz to z dużym zestawem danych, spodziewaj się wysadzenia stosu.
Mike Lewis

9
W tabelach dużych danych możesz użyć Hash[a.each_slice(2).to_a].
Hauleth

4
Co oznacza „wysadzić swój stos”?
Kevin

6
@Kevin, stos używa niewielkiego obszaru pamięci, który program przydziela i rezerwuje na określone operacje. Najczęściej służy do przechowywania stosu metod, które do tej pory były wywoływane. To jest początek terminu śledzenia stosu i dlatego też nieskończenie rekurencyjna metoda może spowodować przepełnienie stosu . Metoda w tej odpowiedzi używa również stosu, ale ponieważ stos jest tylko niewielkim obszarem pamięci, jeśli spróbujesz tej metody z dużą tablicą, wypełni on stos i spowoduje błąd (błąd wzdłuż tych samych linii co przepełnienie stosu).
Ben Lee

103

Ruby 2.1.0 wprowadził to_hmetodę Array, która robi to, czego potrzebujesz, jeśli twoja oryginalna tablica składa się z tablic par klucz-wartość: http://www.ruby-doc.org/core-2.1.0/Array.html#method -i-to_h .

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

1
Piękny! Znacznie lepiej niż niektóre inne rozwiązania tutaj.
Dennis

3
w wersjach wcześniejszych niż 2.1.0 ruby ​​można użyć metody Hash :: [], aby uzyskać podobne wyniki, o ile masz pary zagnieżdżonej tablicy. więc a = [[: foo,: 1], [bar, 2]] --- Hash [a] => {: foo => 1
,:

@AfDev, rzeczywiście, dzięki. Masz rację (ignorując pomniejsze literówki: barmusi to być symbol, a symbol :2powinien być liczbą całkowitą. Poprawione jest więc twoje wyrażenie a = [[:foo, 1], [:bar, 2]]).
Jochem Schulenklopper,

28

Po prostu użyj Hash.[]z wartościami w tablicy. Na przykład:

arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}

1
co oznacza [* arr]?
Alan Coromano

1
@Marius: *arrkonwertuje arrna listę argumentów, więc wywołuje to []metodę Hash z zawartością arr jako argumentów.
Chuck,

26

Lub jeśli masz tablicę [key, value]tablic, możesz:

[[1, 2], [3, 4]].inject({}) do |r, s|
  r.merge!({s[0] => s[1]})
end # => { 1 => 2, 3 => 4 }

2
Odpowiedź nie jest związana z pytaniem, a w twoim przypadku nadal jest o wiele łatwiej użyć tego samegoHash[*arr]
Yossi

2
Nie. Wróci { [1, 2] => [3, 4] }. A ponieważ tytuł pytania brzmi „Array to Hash”, a wbudowana metoda „Hash to Array”: { 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]]myślałem, że więcej niż jeden może skończyć tutaj, próbując uzyskać odwrotność wbudowanej metody „Hash to Array”. Właściwie tak właśnie tu się skończyłem.
Erik Escobedo,

1
Przepraszam, dodałem dodatkową gwiazdkę. Hash[arr]wykona za ciebie pracę.
Yossi,

9
Lepsze rozwiązanie IMHO: Hash [* array.flatten (1)]
gość

2
Yossi: Przepraszam, że wskrzeszałem umarłych, ale jest jeden większy problem z jego odpowiedzią, a mianowicie użycie #injectmetody. Z #merge!, #each_with_objectpowinien był zostać użyty. Jeśli #injectjest nalegany, #mergezamiast #merge!powinien był być używany.
Boris Stitnicky

12

Właśnie tego szukałem, przeglądając to:

[{a: 1}, {b: 2}].reduce({}) { |h, v| h.merge v } => {:a=>1, :b=>2}


Nie chcesz używać merge, konstruuje i odrzuca nowy skrót dla iteracji w pętli i jest bardzo wolny. Jeśli masz tablicę skrótów, spróbuj [{a:1},{b:2}].reduce({}, :merge!)zamiast tego - scala wszystko w ten sam (nowy) skrót.

Dzięki, tego też chciałem! :)
Thanasis Petsas,

Możesz także zrobić.reduce(&:merge!)
Ben Lee

1
[{a: 1}, {b: 2}].reduce(&:merge!)ocenia na{:a=>1, :b=>2}
Ben Lee,

Działa to, ponieważ wstrzykiwanie / zmniejszanie ma funkcję, w której można pominąć argument, w którym to przypadku używa pierwszego argumentu tablicy do działania jako argumentu wejściowego, a resztę tablicy jako tablicy. Połącz to z symbol-to-proc, a otrzymasz zwięzłą konstrukcję. Innymi słowy [{a: 1}, {b: 2}].reduce(&:merge!)jest taki sam jak ten, [{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) }który jest taki sam jak [{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) }.
Ben Lee

10

Enumeratorobejmuje Enumerable. Ponieważ 2.1, Enumerableposiada również metodę #to_h. Dlatego możemy napisać:

a = ["item 1", "item 2", "item 3", "item 4"]
a.each_slice(2).to_h
# => {"item 1"=>"item 2", "item 3"=>"item 4"}

Ponieważ #each_slicebez bloku daje nam Enumeratori zgodnie z powyższym wyjaśnieniem możemy wywołać #to_hmetodę na Enumeratorobiekcie.


7

Możesz spróbować w ten sposób dla pojedynczej tablicy

irb(main):019:0> a = ["item 1", "item 2", "item 3", "item 4"]
  => ["item 1", "item 2", "item 3", "item 4"]
irb(main):020:0> Hash[*a]
  => {"item 1"=>"item 2", "item 3"=>"item 4"}

dla tablicy tablic

irb(main):022:0> a = [[1, 2], [3, 4]]
  => [[1, 2], [3, 4]]
irb(main):023:0> Hash[*a.flatten]
  => {1=>2, 3=>4}

6
a = ["item 1", "item 2", "item 3", "item 4"]
Hash[ a.each_slice( 2 ).map { |e| e } ]

lub jeśli nienawidzisz Hash[ ... ]:

a.each_slice( 2 ).each_with_object Hash.new do |(k, v), h| h[k] = v end

lub, jeśli jesteś leniwym fanem zepsutego programowania funkcjonalnego:

h = a.lazy.each_slice( 2 ).tap { |a|
  break Hash.new { |h, k| h[k] = a.find { |e, _| e == k }[1] }
}
#=> {}
h["item 1"] #=> "item 2"
h["item 3"] #=> "item 4"

Jeśli nie do końca nienawidzisz, Hash[ ... ]ale chcesz użyć go jako metody powiązanej (tak jak możesz to zrobić to_h), możesz połączyć sugestie Borisa i napisać:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }
b-studio

Aby uczynić semantykę powyższego kodu jaśniejszym: Spowoduje to utworzenie „leniwego skrótu” h , który początkowo jest pusty , i wyciągnie elementy z oryginalnej tablicy a, gdy zajdzie taka potrzeba. Tylko wtedy będą faktycznie przechowywane w h!
Daniel Werner,

1

Wszystkie odpowiedzi zakładają, że tablica początkowa jest unikalna. OP nie określił, jak obsługiwać tablice ze zduplikowanymi wpisami, które skutkują powieleniem kluczy.

Spójrzmy na:

a = ["item 1", "item 2", "item 3", "item 4", "item 1", "item 5"]

Stracisz item 1 => item 2parę, ponieważ jest ona zastąpiona bij item 1 => item 5:

Hash[*a]
=> {"item 1"=>"item 5", "item 3"=>"item 4"}

Wszystkie metody, w tym reduce(&:merge!)skutkują tym samym usunięciem.

Możliwe jednak, że tego właśnie oczekujesz. Ale w innych przypadkach prawdopodobnie Arrayzamiast tego chcesz uzyskać wynik o wartości:

{"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

Naiwnym sposobem byłoby utworzenie zmiennej pomocniczej, skrótu o wartości domyślnej, a następnie wypełnienie jej w pętli:

result = Hash.new {|hash, k| hash[k] = [] } # Hash.new with block defines unique defaults.
a.each_slice(2) {|k,v| result[k] << v }
a
=> {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

Może być możliwe użycie associ reducewykonanie powyżej w jednym wierszu, ale staje się to znacznie trudniejsze do przemyślenia i przeczytania.

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.