2009-05-01 26 views
96

Her value ürününü value.some_method ile bir hash değerinde değiştirmek istiyorum. Geri #collect ve #map ama her zaman sadece almak diziler çalıştıKarma değerler nasıl değiştirilir?

{"a" => "B", "c" => "D"} 

: benzediğini böylece her değer .upcase d olmalıdır

{"a" => "b", "c" => "d"}` 

: Örneğin, basit bir karma verilen için

. Bunu yapmanın zarif bir yolu var mı?

GÜNCELLEME

Kahretsin, ben unuttum: karma değiştirilmemesi gerektiğini bir örnek değişkeninde olduğunu. Değişen değerler ile yeni bir karmaşaya ihtiyacım var, ama bu değişkeni açıkça tanımlamamayı ve daha sonra onu dolduran karmaşayı çevirmeyi tercih ederim. Eğer olmayan yıkıcı yapmayı tercih ve bunun yerine my_hash değiştirerek yeni bir karma iade ederseniz,

new_hash = hash.magic{ ... } 
+0

Cevabımdaki ikinci örnek, yeni bir karma döndürür. #inject'in yaptığı büyü üzerinde genişlememi istiyorsanız yorum yapın. – kch

cevap

185
my_hash.each { |k, v| my_hash[k] = v.upcase } 

ya:

a_new_hash = my_hash.inject({}) { |h, (k, v)| h[k] = v.upcase; h } 

Bu son sürüm eklendi sahiptir şey gibi Anahtarları da dönüştürebilirsin.

+22

İlk öneri uygun değil; Yinelenen bir öğenin yinelenen tanımsız bir şekilde değiştirilmesi, tanımlanmamış davranışlara yol açar - bu diğer dillerde de geçerlidir. Matz'dan bir cevap (bir Array kullanarak bir örneğe yanıt verir, ancak cevap geneldir): http://j.mp/tPOh2U – Marcus

+4

@Marcus Genel olarak bu tür bir modifikasyondan kaçınılması gerektiğine katılıyorum. Ancak bu durumda muhtemelen güvenlidir, çünkü modifikasyon gelecek iterasyonları değiştirmez. Şimdi başka bir anahtar (bloğa geçmemiş olan) atanmışsa, o zaman sorun olur. – Kelvin

+28

@Adam @kch 'each_with_object', karma ile bloğu sonlandırmak istemediğinizde, 'inject' in daha kullanışlı bir sürümüdür: my_hash.each_with_object ({}) {| (k, v), h | h [k] = v.upcase} ' – Kelvin

9

bu işlevi deneyin:

h = {"a" => "b", "c" => "d"} 
h.each{|i,j| j.upcase!} # now contains {"a" => "B", "c" => "D"}. 
+5

yazabilirsin, bu iyi bir şeydir, fakat bunun yalnızca j'nin kendine ait bir yıkıcı yönteme sahip olduğu zaman işe yarayacağını belirtmek gerekir. Yani genel value.some_method davayı kaldıramıyor. – kch

+0

İyi nokta ve güncellemesiyle, ikinci çözümünüz (yıkıcı olmayan) en iyisidir. –

1

Böyle bir şey yapın:

new_hash = Hash [* original_hash.collect {| anahtar, değer | [anahtar, değer + 1]}. flatten]

Bu, anahtar veya değeri herhangi bir ifadeyle (ve elbette yıkıcı değil) dönüştürmek için olanak sağlar.

+0

Bu da oldukça akıllıca. Teşekkürler!:) –

1

Bir adım daha ileri atmak ve bunu iç içe geçmiş bir hashta yapmak isteyebilirsiniz. Kesinlikle bu Rails projeleri ile adil bir miktar olur.

İşte bir params karma UTF-8 olduğundan emin olmak için bazı kod:

def convert_hash hash 
    hash.inject({}) do |h,(k,v)| 
     if v.kind_of? String 
     h[k] = to_utf8(v) 
     else 
     h[k] = convert_hash(v) 
     end 
     h 
    end  
    end  

    # Iconv UTF-8 helper 
    # Converts strings into valid UTF-8 
    # 
    # @param [String] untrusted_string the string to convert to UTF-8 
    # @return [String] your string in UTF-8 
    def to_utf8 untrusted_string="" 
    ic = Iconv.new('UTF-8//IGNORE', 'UTF-8') 
    ic.iconv(untrusted_string + ' ')[0..-2] 
    end 
30

Sen değerlerini toplamak ve yeniden Hash Array onu dönüştürebilir. Bunun gibi

:

config = Hash[ config.collect {|k,v| [k, v.upcase] } ] 
+0

Bu daha özlü ve IMHO daha iyi bir sözdizimseldir - @rewritten kabul edilen cevapta yorumladığı gibi – MatzFan

21

Bu yapacağım:

inject avantaj aksine
my_hash.each_with_object({}) { |(key, value), hash| hash[key] = value.upcase } 

Eğer bloğunun içinde tekrar karma dönmek için hiçbir ihtiyaç olmasıdır.

+0

Bu yöntemin orijinal karmayı değiştirmediğini beğenirim. –

1

Ruby, tap yöntemini (1.8.7, 1.9.3 ve 2.1.0) bu gibi şeyler için çok yararlıdır.

original_hash = { :a => 'a', :b => 'b' } 
original_hash.clone.tap{ |h| h.each{ |k,v| h[k] = v.upcase } } 
# => {:a=>"A", :b=>"B"} 
original_hash # => {:a=>"a", :b=>"b"} 
-1

Raylar özgü birisi yalnızca değerlerin her birine to_s yöntemini çağırmak gerekiyor ve Rails 4.2 (transform_values yöntemi link içerir) kullanmıyor

durumda, aşağıdakileri yapabilirsiniz:

original_hash = { :a => 'a', :b => BigDecimal('23.4') } 
#=> {:a=>"a", :b=>#<BigDecimal:5c03a00,'0.234E2',18(18)>} 
JSON(original_hash.to_json) 
#=> {"a"=>"a", "b"=>"23.4"} 

Not: 'json' kütüphanesinin kullanılması requir olduğunu ed.

Not 2: Bu durum

7

dizeleri içine anahtarları dönecek ActiveSupport v4.2.0 bunun için bir yöntem var. Bu, transform_values olarak adlandırılır ve temel olarak her anahtar/değer çifti için bir blok yürütür.

each ile yaptıklarından beri, döngüden daha iyi bir yol olmadığını düşünüyorum. Bu yolu değerini değişecek: değerleri dizelerdir biliyorsanız döngüsünde iken

hash = {sample: 'gach'} 

result = {} 
hash.each do |key, value| 
    result[key] = do_stuff(value) 
end 
0

, onlara replace yöntemini çağırabilirsiniz.

Altohugh bu, değerlerin dizge olduğu ve dolayısıyla soruyu tam olarak cevaplamadığı durumla sınırlıdır, bunun birisine yararlı olabileceğini düşündüm. Ayrıca yıkıcı Hash#transform_values! sürümü var

hash = {"a" => "b", "c" => "d"} 
new_hash = hash.transform_values(&:upcase) 
# => {"a" => "B", "c" => "D"} 

:

1
new_hash = old_hash.merge(old_hash) do |_key, value, _value| 
      value.upcase 
      end 

# old_hash = {"a" => "b", "c" => "d"} 
# new_hash = {"a" => "B", "c" => "D"} 
+0

Bu verimli mi? – ioquatix

28

ruby 2.4.0 beri yerli Hash#transform_values yöntemi kullanabilirsiniz.

+3

büyük, aynı zamanda raylar ile geliyor 4.2.1 https://apidock.com/rails/v4.2.1/Hash/transform_values ​​ –

+1

Bu en iyi çözümdür. Kod daha kısa ve daha okunabilir. –

+0

@ZackXu Bu daha iyidir, ancak bu sorunun 2009'da sorulmuş olduğu gerçeğini unutmayalım ve o anda cevapların çoğu yazıldı. 2017'ye hızlı ileri sarmak, Hash anahtar ve değer dönüşümleri için işler çok daha kolay. Çünkü yeni Ruby sürümleri bu yöntemleri uyguladı. – rubyprince