Array ke Hash Ruby

192

Oke jadi ini masalahnya, saya sudah lama mencari di Google untuk menemukan solusi untuk ini dan meskipun ada banyak di luar sana, mereka sepertinya tidak melakukan pekerjaan yang saya cari.

Pada dasarnya saya memiliki array yang terstruktur seperti ini

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

Saya ingin mengonversi ini menjadi Hash sehingga terlihat seperti ini

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

yaitu item yang ada di indeks 'genap' adalah kunci dan item pada indeks 'aneh' adalah nilainya.

Ada ide bagaimana melakukan ini dengan bersih? Saya kira metode brute force adalah dengan hanya menarik semua indeks genap ke dalam array yang terpisah dan kemudian loop di sekitarnya untuk menambahkan nilai-nilai.

djhworld
sumber

Jawaban:

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

Itu dia. Ini *disebut operator percikan .

Satu peringatan per @ Mike Lewis (dalam komentar): "Berhati-hatilah dengan ini. Ruby memperluas percikan di stack. Jika Anda melakukan ini dengan dataset besar, berharap untuk mengeluarkan stack Anda."

Jadi, untuk sebagian besar kasus penggunaan umum metode ini hebat, tetapi gunakan metode yang berbeda jika Anda ingin melakukan konversi pada banyak data. Misalnya, @ Łukasz Niemier (juga dalam komentar) menawarkan metode ini untuk kumpulan data besar:

h = Hash[a.each_slice(2).to_a]
Ben Lee
sumber
10
@tester, *disebut operator percikan . Dibutuhkan array dan mengonversinya menjadi daftar item literal. Jadi *[1,2,3,4]=> 1, 2, 3, 4. Dalam contoh ini, hal di atas setara dengan melakukan Hash["item 1", "item 2", "item 3", "item 4"]. Dan Hashmemiliki []metode yang menerima daftar argumen (membuat bahkan indeks kunci dan nilai indeks ganjil), tetapi Hash[]tidak menerima array, jadi kami memercikkan array menggunakan *.
Ben Lee
15
Berhati-hatilah dengan ini. Ruby memperluas percikan di tumpukan. Jika Anda melakukan ini dengan set data yang besar, berharap untuk mengeluarkan tumpukan Anda.
Mike Lewis
9
Pada tabel data besar yang dapat Anda gunakan Hash[a.each_slice(2).to_a].
Hauleth
4
Apa artinya "meniup tumpukan Anda"?
Kevin
6
@Kevin, sang tumpukan menggunakan area kecil memori yang dialokasikan oleh program dan cadangan untuk operasi tertentu tertentu. Paling umum, ini digunakan untuk menyimpan setumpuk metode yang telah dipanggil sejauh ini. Ini adalah asal dari istilah stack stack , dan ini juga mengapa metode rekursif tanpa batas dapat menyebabkan stack overflow . Metode dalam jawaban ini juga menggunakan tumpukan, tetapi karena tumpukan hanya area kecil memori, jika Anda mencoba metode ini dengan array besar, itu akan mengisi tumpukan dan menyebabkan kesalahan (kesalahan di sepanjang baris yang sama dengan stack overflow).
Ben Lee
103

Ruby 2.1.0 memperkenalkan to_hmetode pada Array yang melakukan apa yang Anda butuhkan jika array asli Anda terdiri dari array pasangan nilai kunci: 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}
Jochem Schulenklopper
sumber
1
Cantik! Jauh lebih baik daripada beberapa solusi lain di sini.
Dennis
3
untuk versi pra 2.1.0 ruby, Anda dapat menggunakan metode Hash :: [] untuk mendapatkan hasil yang sama, selama Anda memiliki pasangan array bersarang. jadi a = [[: foo,: 1], [bar, 2]] --- Hash [a] => {: foo => 1
,:
@ AFDev, memang, terima kasih. Anda benar (ketika mengabaikan kesalahan ketik kecil: barharus simbol, dan simbol :2harus bilangan bulat. Jadi, ekspresi Anda dikoreksi adalah a = [[:foo, 1], [:bar, 2]]).
Jochem Schulenklopper
28

Cukup gunakan Hash.[]dengan nilai-nilai dalam array. Sebagai contoh:

arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}
Membuang
sumber
1
apa arti [* arr]?
Alan Coromano
1
@Marius: *arrmengkonversi arrke daftar argumen, jadi ini memanggil []metode Hash dengan isi arr sebagai argumen.
Chuck
26

Atau jika Anda memiliki array [key, value]array, Anda dapat melakukan:

[[1, 2], [3, 4]].inject({}) do |r, s|
  r.merge!({s[0] => s[1]})
