Ruby'de, aşağıdaki biçimlerden birinde bir dizi verildiğinde ...
[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]
... bunu ... şeklinde bir hash'e dönüştürmenin en iyi yolu nedir?
{apple => 1, banana => 2}
Ruby'de, aşağıdaki biçimlerden birinde bir dizi verildiğinde ...
[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]
... bunu ... şeklinde bir hash'e dönüştürmenin en iyi yolu nedir?
{apple => 1, banana => 2}
Yanıtlar:
NOT : Kısa ve etkili bir çözüm için lütfen aşağıdaki Marc-André Lafortune'un cevabına bakın.
Bu cevap başlangıçta, yazı yazarken en çok oylanan düzleştirmeyi kullanan yaklaşımlara bir alternatif olarak sunuldu. Bu örneği bir en iyi uygulama veya verimli bir yaklaşım olarak sunma niyetinde olmadığımı açıklığa kavuşturmalıydım. Orijinal cevap takip ediyor.
Uyarı! Çözümler kullanarak , flatten Dizi anahtarları veya değerleri korumak olmaz!
@John Topley'in popüler cevabına dayanarak deneyelim:
a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]
Bu bir hata verir:
ArgumentError: odd number of arguments for Hash
from (irb):10:in `[]'
from (irb):10
Yapıcı, eşit uzunlukta bir Dizi bekliyordu (örneğin ['k1', 'v1,' k2 ',' v2 ']). Daha da kötüsü, eşit bir uzunluğa düzleştirilmiş farklı bir Dizi'nin bize sessizce yanlış değerlere sahip bir Hash vermesidir.
Dizi anahtarlarını veya değerlerini kullanmak istiyorsanız, eşlemeyi kullanabilirsiniz :
h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"
Bu, Dizi anahtarını korur:
h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
h3 = Hash[*a3.flatten(1)]bunun yerine h3 = Hash[*a3.flatten]bir hata atar.
to_hdaha iyi olduğunu düşünüyorum .
Basitçe kullan Hash[*array_variable.flatten]
Örneğin:
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}"
Kullanmak Array#flatten(1)özyinelemeyi sınırlar, böylece Arrayanahtarlar ve değerler beklendiği gibi çalışır.
Hash[*ary.flatten(1)]dizi anahtarlarını ve değerlerini koruyacak olanı da kullanabilirsiniz . Onları flattenyok eden, kaçınılması kolay olan özyinelemeli .
En iyi yol Array#to_hşunları kullanmaktır :
[ [:apple,1],[:banana,2] ].to_h #=> {apple: 1, banana: 2}
to_hAyrıca bir bloğu kabul ettiğini unutmayın :
[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] }
# => {apple: "I like apples", banana: "I like bananas"}
Not : to_hRuby 2.6.0+'da bir blok kabul eder; erken yakutlar için backportsmücevheri kullanabilirsin verequire 'backports/2.6.0/enumerable/to_h'
to_h bloksuz Ruby 2.1.0'da tanıtıldı.
Ruby 2.1'den önce, daha az okunaklı olanı kullanılabilir Hash[]:
array = [ [:apple,1],[:banana,2] ]
Hash[ array ] #= > {:apple => 1, :banana => 2}
Son olarak, herhangi bir çözüme karşı dikkatli olun flatten, bu, dizilerin kendileri olan değerlerle sorunlar yaratabilir.
to_hYöntemi yukarıdaki cevaplardan daha çok seviyorum çünkü dizi üzerinde çalıştıktan sonra dönüştürme niyetini ifade ediyor .
Array#to_hne ne de Enumerable#to_hRuby 1.9 çekirdeğinde.
[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]ve çıktının olmasını istersem {"apple" =>[1,3], "banana"=>[2,4]}?
Güncelleme
Ruby 2.1.0 bugün yayınlandı . Ve birlikte gelir Array#to_h( sürüm notları ve yakut-doc ) bir dönüştürme konusunu çözer, Arraybir karşı Hash.
Ruby dokümanları örneği:
[[:foo, :bar], [1, 2]].to_h # => {:foo => :bar, 1 => 2}
Düzenleme: Ben yazarken gönderilen yanıtları gördüm, Hash [a.flatten] gitmenin yolu gibi görünüyor. Cevabı düşünürken dokümantasyondaki o kısmı kaçırmış olmalıyım. Yazdığım çözümlerin gerekirse alternatif olarak kullanılabileceğini düşündüm.
İkinci biçim daha basittir:
a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }
a = dizi, h = hash, r = dönüş değeri hash (biriktirdiğimiz), i = dizideki öğe
İlk formu yapmanın en düzgün yolu şuna benzer bir şey:
a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }
a.inject({})Daha esnek değer atamalarına izin veren tek satırlık için +1 .
h = {}İkinci örnekten enjekte kullanarak, son olaraka.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
a.each_slice(2).to_h
Bu yanıt, diğer yanıtlardan gelen bilgilerin kapsamlı bir özeti olmayı umuyor.
Sorudaki veriler ve birkaç ekstra göz önüne alındığında çok kısa versiyon:
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
Tartışma ve ayrıntılar devam eder.
Önceden kullanacağımız verileri göstermek için, veriler için çeşitli olasılıkları temsil eden bazı değişkenler oluşturacağım. Aşağıdaki kategorilere uyarlar:
a1ve a2:(Not: Bunu appleve bananadeğişkenleri temsil etmesi gerektiğini varsayıyorum . Başkalarının yaptığı gibi, buradan itibaren dizeleri kullanacağım, böylece girdi ve sonuçlar eşleşebilir.)
a1 = [ 'apple', 1 , 'banana', 2 ] # flat input
a2 = [ ['apple', 1], ['banana', 2] ] # key/value paired input
a3:Diğer bazı yanıtlarda, başka bir olasılık sunuldu (burada genişleteceğim) - anahtarlar ve / veya değerler kendi başlarına diziler olabilir:
a3 = [ [ 'apple', 1 ],
[ 'banana', 2 ],
[ ['orange','seedless'], 3 ],
[ 'pear', [4, 5] ],
]
a4:İyi bir önlem için, eksik bir girdiye sahip olabileceğimiz bir durum için bir tane ekleyeceğimi düşündüm:
a4 = [ [ 'apple', 1],
[ 'banana', 2],
[ ['orange','seedless'], 3],
[ 'durian' ], # a spiky fruit pricks us: no value!
]
a1:Bazıları #to_h(Ruby 2.1.0'da ortaya çıktı ve önceki sürümlere geri aktarılabilen ) kullanmayı önerdi . Başlangıçta düz bir dizi için bu işe yaramaz:
a1.to_h # => TypeError: wrong element type String at 0 (expected array)
Kullanma Hash::[]ile kombine uyarısı operatörü yapar:
Hash[*a1] # => {"apple"=>1, "banana"=>2}
Dolayısıyla temsil edilen basit durumun çözümü budur a1.
a2:Bir dizi [key,value]türü dizide, gitmenin iki yolu vardır.
İlk olarak, Hash::[]hala çalışıyor (olduğu gibi *a1):
Hash[a2] # => {"apple"=>1, "banana"=>2}
Ve #to_hşimdi de çalışıyor:
a2.to_h # => {"apple"=>1, "banana"=>2}
Yani, basit iç içe dizi durumu için iki kolay cevap.
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]}
Dengeli olmayan giriş verilerini aldıysak, aşağıdaki sorunlarla karşılaşırız #to_h:
a4.to_h # => ArgumentError: wrong array length at 3 (expected 2, was 1)
Ama Hash::[]yine de işe yarıyor, sadece niliçin değer olarak ayarlanıyor durian(ve a4'teki diğer herhangi bir dizi öğesi sadece 1 değerli bir dizi):
Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
a5vea6Bir argümanla flattenveya 1argümansız olarak bahsedilen birkaç cevap daha , hadi yeni değişkenler oluşturalım:
a5 = a4.flatten
# => ["apple", 1, "banana", 2, "orange", "seedless" , 3, "durian"]
a6 = a4.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian"]
Karşılaştığımız a4denge sorunu nedeniyle temel veri olarak kullanmayı seçtim a4.to_h. Aramanın flatten, birisinin bunu çözmeye çalışmak için kullanabileceği bir yaklaşım olabileceğini düşünüyorum, bu da aşağıdaki gibi görünebilir.
flattenbağımsız değişkenler olmadan ( a5):Hash[*a5] # => {"apple"=>1, "banana"=>2, "orange"=>"seedless", 3=>"durian"}
# (This is the same as calling `Hash[*a4.flatten]`.)
Naif bir bakışta, bu işe yarıyor gibi görünüyor - ama çekirdeksiz portakallar konusunda bizi yanlış bir adım attı, böylece aynı zamanda 3bir anahtar ve durianbir değer oluşturdu .
Ve bu, olduğu gibi a1, sadece çalışmıyor:
a5.to_h # => TypeError: wrong element type String at 0 (expected array)
Yani a4.flattenbizim için yararlı değil, sadece kullanmak istiyoruzHash[a4]
flatten(1)Örnek ( a6):Peki ya sadece kısmen düzleşmeye ne dersiniz? Kısmen düzleştirilmiş dizi ( ) üzerinde Hash::[]kullanarak çağırmanın, çağırmakla aynı şey olmadığını belirtmek gerekir :splata6Hash[a4]
Hash[*a6] # => ArgumentError: odd number of arguments for Hash
a6):Peki ya diziyi ilk etapta bu şekilde almış olsaydık? (Yani, a1bizim girdi verilerimizdi - sadece bu sefer bazı veriler, diziler veya diğer nesneler olabilir.) Bunun Hash[*a6]işe yaramadığını gördük , ama ya yine de şu davranışı elde etmek istiyorsak son öğe (önemli! aşağıya bakın) bir nildeğer için anahtar görevi görüyor mu?
Böyle bir durumda, dış dizideki öğeler olarak Enumerable#each_slicekendimizi anahtar / değer çiftlerine geri döndürmeyi kullanarak bunu yapmanın hala bir yolu var :
a7 = a6.each_slice(2).to_a
# => [["apple", 1], ["banana", 2], [["orange", "seedless"], 3], ["durian"]]
Bunun bize " özdeş " olmayan a4, ancak aynı değerlere sahip yeni bir dizi getireceğini unutmayın :
a4.equal?(a7) # => false
a4 == a7 # => true
Ve böylece tekrar kullanabiliriz Hash::[]:
Hash[a7] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
# or Hash[a6.each_slice(2).to_a]
Unutulmamalıdır ki, each_slice(2)çözüm, yalnızca son anahtar bir değeri eksik olan anahtar ise, işleri akıl sağlığına kavuşturur . Daha sonra fazladan bir anahtar / değer çifti eklersek:
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
Ve bundan elde edeceğimiz iki hash önemli yönlerden farklıdır:
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
}
(Not: Yapıyı burada göstermeyi kolaylaştırmak için awesome_print's kullanıyorum ap; bunun için kavramsal bir gereklilik yok.)
Dolayısıyla, each_slicedengesiz bir düz girişin çözümü yalnızca dengesiz bit en sondaysa işe yarar.
[key, value]çiftler olarak ayarlayın (dış dizideki her öğe için bir alt dizi).#to_hya da Hash::[]her iki çalışma olacak.Hash::[]uyarıyla kombine ( *), çalışacak kadar uzun girişler dengeli olarak .value madde eksik sadece bir tanesidir.Yan not: Bu yanıtı, katılması gereken bir değer olduğunu düşündüğüm için gönderiyorum - mevcut yanıtların bazıları yanlış bilgilere sahip ve hiçbiri (okuduğum) burada yapmaya çalıştığım kadar eksiksiz bir yanıt vermedi. Umarım yardımcı olur. Yine de benden önce gelenlere teşekkür ediyorum ve birçoğu bu cevabın bazı kısımlarına ilham kaynağı oldu.
Yanıta ekleyerek ancak anonim diziler kullanarak ve açıklama ekleyerek:
Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]
Bu cevabı içeriden başlayarak ayırmak:
"a,b,c,d" aslında bir dizedir.split virgülle bir dizi haline getirin.zip aşağıdaki dizi ile birlikte.[1,2,3,4] gerçek bir dizidir.Ara sonuç:
[[a,1],[b,2],[c,3],[d,4]]
flatten sonra bunu şuna dönüştürür:
["a",1,"b",2,"c",3,"d",4]
ve sonra:
*["a",1,"b",2,"c",3,"d",4] onu içine açar
"a",1,"b",2,"c",3,"d",4
Hash[]yöntemin argümanları olarak kullanabileceğimiz :
Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]
hangi sonuç:
{"a"=>1, "b"=>2, "c"=>3, "d"=>4}
*) olmadan da çalışır ve düzleştir: Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}. Eklediğim bir cevapta daha fazla ayrıntı.
buna benzeyen bir diziniz varsa -
data = [["foo",1,2,3,4],["bar",1,2],["foobar",1,"*",3,5,:foo]]
ve her dizinin ilk elemanlarının hash için anahtarlar olmasını ve diğer elemanların değer dizileri olmasını istiyorsanız, o zaman şöyle bir şey yapabilirsiniz -
data_hash = Hash[data.map { |key| [key.shift, key] }]
#=>{"foo"=>[1, 2, 3, 4], "bar"=>[1, 2], "foobar"=>[1, "*", 3, 5, :foo]}
Bunun en iyi yol olup olmadığından emin değilim, ama bu işe yarar:
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
Sayısal değerler sıralı dizinler ise, daha basit yollarımız olabilir ... İşte benim kod gönderimim, My Ruby biraz paslanmış
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}