Hash Ruby dizisi


192

Tamam, işte anlaşma, uzun zamandır buna bir çözüm bulmak için çalışıyorum ve orada çok fazla olsa da, aradığım işi yapmıyorlar.

Temelde böyle bir dizi var

["item 1", "item 2", "item 3", "item 4"] 

Bunu bir Hash'e dönüştürmek istiyorum, böylece şöyle görünüyor

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

yani 'çift' dizinlerdeki öğeler anahtarlardır ve 'tek' dizinlerdeki öğeler değerlerdir.

Bunu nasıl temiz yapacağınız hakkında bir fikriniz var mı? Bir kaba kuvvet yöntemi sadece tüm çift dizinleri ayrı bir diziye çekin ve sonra değerleri eklemek için onların etrafında döngü olacağını varsayalım.

Yanıtlar:


358
a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }

Bu kadar. " Splat" operatörü *olarak adlandırılır .

@Mike Lewis başına bir uyarı (yorumlarda): "Buna çok dikkat edin. Ruby yığındaki uyarıları genişletir. Bunu büyük bir veri kümesiyle yaparsanız, yığınızı havaya uçurmayı bekleyin."

Bu nedenle, çoğu genel kullanım durumu için bu yöntem harikadır, ancak dönüşümü çok fazla veri üzerinde yapmak istiyorsanız farklı bir yöntem kullanın. Örneğin, @ asukasz Niemier (yorumlarda da) büyük veri setleri için bu yöntemi sunar:

h = Hash[a.each_slice(2).to_a]

10
@tester, *denir uyarısı operatörü. Bir dizi alır ve onu değişken bir liste haline getirir. Yani *[1,2,3,4]=> 1, 2, 3, 4. Bu örnekte, yukarıdakine eşdeğerdir Hash["item 1", "item 2", "item 3", "item 4"]. Ve Hashbir []argüman listesini kabul eden bir yöntem var (hatta dizinler anahtarları ve tek dizinler değerleri yapma), ancak Hash[]bir diziyi kabul etmiyor, bu yüzden diziyi kullanarak uyarıyoruz *.
Ben Lee

15
Buna çok dikkat edin. Ruby destedeki uyarıları genişletir. Bunu büyük bir veri kümesiyle yaparsanız, yığınızı havaya uçurmayı bekleyin.
Mike Lewis

9
Büyük veri tablolarında kullanabilirsiniz Hash[a.each_slice(2).to_a].
Hauleth

4
"Yığını havaya uçur" ne anlama geliyor?
Kevin

6
@Kevin, yığın , programın belirli belirli işlemler için ayırdığı ve ayırdığı küçük bir bellek alanı kullanır. En yaygın olarak, şimdiye kadar çağrılan yöntemlerin bir yığınını tutmak için kullanılır. Bu, yığın izinin kökenidir ve bu nedenle sonsuz bir şekilde yinelenen bir yöntem bir yığın taşmasına neden olabilir . Bu yanıttaki yöntem de yığını kullanır, ancak yığın yalnızca küçük bir bellek alanı olduğundan, bu yöntemi büyük bir dizi ile denerseniz, yığını doldurur ve bir hataya neden olur (aynı satırlar boyunca bir hata yığın taşması).
Ben Lee

103

Ruby 2.1.0 to_h, Array'da, orijinal diziniz anahtar / değer çiftlerinin dizilerinden oluşuyorsa ihtiyacınız olanı yapan bir yöntem sundu : http://www.ruby-doc.org/core-2.1.0/Array.html#method -i-to_h .

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

1
Güzel! Buradaki diğer çözümlerden çok daha iyi.
Dennis

3
2.1.0 öncesi ruby ​​versiyonları için, iç içe dizinin çiftleri olduğu sürece benzer sonuçlar elde etmek için Hash :: [] yöntemini kullanabilirsiniz. yani a = [[: foo,: 1], [bar, 2]] --- Hash [a] => {: foo => 1
,:

@ AfDev, gerçekten, teşekkürler. Haklısın (küçük yazım hatalarını göz ardı ederken: barbir sembol :2olmalı ve sembol bir tamsayı olmalıdır. Yani, ifaden düzeltildi a = [[:foo, 1], [:bar, 2]]).
Jochem Schulenklopper

28

Sadece Hash.[]dizideki değerlerle kullanın . Örneğin:

arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}

1
[* arr] ne anlama geliyor?
Alan Coromano

1
@Marius: bir argüman listesine *arrdönüşür arr, bu yüzden bu, arr'nin içeriğiyle arg []yöntemini argüman olarak çağırır .
Chuck

26

Veya bir dizi diziniz [key, value]varsa şunları yapabilirsiniz:

[[1, 2], [3, 4]].inject({}) do |r, s|
  r.merge!({s[0] => s[1]})
end # => { 1 => 2, 3 => 4 }

2
Cevabınız soru ile ilgili değildir ve sizin durumunuzda bunu kullanmak çok daha kolaydırHash[*arr]
Yossi

2
Hayır! Dönecekti { [1, 2] => [3, 4] }. Ve sorunun başlığı "Hash to Array" dediği ve yerleşik "Hash to Array" yönteminin yaptığı gibi { 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]], burada birden fazla "Hash to Array" yönteminin tersini almaya çalışırken birden fazlasının burada sona erebileceğini düşündüm. Aslında, ben zaten burada böyle bitirdim.
Erik Escobedo