end # => { 1 => 2, 3 => 4 }
Erik Escobedo
sumber
2
Jawaban Anda tidak terkait dengan pertanyaan dan dalam kasus Anda, masih jauh lebih mudah untuk menggunakan yang samaHash[*arr]
Yossi
2
Nggak. Itu akan kembali { [1, 2] => [3, 4] }. Dan karena judul pertanyaannya berbunyi "Array to Hash" dan metode "Hash to Array" bawaan:, { 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]]saya pikir lebih dari satu yang bisa berakhir di sini mencoba mendapatkan kebalikan dari metode "Hash to Array" bawaan. Sebenarnya, begitulah akhirnya saya berakhir di sini.
Erik Escobedo
1
Maaf, saya menambahkan tanda bintang cadangan. Hash[arr]akan melakukan pekerjaan untuk Anda.
Yossi
9
Solusi IMHO yang lebih baik: Hash [* array.flatten (1)]
tamu
2
Yossi: Maaf telah membangkitkan orang mati, tetapi ada satu masalah yang lebih besar dengan jawabannya, dan itu adalah penggunaan #injectmetode. Dengan #merge!, #each_with_objectseharusnya sudah digunakan. Jika #injectbersikeras, #mergedaripada #merge!seharusnya digunakan.
Boris Stitnicky
12

Ini yang saya cari saat mencari di Google:

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

Karl Glaser
sumber
Anda tidak ingin menggunakan merge, itu membangun dan membuang iterasi per loop baru dan sangat lambat. Jika Anda memiliki array hash, cobalah [{a:1},{b:2}].reduce({}, :merge!)- menggabungkan semuanya menjadi hash (baru) yang sama.
Terima kasih, ini yang saya inginkan juga! :)
Thanasis Petsas
Anda juga dapat melakukannya.reduce(&:merge!)
Ben Lee
1
[{a: 1}, {b: 2}].reduce(&:merge!)mengevaluasi ke{:a=>1, :b=>2}
Ben Lee
Ini berfungsi karena menyuntikkan / mengurangi memiliki fitur di mana Anda dapat menghilangkan argumen, dalam hal ini menggunakan argumen pertama array untuk beroperasi sebagai argumen input, dan sisanya dari array sebagai array. Gabungkan itu dengan simbol-ke-proc dan Anda mendapatkan konstruksi ringkas ini. Dengan kata lain [{a: 1}, {b: 2}].reduce(&:merge!)adalah sama dengan [{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) }yang sama dengan [{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) }.
Ben Lee
10

Enumeratortermasuk Enumerable. Karena 2.1, Enumerablejuga punya metode #to_h. Itu sebabnya, kita dapat menulis: -

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

Karena #each_slicetanpa blok memberi kita Enumerator, dan sesuai penjelasan di atas, kita dapat memanggil #to_hmetode pada Enumeratorobjek.

Arup Rakshit
sumber
7

Anda dapat mencoba seperti ini, untuk array tunggal

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

untuk array array

irb(main):022:0> a = [[1, 2], [3, 4]]
  => [[1, 2], [3, 4]]
irb(main):023:0> Hash[*a.flatten]
  => {1=>2, 3=>4}
Jenorish
sumber
6
a = ["item 1", "item 2", "item 3", "item 4"]
Hash[ a.each_slice( 2 ).map { |e| e } ]

atau, jika Anda membenci Hash[ ... ]:

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

atau, jika Anda adalah penggemar malas pemrograman fungsional yang rusak:

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"
Boris Stitnicky
sumber
Jika Anda tidak sepenuhnya membenci Hash[ ... ]tetapi ingin menggunakannya sebagai metode dirantai (seperti yang dapat Anda lakukan dengan to_h) Anda dapat menggabungkan saran Boris dan menulis:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }
b-studios
Untuk membuat semantik kode di atas lebih jelas: Ini akan membuat "lazy hash" h , yang awalnya kosong , dan akan mengeluarkan elemen dari array asli ketika diperlukan. Hanya kemudian mereka benar-benar akan disimpan dalam h!
Daniel Werner
1

Semua jawaban menganggap array awal adalah unik. OP tidak menentukan cara menangani array dengan entri duplikat, yang menghasilkan kunci duplikat.

Mari lihat:

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

Anda akan kehilangan item 1 => item 2pasangan karena ditimpa oleh item 1 => item 5:

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

Semua metode, termasuk reduce(&:merge!)hasil dalam penghapusan yang sama.

Bisa jadi ini persis seperti yang Anda harapkan. Namun dalam kasus lain, Anda mungkin ingin mendapatkan hasil dengan Arraynilai for gantinya:

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

Cara naif adalah membuat variabel pembantu, hash yang memiliki nilai default, dan kemudian mengisinya dalam satu lingkaran:

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

Mungkin bisa digunakan assocdan reducedilakukan di atas dalam satu baris, tetapi itu menjadi lebih sulit untuk dipikirkan dan dibaca.

berkes
sumber