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}
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}
Jawaban:
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}
h3 = Hash[*a3.flatten(1)]
bukannya h3 = Hash[*a3.flatten]
akan melempar kesalahan.
to_h
lebih baik.
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 Array
kunci dan nilai berfungsi seperti yang diharapkan.
Hash[*ary.flatten(1)]
, yang akan mempertahankan kunci dan nilai array. Itu rekursif flatten
yang menghancurkan mereka, yang cukup mudah untuk dihindari.
Cara terbaik adalah dengan menggunakan Array#to_h
:
[ [:apple,1],[:banana,2] ].to_h #=> {apple: 1, banana: 2}
Perhatikan bahwa to_h
juga menerima blok:
[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] }
# => {apple: "I like apples", banana: "I like bananas"}
Catatan : to_h
menerima blok di Ruby 2.6.0+; untuk batu rubi awal Anda dapat menggunakan backports
permata 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.
to_h
metode ini lebih baik daripada jawaban di atas karena metode ini mengungkapkan maksud dari konversi setelah beroperasi pada array.
Array#to_h
maupun Enumerable#to_h
dalam inti ruby 1,9.
[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]
dan saya ingin outputnya sebagai {"apple" =>[1,3], "banana"=>[2,4]}
?
Memperbarui
Ruby 2.1.0 dirilis hari ini . Dan saya datang dengan Array#to_h
( catatan rilis dan ruby-doc ), yang memecahkan masalah mengonversi Array
file menjadi Hash
.
Contoh dokumen Ruby:
[[:foo, :bar], [1, 2]].to_h # => {:foo => :bar, 1 => 2}
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 }
a.inject({})
satu baris yang memungkinkan penugasan nilai yang lebih fleksibel.
h = {}
contoh kedua melalui penggunaan inject, berakhir dengana.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
a.each_slice(2).to_h
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.
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:
a1
dan a2
:(Catatan: Saya menganggap itu apple
dan banana
dimaksudkan 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
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] ],
]
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!
]
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
.
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_h
berfungsi sekarang:
a2.to_h # => {"apple"=>1, "banana"=>2}
Jadi, dua jawaban mudah untuk kasus array bersarang sederhana.
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]}
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 nil
sebagai 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}
a5
dana6
Beberapa jawaban lain disebutkan flatten
, dengan atau tanpa 1
argumen, 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 a4
sebagai data dasar karena masalah keseimbangan yang kami miliki, yang muncul dengan a4.to_h
. Saya pikir menelepon flatten
mungkin salah satu pendekatan yang mungkin digunakan seseorang untuk mencoba menyelesaikannya, yang mungkin terlihat seperti berikut ini.
flatten
tanpa 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 3
sebuah kunci dan durian
sebuah nilai .
Dan ini, seperti a1
, tidak berhasil:
a5.to_h # => TypeError: wrong element type String at 0 (expected array)
Jadi a4.flatten
tidak berguna bagi kami, kami hanya ingin menggunakanHash[a4]
flatten(1)
kasus ( a6
):Tapi bagaimana dengan hanya merata sebagian? Perlu dicatat bahwa memanggil Hash::[]
menggunakan splat
pada array yang diratakan sebagian ( a6
) tidak sama dengan memanggil Hash[a4]
:
Hash[*a6] # => ArgumentError: odd number of arguments for Hash
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 nil
nilai?
Dalam situasi seperti ini, masih ada cara untuk melakukan ini, menggunakan Enumerable#each_slice
untuk 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]
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_slice
solusi untuk input datar tidak seimbang hanya berfungsi jika bit tidak seimbang berada di bagian paling akhir.
[key, value]
pasangan (sub-larik untuk setiap item di larik terluar).#to_h
atau Hash::[]
keduanya akan berhasil.Hash::[]
kombinasi dengan splat ( *
) akan berfungsi, selama input seimbang .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.
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}
*
) 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.
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]}
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
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}