1
Üzgünüm, yedek bir yıldız ekledim. Hash[arr]işi senin için yapacak.
Yossi

9
IMHO daha iyi çözüm: Hash [* array.flatten (1)]
konuk

2
Yossi: Ölüyü büyüttüğüm için özür dilerim, ama cevabında daha büyük bir sorun var ve bu #injectyöntem kullanılıyor. İle #merge!, #each_with_objectkullanılmış olmalıdır. Eğer #inject, ısrar edilir #mergeziyade #merge!kullanılmış olmalıdır.
Boris Stitnicky

12

Bunu googling yaparken aradığım şey buydu:

[{a: 1}, {b: 2}].reduce({}) { |h, v| h.merge v } => {:a=>1, :b=>2}


Kullanmak istemezsiniz merge, döngü yinelemesi başına yeni bir karma oluşturur ve atar ve çok yavaştır. Bir dizi karma [{a:1},{b:2}].reduce({}, :merge!)varsa, bunun yerine her şeyi aynı (yeni) karma ile birleştirir.

Teşekkürler, ben de bunu istedim! :)
Thanasis Petsas

Ayrıca şunları da yapabilirsiniz.reduce(&:merge!)
Ben Lee

1
[{a: 1}, {b: 2}].reduce(&:merge!)değerlendirir{:a=>1, :b=>2}
Ben Lee

Bu işe yarar çünkü inject / reduce argümanı atlayabileceğiniz bir özelliğe sahiptir, bu durumda girdi argümanı olarak çalışmak için dizinin ilk argümanını ve dizi olarak dizinin geri kalanını dizi olarak kullanır. Bunu sembol-proc ile birleştirin ve bu özlü yapıyı elde edin. Başka bir deyişle [{a: 1}, {b: 2}].reduce(&:merge!), aynı [{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) }olanla aynıdır [{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) }.
Ben Lee

10

Enumeratoriçerir Enumerable. Bu yana 2.1, Enumerableaynı zamanda bir yöntemi vardır #to_h. Bu yüzden şunu yazabiliriz: -

a = ["item 1", "item 2", "item 3", "item 4"]
a.each_slice(2).to_h
# => {"item 1"=>"item 2", "item 3"=>"item 4"}

Çünkü #each_sliceblok olmadan bize verir Enumeratorve yukarıdaki açıklamaya göre, nesneyi #to_hyöntemi çağırabiliriz Enumerator.


7

Tek bir dizi için böyle deneyebilirsiniz

irb(main):019:0> a = ["item 1", "item 2", "item 3", "item 4"]
  => ["item 1", "item 2", "item 3", "item 4"]
irb(main):020:0> Hash[*a]
  => {"item 1"=>"item 2", "item 3"=>"item 4"}

dizi dizisi için

irb(main):022:0> a = [[1, 2], [3, 4]]
  => [[1, 2], [3, 4]]
irb(main):023:0> Hash[*a.flatten]
  => {1=>2, 3=>4}

6
a = ["item 1", "item 2", "item 3", "item 4"]
Hash[ a.each_slice( 2 ).map { |e| e } ]

veya nefret ediyorsanız Hash[ ... ]:

a.each_slice( 2 ).each_with_object Hash.new do |(k, v), h| h[k] = v end

veya kırık işlevsel programlamanın tembel bir hayranıysanız:

h = a.lazy.each_slice( 2 ).tap { |a|
  break Hash.new { |h, k| h[k] = a.find { |e, _| e == k }[1] }
}
#=> {}
h["item 1"] #=> "item 2"
h["item 3"] #=> "item 4"

Tamamen nefret etmiyorsanız Hash[ ... ]ancak zincirleme bir yöntem olarak kullanmak istiyorsanız (yapabileceğiniz gibi to_h) Boris önerilerini birleştirebilir ve yazabilirsiniz:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }
b-studios

Yukarıdaki kodun anlambilimini daha net hale getirmek için: Bu , başlangıçta boş olan ve gerektiğinde orijinal diziden öğeleri a çeken bir "tembel karma" h oluşturur . Ancak o zaman aslında h'de saklanırlar!
Daniel Werner

1

Tüm cevaplar başlangıç ​​dizisinin benzersiz olduğunu varsayar. OP, yinelenen girişlere sahip dizilerin nasıl işleneceğini belirtmedi, bu da yinelenen anahtarlarla sonuçlandı.

Şuna bakalım:

a = ["item 1", "item 2", "item 3", "item 4", "item 1", "item 5"]

item 1 => item 2Geçersiz kılındığı için çifti kaybedeceksiniz bij item 1 => item 5:

Hash[*a]
=> {"item 1"=>"item 5", "item 3"=>"item 4"}

Tüm reduce(&:merge!)sonuçlar, aynı çıkarma sonucu dahil .

Yine de, bu tam olarak beklediğiniz şey olabilir. Ancak diğer durumlarda, muhtemelen Arraybunun yerine for değeri içeren bir sonuç almak istersiniz :

{"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

En saf yol, bir yardımcı değişken, varsayılan değere sahip bir karma oluşturmak ve daha sonra bunu bir döngüde doldurmak olacaktır:

result = Hash.new {|hash, k| hash[k] = [] } # Hash.new with block defines unique defaults.
a.each_slice(2) {|k,v| result[k] << v }
a
=> {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

Yukarıda bir satırda kullanmak assocve reducebunu yapmak mümkün olabilir , ancak bu akıl yürütmek ve okumak çok daha zorlaşır.

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.