Mengubah setiap nilai dalam hash di Ruby

170

Saya ingin mengubah setiap nilai dalam hash untuk menambahkan '%' sebelum dan sesudah nilai demikian

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

harus diubah menjadi

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

Apa cara terbaik untuk melakukan ini?

Klik Balik terbalik
sumber
1
Tolong jelaskan jika Anda ingin bermutasi objek string asli, hanya bermutasi yang asli, atau bermutasi tidak ada.
Phrogz
1
Maka Anda telah menerima jawaban yang salah. (Jangan tersinggung dengan @pst yang dimaksudkan, karena saya secara pribadi juga menganjurkan pemrograman gaya fungsional alih-alih objek bermutasi.)
Phrogz
Tapi tetap saja pendekatannya bagus
theReverseFlick

Jawaban:

178

Jika Anda ingin string yang sebenarnya bermutasi di tempat (mungkin dan diinginkan mempengaruhi referensi lain ke objek string yang sama):

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

Jika Anda ingin hash berubah di tempat, tetapi Anda tidak ingin memengaruhi string (Anda ingin mendapatkan string baru):

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

Jika Anda ingin hash baru:

# 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}%"] } ]
Phrogz
sumber
1
@ Andrew Marshall Benar, terima kasih. Di Ruby 1.8, Hash.[]tidak menerima array dari pasangan array, itu membutuhkan sejumlah argumen langsung (karena itu percikan di depan).
Phrogz
2
Sebenarnya, Hash. [Key_value_pairs] diperkenalkan di 1.8.7, jadi hanya Ruby 1.8.6 yang tidak membutuhkan gambar percikan & perataan.
Marc-André Lafortune
2
@Aupajo Hash#eachmenghasilkan kunci dan nilai pada blok. Dalam hal ini, saya tidak peduli dengan kuncinya, jadi saya tidak menyebutkan apa pun yang bermanfaat. Nama variabel dapat dimulai dengan garis bawah, dan pada kenyataannya mungkin hanya garis bawah. Tidak ada manfaat kinerja melakukan ini, itu hanya catatan mendokumentasikan diri sendiri yang halus bahwa saya tidak melakukan apa pun dengan nilai blok pertama itu.
Phrogz
1
Saya pikir maksud Anda my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }, harus mengembalikan hash dari blok
aceofspades
1
Sebagai alternatif, Anda dapat menggunakan metode Each_value, yang sedikit lebih mudah dipahami daripada menggunakan garis bawah untuk nilai kunci yang tidak digunakan.
Strand McCutchen
266

Di Ruby 2.1 dan yang lebih tinggi dapat Anda lakukan

{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h
shock_one
sumber
5
Terima kasih, sebenarnya apa yang saya cari. Tidak tahu mengapa Anda tidak terlalu dibesarkan.
simperreault
7
Ini, meskipun, sangat lambat dan sangat lapar RAM. Input Hash diulang untuk menghasilkan seperangkat Array bersarang antara yang kemudian dikonversi menjadi Hash baru. Mengabaikan penggunaan puncak RAM, run time jauh lebih buruk - pembandingan ini dibandingkan solusi modifikasi di tempat dalam jawaban lain menunjukkan 2,5s versus 1,5s pada jumlah iterasi yang sama. Karena Ruby adalah bahasa yang relatif lambat, menghindari sedikit bahasa lambat menjadi lambat :-)
Andrew Hodgkinson
2
@AndrewHodgkinson sementara secara umum saya setuju dan saya tidak menganjurkan tidak memperhatikan kinerja runtime, tidak melacak semua jebakan kinerja ini mulai menjadi sakit dan bertentangan dengan filosofi "produktivitas pertama pengembang" rubi? Saya kira ini kurang dari komentar untuk Anda, dan lebih dari komentar umum pada paradoks akhirnya membawa kita ke, menggunakan ruby.
elsurudo
4
Masalahnya adalah: yah, kami sudah menyerah kinerja dalam keputusan kami untuk menggunakan ruby, jadi apa bedanya "sedikit ini"? Ini lereng yang licin, bukan? Sebagai catatan, saya lebih suka solusi ini daripada jawaban yang diterima, dari perspektif keterbacaan.
elsurudo
1
Jika Anda menggunakan Ruby 2.4+, itu bahkan lebih mudah digunakan #transform_values!seperti yang ditunjukkan oleh sschmeck ( stackoverflow.com/a/41508214/6451879 ).
Finn
128

Ruby 2.4 memperkenalkan metode Hash#transform_values!yang dapat Anda gunakan.

