Ruby'de bir karmadaki her değeri değiştirme


170

Bir değerdeki her değeri, değerden önce ve sonra '%' eklemek için değiştirmek istiyorum

{ :a=>'a' , :b=>'b' }

olarak değiştirilmeli

{ :a=>'%a%' , :b=>'%b%' }

Bunu yapmanın en iyi yolu nedir?


1
Lütfen orijinal dize nesnelerini mutasyona uğratmak, orijinalin mutasyona uğramak veya hiçbir şeyi değiştirmek istemiyorsanız açıklığa kavuşturun.
Phrogz

1
O zaman yanlış yanıtı kabul ettiniz. (@Pst için amaçlanan bir suç yoktur, çünkü ben de nesneleri mutasyona
uğratmak

Ama yine de yaklaşım çok güzeldi
theReverseFlick

Yanıtlar:


178

Gerçek dizelerin kendilerinin yerinde değişmesini istiyorsanız (muhtemelen ve istenen şekilde aynı dize nesnelerine yapılan diğer başvuruları etkiler):

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |_,str| str.gsub! /^|$/, '%' }
my_hash.each{ |_,str| str.replace "%#{str}%" }

Karma'nın yerinde değişmesini istiyorsanız, ancak dizeleri etkilemek istemiyorsanız (yeni dizeler almasını istiyorsanız):

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |key,str| my_hash[key] = "%#{str}%" }
my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }

Yeni bir karma istiyorsanız:

# Ruby 1.8.6+
new_hash = Hash[*my_hash.map{|k,str| [k,"%#{str}%"] }.flatten]

# Ruby 1.8.7+
new_hash = Hash[my_hash.map{|k,str| [k,"%#{str}%"] } ]

1
@Andrew Marshall Doğru, teşekkürler. Ruby 1.8'de, Hash.[]bir dizi dizi çiftini kabul etmez , çift ​​sayıda doğrudan argüman gerektirir (bu nedenle önden uyarı).
Phrogz

2
Aslında Hash. [Key_value_pairs] 1.8.7'de tanıtıldı, bu yüzden sadece Ruby 1.8.6 uyarlamaya ve düzleştirmeye ihtiyaç duymuyor.
Marc-André Lafortune

2
@Aupajo bloğa Hash#eachhem anahtar hem de değer verir. Bu durumda, anahtarı umursamadım ve bu yüzden yararlı bir şey söylemedim. Değişken isimleri bir alt çizgi ile başlayabilir ve aslında sadece bir alt çizgi olabilir. Bunu yapmanın hiçbir performans avantajı yok, bu ilk blok değeri ile hiçbir şey yapmıyorum sadece kendini belgeleyen ince bir not.
Phrogz

1
Demek istediğin my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }, hash bloktan geri dönmek zorunda
aceofspades

1
Alternatif olarak, kullanılmayan anahtar değeri için alt çizgi kullanmaktan biraz daha kolay olan each_value yöntemini kullanabilirsiniz.
Strand McCutchen

266

Ruby 2.1 ve üstü sürümlerde şunları yapabilirsiniz:

