Apa cara terbaik untuk mengonversi array menjadi hash di Ruby

123

Di Ruby, diberikan array dalam salah satu bentuk berikut ...

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

... apa cara terbaik untuk mengubahnya menjadi hash dalam bentuk ...

{apple => 1, banana => 2}
Nathan Fritz
sumber

Jawaban:

91

CATATAN : Untuk solusi yang ringkas dan efisien, silakan lihat jawaban Marc-André Lafortune di bawah ini.

Jawaban ini awalnya ditawarkan sebagai alternatif pendekatan yang menggunakan flatten, yang paling banyak dipilih pada saat penulisan. Saya harus mengklarifikasi bahwa saya tidak bermaksud menyajikan contoh ini sebagai praktik terbaik atau pendekatan yang efisien. Berikut jawaban asli.


Peringatan! Solusi yang menggunakan flatten tidak akan mempertahankan kunci atau nilai Array!

Berdasarkan jawaban populer @John Topley, mari kita coba:

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

Ini melempar kesalahan:

ArgumentError: odd number of arguments for Hash
        from (irb):10:in `[]'
        from (irb):10

Konstruktor mengharapkan Array dengan panjang genap (misalnya ['k1', 'v1,' k2 ',' v2 ']). Yang lebih buruk adalah bahwa Array berbeda yang diratakan dengan panjang yang rata hanya akan memberi kita Hash dengan nilai yang salah.

Jika Anda ingin menggunakan kunci atau nilai Array, Anda dapat menggunakan map :

h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"

Ini mempertahankan kunci Array:

h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
rebus
sumber
15
Ini sama dengan Hash [a3], karena a3 == a3.map {| k, v | [k, v]} benar, sebenarnya sama dengan a3.dup.
Cluster
2
Daripada menggunakan map, mengapa tidak menentukan kedalaman rata saja? Misalnya: h3 = Hash[*a3.flatten(1)]bukannya h3 = Hash[*a3.flatten]akan melempar kesalahan.
Jeff McCune
3
Jawaban ini tidak efisien. Itu juga sudah ketinggalan zaman. Lihat jawaban saya.
Marc-André Lafortune
1
Ya, saya pikir Marc-André to_hlebih baik.
B Tujuh
1
@ Marc-André Lafortune terima kasih, saya telah memperbarui jawaban saya untuk mengarahkan pengguna ke jawaban Anda.
Rebus
145

Cukup gunakan Hash[*array_variable.flatten]

Sebagai contoh:

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

Penggunaan Array#flatten(1)membatasi rekursi sehingga Arraykunci dan nilai berfungsi seperti yang diharapkan.

John Topley
sumber
4
Oh, kefasihan! Inilah mengapa saya menyukai Ruby
iGbanam
11
PERINGATAN: jawaban menggunakan flatten akan menyebabkan masalah jika Anda menginginkan kunci atau nilai Array.
Rebus
Saya telah memposting solusi alternatif di bawah ini yang akan menghindari masalah dengan kunci atau nilai Array.
Rebus
5
Lebih baik tidak mencoba dan melakukan solusi umum untuk ini. Jika kunci dan nilai Anda dipasangkan seperti pada [[key1, value1], [key2, value2]] maka teruskan saja ke Hash [] tanpa menggemukkan. Hash [a2] == Hash [* a2.flatten]. Jika array sudah diratakan seperti pada, [key1, value1, key2, value2] maka awali var dengan *, Hash [* a1]
Cluster
8
FWIW, jika Anda benar-benar menginginkan (lebih dari satu) versi satu ukuran untuk semua, Anda juga dapat menggunakan Hash[*ary.flatten(1)], yang akan mempertahankan kunci dan nilai array. Itu rekursif flattenyang menghancurkan mereka, yang cukup mudah untuk dihindari.
brymck
81

Cara terbaik adalah dengan menggunakan Array#to_h:

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

Perhatikan bahwa to_hjuga menerima blok:

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

Catatan : to_hmenerima blok di Ruby 2.6.0+; untuk batu rubi awal Anda dapat menggunakan backportspermata saya danrequire 'backports/2.6.0/enumerable/to_h'

to_h tanpa blok diperkenalkan di Ruby 2.1.0.

Sebelum Ruby 2.1, seseorang dapat menggunakan yang kurang terbaca Hash[]:

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

Akhirnya, berhati-hatilah dengan solusi apa pun yang digunakan flatten, ini dapat menimbulkan masalah dengan nilai yang merupakan array itu sendiri.

Marc-André Lafortune
sumber
4
Terima kasih atas kesederhanaan metode .to_h yang baru!
kecanduan coding
3
Saya menyukai to_hmetode ini lebih baik daripada jawaban di atas karena metode ini mengungkapkan maksud dari konversi setelah beroperasi pada array.
B Tujuh
1
@BSeven Baik Array#to_hmaupun Enumerable#to_hdalam inti ruby 1,9.
Iron Savior
Bagaimana jika saya memiliki array sebagai [[apple, 1], [banana, 2], [apple, 3], [banana, 4]]dan saya ingin outputnya sebagai {"apple" =>[1,3], "banana"=>[2,4]}?
nishant
@NishantKumar itu pertanyaan yang berbeda.
Marc-André Lafortune
20

Memperbarui

Ruby 2.1.0 dirilis hari ini . Dan saya datang dengan Array#to_h( catatan rilis dan ruby-doc ), yang memecahkan masalah mengonversi Arrayfile menjadi Hash.

Contoh dokumen Ruby:

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

Sunting: Melihat tanggapan yang diposting saat saya menulis, Hash [a.flatten] tampaknya cara yang harus dilakukan. Pasti melewatkan sedikit itu dalam dokumentasi ketika saya memikirkan tanggapannya. Berpikir solusi yang saya tulis dapat digunakan sebagai alternatif jika diperlukan.

Bentuk kedua lebih sederhana:

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

a = array, h = hash, r = return-value hash (yang kita kumpulkan), i = item dalam array

Cara paling rapi yang bisa saya pikirkan untuk melakukan bentuk pertama adalah seperti ini:

a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }
Daemin
sumber
2
+1 untuk a.inject({})satu baris yang memungkinkan penugasan nilai yang lebih fleksibel.
Chris Bloom
Ini juga mungkin untuk menghilangkan h = {}contoh kedua melalui penggunaan inject, berakhir dengana.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
lindes
Anda bisa melakukannyaa.each_slice(2).to_h
Conor O'Brien
6

Anda juga dapat dengan mudah mengonversi larik 2D menjadi hash menggunakan:

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

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

1.9.3p362 :006 > h = Hash[a]

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

Ringkasan & TL; DR:

Jawaban ini diharapkan menjadi rangkuman informasi yang komprehensif dari jawaban lain.

Versi yang sangat singkat, mengingat data dari pertanyaan ditambah beberapa tambahan:

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

Diskusi dan detail ikuti.


Penyiapan: variabel

Untuk menampilkan data yang akan kita gunakan di depan, saya akan membuat beberapa variabel untuk mewakili berbagai kemungkinan data. Mereka masuk ke dalam kategori berikut:

Berdasarkan apa yang langsung di pertanyakan, sebagai a1dan a2:

(Catatan: Saya menganggap itu appledan bananadimaksudkan untuk mewakili variabel. Seperti yang telah dilakukan orang lain, saya akan menggunakan string mulai dari sini sehingga masukan dan hasil bisa cocok.)

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

Kunci dan / atau nilai multi-nilai, seperti a3:

Dalam beberapa jawaban lain, kemungkinan lain disajikan (yang saya kembangkan di sini) - kunci dan / atau nilai dapat berupa array sendiri:

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

Array tidak seimbang, seperti a4:

Untuk ukuran yang baik, saya pikir saya akan menambahkan satu untuk kasus di mana kami mungkin memiliki masukan yang tidak lengkap:

a4 = [ [ 'apple',                   1],
       [ 'banana',                  2],
       [ ['orange','seedless'],     3],
       [ 'durian'                    ], # a spiky fruit pricks us: no value!
     ]

Sekarang, untuk bekerja:

Dimulai dengan sebuah array awalnya-datar, a1:

Beberapa orang menyarankan penggunaan #to_h(yang muncul di Ruby 2.1.0, dan dapat di- backport ke versi sebelumnya). Untuk array yang awalnya datar, ini tidak berfungsi:

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

Menggunakan Hash::[]kombinasi dengan operator percikan tidak:

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

Jadi itulah solusi untuk kasus sederhana yang direpresentasikan oleh a1.

Dengan berbagai kunci array nilai pasangan /, a2:

Dengan array [key,value]tipe array, ada dua cara untuk melakukannya.

Pertama, Hash::[]masih berfungsi (seperti halnya dengan *a1):

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

Dan kemudian juga #to_hberfungsi sekarang:

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

Jadi, dua jawaban mudah untuk kasus array bersarang sederhana.

Ini tetap benar bahkan dengan sub-array sebagai kunci atau nilai, seperti 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]}

Tetapi durian memiliki paku (struktur yang tidak normal memberikan masalah):

Jika kami mendapatkan data masukan yang tidak seimbang, kami akan mengalami masalah dengan #to_h:

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

Tetapi Hash::[]masih berfungsi, hanya menetapkan nilsebagai nilai untuk durian(dan elemen array lainnya di a4 yang hanya array 1-nilai):

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

Perataan - menggunakan variabel baru a5dana6

Beberapa jawaban lain disebutkan flatten, dengan atau tanpa 1argumen, jadi mari buat beberapa variabel baru:

a5 = a4.flatten
# => ["apple", 1, "banana", 2,  "orange", "seedless" , 3, "durian"] 
a6 = a4.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian"] 

Saya memilih untuk menggunakan a4sebagai data dasar karena masalah keseimbangan yang kami miliki, yang muncul dengan a4.to_h. Saya pikir menelepon flattenmungkin salah satu pendekatan yang mungkin digunakan seseorang untuk mencoba menyelesaikannya, yang mungkin terlihat seperti berikut ini.

flattentanpa argumen ( a5):

Hash[*a5]       # => {"apple"=>1, "banana"=>2, "orange"=>"seedless", 3=>"durian"}
# (This is the same as calling `Hash[*a4.flatten]`.)

Sekilas naif, ini muncul untuk bekerja - tetapi turun kami di kaki salah dengan jeruk tanpa biji, dengan demikian juga membuat 3sebuah kunci dan duriansebuah nilai .

Dan ini, seperti a1, tidak berhasil:

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

Jadi a4.flattentidak berguna bagi kami, kami hanya ingin menggunakanHash[a4]

The flatten(1)kasus ( a6):

Tapi bagaimana dengan hanya merata sebagian? Perlu dicatat bahwa memanggil Hash::[]menggunakan splatpada array yang diratakan sebagian ( a6) tidak sama dengan memanggil Hash[a4]:

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

Array yang sudah diratakan, masih bersarang (cara alternatif untuk mendapatkannya a6):

Tapi bagaimana jika ini adalah cara pertama kita mendapatkan array? (Artinya, sebanding dengan a1, itu adalah data masukan kami - kali ini beberapa datanya dapat berupa larik atau objek lain.) Kami telah melihat itu Hash[*a6]tidak berfungsi, tetapi bagaimana jika kami masih ingin mendapatkan perilaku di mana elemen terakhir (penting! lihat di bawah) bertindak sebagai kunci untuk nilnilai?

Dalam situasi seperti ini, masih ada cara untuk melakukan ini, menggunakan Enumerable#each_sliceuntuk kembali ke pasangan kunci / nilai sebagai elemen dalam larik terluar:

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

Perhatikan bahwa ini akhirnya memberi kita array baru yang tidak " identik " a4, tetapi memiliki nilai yang sama :

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

Dan dengan demikian kita dapat kembali menggunakan Hash::[]:

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

Tapi ada masalah!

Penting untuk dicatat bahwa each_slice(2)solusinya hanya mengembalikan semuanya ke kewarasan jika kunci terakhir adalah yang kehilangan nilainya. Jika nanti kami menambahkan pasangan kunci / nilai ekstra:

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

Dan dua hash yang kami dapatkan dari sini berbeda dalam hal-hal penting:

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
}

(Catatan: Saya menggunakan awesome_print's ap. Hanya untuk membuatnya lebih mudah untuk menunjukkan struktur di sini, tidak ada persyaratan konseptual untuk ini)

Jadi each_slicesolusi untuk input datar tidak seimbang hanya berfungsi jika bit tidak seimbang berada di bagian paling akhir.


Take-aways:

  1. Jika memungkinkan, siapkan input untuk hal-hal ini sebagai [key, value]pasangan (sub-larik untuk setiap item di larik terluar).
  2. Ketika Anda memang bisa melakukan itu, salah satu #to_hatau Hash::[]keduanya akan berhasil.
  3. Jika Anda tidak dapat melakukannya, Hash::[]kombinasi dengan splat ( *) akan berfungsi, selama input seimbang .
  4. Dengan larik yang tidak seimbang dan datar sebagai input, satu-satunya cara ini akan bekerja secara masuk akal adalah jika item terakhir value adalah satu-satunya yang hilang.

Catatan tambahan: Saya memposting jawaban ini karena saya merasa ada nilai yang bisa ditambahkan - beberapa jawaban yang ada memiliki informasi yang salah, dan tidak ada (yang saya baca) memberikan jawaban selengkap yang saya coba lakukan di sini. Saya harap ini membantu. Meskipun demikian, saya berterima kasih kepada mereka yang datang sebelum saya, beberapa di antaranya memberikan inspirasi untuk sebagian jawaban ini.

Lindes
sumber
3

Menambahkan jawaban tetapi menggunakan array dan anotasi anonim:

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

Membongkar jawaban itu, mulai dari dalam:

  • "a,b,c,d" sebenarnya adalah sebuah string.
  • split dengan koma menjadi sebuah larik.
  • zip itu bersama dengan array berikut.
  • [1,2,3,4] adalah larik sebenarnya.

Hasil antara adalah:

[[a,1],[b,2],[c,3],[d,4]]

flatten kemudian mengubahnya menjadi:

["a",1,"b",2,"c",3,"d",4]

lalu:

*["a",1,"b",2,"c",3,"d",4] buka gulungannya "a",1,"b",2,"c",3,"d",4

yang bisa kita gunakan sebagai argumen untuk Hash[]metode ini:

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

yang menghasilkan:

{"a"=>1, "b"=>2, "c"=>3, "d"=>4}
StevenJenkins
sumber
Ini juga bekerja tanpa splat ( *) dan flatten: Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}. Lebih detail dalam jawaban yang saya tambahkan.
lindes
0

jika Anda memiliki larik yang terlihat seperti ini -

data = [["foo",1,2,3,4],["bar",1,2],["foobar",1,"*",3,5,:foo]]

dan Anda ingin elemen pertama dari setiap array menjadi kunci untuk hash dan elemen lainnya menjadi array nilai, lalu Anda dapat melakukan sesuatu seperti ini -

data_hash = Hash[data.map { |key| [key.shift, key] }]

#=>{"foo"=>[1, 2, 3, 4], "bar"=>[1, 2], "foobar"=>[1, "*", 3, 5, :foo]}
pengguna3588841
sumber
0

Tidak yakin apakah ini cara terbaik, tetapi ini berhasil:

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
Anders Sandvig
sumber
-1

Jika nilai numerik adalah indeks seq, maka kita bisa memiliki cara yang lebih sederhana ... Ini kiriman kode saya, Ruby saya agak berkarat

   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}
Gishu
sumber