{ :a=>'a' , :b=>'b' }.transform_values! { |v| "%#{v}%" }
# => {:a=>"%a%", :b=>"%b%"} 
sschmeck
sumber
Persis apa yang saya cari!
Dolev
4
Tentu saja ada juga Hash#transform_values(tanpa bang), yang tidak memodifikasi receiver. Kalau tidak, jawaban yang bagus, terima kasih!
iGEL
1
Ini benar-benar akan mengurangi penggunaan saya pada reduce:-p
iGEL
86

Cara terbaik untuk memodifikasi nilai-nilai Hash di tempat adalah

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

Lebih sedikit kode dan maksud yang jelas. Juga lebih cepat karena tidak ada objek baru yang dialokasikan di luar nilai yang harus diubah.

Sim
sumber
Tidak sepenuhnya benar: string baru dialokasikan. Meski begitu, solusi menarik yang efektif. +1
Phrogz
@Phrogz poin bagus; Saya memperbarui jawabannya. Alokasi nilai tidak dapat dihindari secara umum karena tidak semua transformasi nilai dapat dinyatakan sebagai mutator seperti gsub!.
Sim
3
Sama seperti jawaban saya tetapi dengan sinonim lain, saya setuju bahwa updatemenyampaikan maksud lebih baik daripada merge!. Saya pikir ini adalah jawaban terbaik.
1
Jika Anda tidak menggunakan k, gunakan _saja.
sekrett
28

Sedikit lebih mudah dibaca, mapke array hash elemen tunggal dan reducedenganmerge

the_hash.map{ |key,value| {key => "%#{value}%"} }.reduce(:merge)
edgerunner
sumber
2
Lebih jelas untuk menggunakanHash[the_hash.map { |key,value| [key, "%#{value}%"] }]
meagar
Ini adalah cara yang sangat tidak efisien untuk memperbarui nilai. Untuk setiap pasangan nilai, pertama-tama ia menciptakan pasangan Array(untuk map) lalu a Hash. Kemudian, setiap langkah operasi pengurangan akan menduplikasi "memo" Hashdan menambahkan pasangan nilai kunci yang baru ke dalamnya. Setidaknya digunakan :merge!dalam reducememodifikasi final Hashdi tempat. Dan pada akhirnya, Anda tidak mengubah nilai-nilai objek yang ada tetapi membuat objek baru, yang bukan pertanyaannya.
Sim
kembali niljika the_hashkosong
DNNX
16

Salah satu metode yang tidak memperkenalkan efek samping ke aslinya:

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

Peta Hash # juga dapat menjadi bacaan yang menarik karena menjelaskan mengapa Hash.mapHash tidak mengembalikan Hash (itulah sebabnya Array [key,value]pasangan yang dihasilkan dikonversi menjadi Hash baru) dan memberikan pendekatan alternatif untuk pola umum yang sama.

Selamat coding.

[Penafian: Saya tidak yakin apakah Hash.mapsemantik berubah di Ruby 2.x]


sumber
7
Apakah Matz bahkan tahu jika Hash.mapsemantik berubah di Ruby 2.x?
Andrew Grimm
1
Metode Hash # [] sangat berguna, tetapi sangat jelek. Apakah ada metode yang lebih cantik untuk mengubah array menjadi hash dengan cara yang sama?
Martijn
15
my_hash.each do |key, value|
  my_hash[key] = "%#{value}%"
end
Andrew Marshall
sumber
Saya tidak suka efek samping, tetapi +1 untuk pendekatan :) Ada each_with_objectdi Ruby 1.9 (IIRC) yang menghindari perlu mengakses nama secara langsung dan Map#mergemungkin juga berfungsi. Tidak yakin bagaimana detail rumitnya berbeda.
1
Hash awal diubah - tidak apa-apa jika perilaku diantisipasi tetapi dapat menyebabkan masalah halus jika "dilupakan". Saya lebih suka mengurangi mutabilitas objek, tetapi mungkin tidak selalu praktis. (Ruby bukan bahasa "bebas efek samping" ;-)
8

Hash.merge! adalah solusi terbersih

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

puts o.inspect
> { :a => "%a%", :b => "%b%" }
Dorian
sumber
@MichieldeMare Maaf, saya tidak cukup menguji ini. Anda benar, blok perlu mengambil dua parameter. Dikoreksi.
5

Setelah mengujinya dengan RSpec seperti ini:

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

Anda dapat menerapkan Hash # map_values ​​sebagai berikut:

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

Fungsi kemudian dapat digunakan seperti ini:

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

Jika Anda penasaran varian inplace mana yang tercepat di sini adalah:

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
lzap
sumber