{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h

5
Teşekkürler, aslında aradığım şey buydu. Neden bu kadar çok oy kullanmadığınızı bilmiyorum.
simperreault

7
Bu çok yavaş ve çok RAM aç. Hash girişi, daha sonra yeni bir Hash'e dönüştürülen bir dizi iç içe diziler üretmek için tekrarlanır. RAM tepe kullanımını göz ardı ederek, çalışma süresi çok daha kötüdür - başka bir cevapta yerinde değişiklik çözümlerine kıyasla bunu karşılaştırmak, aynı sayıda yinelemede 1,5 saniye ile 1,5 saniye arasındadır. Ruby nispeten yavaş bir dil olduğundan, yavaş dilin yavaş bitlerinden kaçınmak çok mantıklıdır :-)
Andrew Hodgkinson

2
@AndrewHodgkinson genel olarak katılıyorum ve çalışma zamanı performansına dikkat etmemeyi savunmuyorum, tüm bu performans tuzaklarını takip etmek bir acı olmaya ve yakut "geliştirici verimliliği" felsefesine karşı çıkmıyor mu? Sanırım bu size bir yorumdan daha az ve bunun bize getirdiği nihai paradoksla ilgili daha genel bir yorum, yakut kullanarak.
elsurudo

4
Muhakeme şu: yakut kullanma kararımızda zaten performanstan vazgeçiyoruz, peki "bu diğer biraz" ne fark yaratıyor? Kaygan bir eğim, değil mi? Kayıt için, okunabilirlik perspektifinden bu çözümü kabul edilen cevaba tercih ediyorum.
elsurudo

1
Ruby #transform_values!2.4+ kullanıyorsanız , sschmeck ( stackoverflow.com/a/41508214/6451879 ) tarafından belirtildiği gibi kullanmak daha da kolaydır .
Finn

128

Ruby 2.4 Hash#transform_values!kullanabileceğiniz yöntemi tanıttı .

{ :a=>'a' , :b=>'b' }.transform_values! { |v| "%#{v}%" }
# => {:a=>"%a%", :b=>"%b%"} 

Tam aradığım şey!
Dolev

4
Elbette Hash#transform_valuesalıcıyı değiştirmeyen (patlama olmadan) da var. Aksi takdirde harika bir cevap, teşekkürler!
iGEL

1
Bu gerçekten benim kullanımımı azaltacaktırreduce :-p
iGEL

86

Bir Hash'in değerlerini yerinde değiştirmenin en iyi yolu

hash.update(hash){ |_,v| "%#{v}%" }

Daha az kod ve açık niyet. Ayrıca, değiştirilmesi gereken değerlerin ötesinde hiçbir yeni nesne tahsis edilmediğinden daha hızlıdır.


Tam olarak doğru değil: yeni dizeler ayrıldı. Yine de, etkili olan ilginç bir çözüm. +1
Phrogz

@Prog iyi bir nokta; Cevabı güncelledim. Genel olarak değer tahsisi önlenemez, çünkü tüm değer dönüşümleri gibi mutasyonlar olarak ifade edilemez gsub!.
Sim

3
Cevabımla aynı, ancak başka bir eşanlamlı olarak, updateniyeti daha iyi ilettiğini kabul ediyorum merge!. Bence bu en iyi cevap.

1
Eğer kullanmıyorsanız k, kullanmak _yerine.
sekrett

28

A, daha okunabilir bir bit mapbu tek öğeli karma bir dizi ve reduceomerge

the_hash.map{ |key,value| {key => "%#{value}%"} }.reduce(:merge)

2
Hash[the_hash.map { |key,value| [key, "%#{value}%"] }]
Kullanmak

Bu, değerleri güncellemenin son derece verimsiz bir yoludur. Her değer çifti için önce bir çift Array(for map) sonra a oluşturur Hash. Daha sonra, azaltma işleminin her adımı "not" u çoğaltacak Hashve yeni anahtar / değer çiftini buna ekleyecektir. En kullanımda az :merge!yer reducenihai değiştirmek için Hashyerinde. Ve sonunda, mevcut nesnenin değerlerini değiştirmiyorsunuz, ancak sorunun sorduğu yeni bir nesne oluşturuyorsunuz.
Sim

Döndürdüğü nileğer the_hashboş
DNNX


16

Orijinaline yan etkiler getirmeyen bir yöntem:

h = {:a => 'a', :b => 'b'}
h2 = Hash[h.map {|k,v| [k, '%' + v + '%']}]

Hash # haritası ayrıca Hash.mapbir Hash döndürmediğini açıklayan ilginç bir okuma olabilir (bu nedenle sonuçta ortaya çıkan [key,value]çiftler dizisi yeni bir Hash'e dönüştürülür) ve aynı genel desene alternatif yaklaşımlar sağlar.

Mutlu kodlama.

[Feragatname: Hash.mapRuby 2.x'te anlambilimin değişip değişmediğinden emin değilim ]


7
Matz Hash.map, Ruby 2.x'te anlambilimin değişip değişmediğini bile biliyor mu?
Andrew Grimm

1
Hash # [] yöntemi çok faydalı, ancak çok çirkin. Dizileri aynı şekilde karma değerlere dönüştürmenin daha güzel bir yöntemi var mı?
Martijn

15
my_hash.each do |key, value|
  my_hash[key] = "%#{value}%"
end

Yan etkileri sevmiyorum ama yaklaşım için +1 :) each_with_objectYakında 1.9 (IIRC) var, bu da isme doğrudan erişmeye gerek kalmıyor ve Map#mergeaynı zamanda işe yarayabilir. Karmaşık ayrıntıların nasıl farklı olduğundan emin değilim.

1
İlk karma değiştirilir - bu tamam ise davranış beklenen ancak "unutulmuş" eğer ince sorunlara neden olabilir. Nesne değişebilirliğini azaltmayı tercih ederim, ancak her zaman pratik olmayabilir. (Ruby neredeyse "yan etkisi olmayan" bir dildir ;-)

8

Hash.merge! en temiz çözüm

o = { a: 'a', b: 'b' }
o.merge!(o) { |key, value| "%#{ value }%" }

puts o.inspect
> { :a => "%a%", :b => "%b%" }

@MichieldeMare Üzgünüm, bunu yeterince test etmedim. Haklısın, bloğun iki parametre alması gerekiyor. Düzeltildi.

5

Bunun gibi RSpec ile test ettikten sonra:

describe Hash do
  describe :map_values do
    it 'should map the values' do
      expect({:a => 2, :b => 3}.map_values { |x| x ** 2 }).to eq({:a => 4, :b => 9})
    end
  end
end

Hash # map_values ​​uygulamasını aşağıdaki gibi uygulayabilirsiniz:

class Hash
  def map_values
    Hash[map { |k, v| [k, yield(v)] }]
  end
end

Fonksiyon daha sonra şu şekilde kullanılabilir:

{:a=>'a' , :b=>'b'}.map_values { |v| "%#{v}%" }
# {:a=>"%a%", :b=>"%b%"}

1

Hangi en hızlı varyantın burada en hızlı olduğunu merak ediyorsanız:

Calculating -------------------------------------
inplace transform_values! 1.265k  0.7%) i/s -      6.426k in   5.080305s
      inplace update      1.300k  2.7%) i/s -      6.579k in   5.065925s
  inplace map reduce    281.367   1.1%) i/s -      1.431k in   5.086477s
      inplace merge!      1.305k  0.4%) i/s -      6.630k in   5.080751s
        inplace each      1.073k  0.7%) i/s -      5.457k in   5.084044s
      inplace inject    697.178   0.9%) i/s -      3.519k in   5.047857s
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.