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_h
daha 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 Array
anahtarlar ve değerler beklendiği gibi çalışır.
Hash[*ary.flatten(1)]
dizi anahtarlarını ve değerlerini koruyacak olanı da kullanabilirsiniz . Onları flatten
yok 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_h
Ayrı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_h
Ruby 2.6.0+'da bir blok kabul eder; erken yakutlar için backports
mü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_h
Yöntemi yukarıdaki cevaplardan daha çok seviyorum çünkü dizi üzerinde çalıştıktan sonra dönüştürme niyetini ifade ediyor .
Array#to_h
ne ne de Enumerable#to_h
Ruby 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, Array
bir 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:
a1
ve a2
:(Not: Bunu apple
ve banana
değ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 nil
iç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}
a5
vea6
Bir argümanla flatten
veya 1
argü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 a4
denge 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.
flatten
bağı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 3
bir anahtar ve durian
bir 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.flatten
bizim 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 :splat
a6
Hash[a4]
Hash[*a6] # => ArgumentError: odd number of arguments for Hash
a6
):Peki ya diziyi ilk etapta bu şekilde almış olsaydık? (Yani, a1
bizim 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 nil
değer için anahtar görevi görüyor mu?
Böyle bir durumda, dış dizideki öğeler olarak Enumerable#each_slice
kendimizi 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_slice
dengesiz 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_h
ya 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}