Ruby'de bir diziyi karmaya dönüştürmenin en iyi yolu nedir


123

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:


91

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}

15
Bu Hash [a3] ile aynıdır, çünkü a3 == a3.map {| k, v | [k, v]} doğrudur, aslında a3.dup ile eşdeğerdir.
Küme

2
Harita kullanmak yerine neden düzleştirmenin derinliğini belirtmiyorsunuz? Örneğin: h3 = Hash[*a3.flatten(1)]bunun yerine h3 = Hash[*a3.flatten]bir hata atar.
Jeff McCune

3
Bu cevap verimli değil. Aynı zamanda güncel değil. Cevabımı gör.
Marc-André Lafortune

1
Evet, Marc-André'nin to_hdaha iyi olduğunu düşünüyorum .
B Seven

1
@ Marc-André Lafortune teşekkürler, kullanıcıları sizinkine yönlendirmek için cevabımı güncelledim.
Yah

145

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.


4
Oh, belagat! Ruby'yi bu yüzden seviyorum
iGbanam

11
UYARI: düzleştir kullanan yanıtlar, Dizi anahtarları veya değerleri istiyorsanız sorunlara neden olur.
Güveç

Aşağıda, Dizi anahtarları veya değerleriyle ilgili sorunları önleyecek alternatif bir çözüm yayınladım.
Güveç

5
Bunun için her şeyi kapsayan bir çözüm denememek daha iyidir. Anahtarlarınız ve değerleriniz [[anahtar1, değer1], [anahtar2, değer2]] 'de olduğu gibi eşleştirilmişse, o zaman kısaltmadan Hash []' e geçirin. Hash [a2] == Hash [* a2.flatten]. Dizi halihazırda [key1, value1, key2, value2] 'deki gibi düzleştirilmişse, o zaman varyantın önüne *, Hash [* a1]
Küme

8
FWIW, eğer gerçekten (daha fazla) tek boyutlu bir sürümü istiyorsanız, Hash[*ary.flatten(1)]dizi anahtarlarını ve değerlerini koruyacak olanı da kullanabilirsiniz . Onları flattenyok eden, kaçınılması kolay olan özyinelemeli .
brymck

81

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.


4
Yeni .to_h yönteminin basitliği için teşekkürler!
kodlama bağımlısı

3
to_hYöntemi yukarıdaki cevaplardan daha çok seviyorum çünkü dizi üzerinde çalıştıktan sonra dönüştürme niyetini ifade ediyor .
B Seven

1
@BSeven Ne Array#to_hne ne de Enumerable#to_hRuby 1.9 çekirdeğinde.
Iron Savior

Ya bir dizim varsa [[apple, 1], [banana, 2], [apple, 3], [banana, 4]]ve çıktının olmasını istersem {"apple" =>[1,3], "banana"=>[2,4]}?
nishant

@NishantKumar bu farklı bir soru.
Marc-André Lafortune


9

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 }

2
a.inject({})Daha esnek değer atamalarına izin veren tek satırlık için +1 .
Chris Bloom

h = {}İkinci örnekten enjekte kullanarak, son olaraka.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
44'te lindes

Yapabilirsina.each_slice(2).to_h
Conor O'Brien

6

Ayrıca aşağıdakileri kullanarak bir 2D diziyi karma haline dönüştürebilirsiniz:

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

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

1.9.3p362 :006 > h = Hash[a]

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

4

Özet ve TL; DR:

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.


Kurulum: değişkenler

Ö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:

Doğrudan soruda ne olduğuna bağlı olarak 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

Aşağıdaki gibi çok değerli anahtarlar ve / veya değerler 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] ],
     ]

Dengesiz dizi 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!
     ]

Şimdi çalışmak için:

Başlangıçta düz bir diziden başlayarak 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.

Bir dizi anahtar / değer çifti dizisi ile 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.

Bu, aşağıdaki gibi anahtarlar veya değerler olarak alt dizilerde bile geçerlidir 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]}

Ancak durianların sivri uçları vardır (anormal yapılar sorun yaratır):

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}

Düzleştirme - yeni değişkenler kullanarak a5vea6

Bir 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

Önceden düzleştirilmiş dizi, hala yuvalanmış (almanın alternatif yolu 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]

Ama bir sorun var!

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.


Hazır sonuçlar:

  1. Mümkün olduğunda, bu şeyler için girdileri [key, value]çiftler olarak ayarlayın (dış dizideki her öğe için bir alt dizi).
  2. Eğer gerçekten bunu, ya ne zaman #to_hya da Hash::[]her iki çalışma olacak.
  3. Eğer yapamıyorsanız, Hash::[]uyarıyla kombine ( *), çalışacak kadar uzun girişler dengeli olarak .
  4. Bir ile dengesiz ve düz eğer girdi olarak dizinin, hiç tek yolu bu irade işi makul olan son 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.


3

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}

Bu, splat ( *) 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ı.
lindes

0

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

0

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

-1

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}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.