Bagaimana cara mengubah nilai Hash?

126

Saya ingin mengganti masing-masing valuedalam hash dengan value.some_method.

Misalnya, untuk diberi hash sederhana:

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

setiap nilai harus .upcased, sehingga terlihat seperti:

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

Saya mencoba #collectdan #maptetapi selalu mendapatkan array kembali. Apakah ada cara yang elegan untuk melakukan ini?

MEMPERBARUI

Sial, saya lupa: Hash dalam variabel instan yang tidak boleh diubah. Saya perlu hash baru dengan nilai-nilai yang diubah, tetapi lebih suka tidak mendefinisikan variabel secara eksplisit dan kemudian loop di atas hash mengisinya. Sesuatu seperti:

new_hash = hash.magic{ ... }
potashin
sumber
contoh kedua dalam jawaban saya mengembalikan hash baru. berkomentar tentang itu jika Anda ingin saya memperluas keajaiban yang #inject lakukan.
kch

Jawaban:

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

atau, jika Anda lebih suka melakukannya tanpa merusak, dan kembalikan hash baru alih-alih memodifikasi my_hash:

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

Versi terakhir ini memiliki manfaat tambahan sehingga Anda dapat mengubah kunci juga.

kch
sumber
26
Saran pertama tidak tepat; memodifikasi elemen enumerable saat iterasi mengarah ke perilaku yang tidak terdefinisi - ini juga berlaku dalam bahasa lain. Berikut ini balasan dari Matz (ia membalas contoh menggunakan Array, tetapi jawabannya umum): j.mp/tPOh2U
Marcus
4
@ Marscus Saya setuju bahwa secara umum modifikasi seperti itu harus dihindari. Tetapi dalam kasus ini mungkin aman, karena modifikasi tidak mengubah iterasi masa depan. Sekarang jika kunci lain (yang bukan yang dilewati ke blok) ditugaskan, maka akan ada masalah.
Kelvin
36
@Adam @kch each_with_objectadalah versi yang lebih nyaman injectketika Anda tidak ingin mengakhiri blok dengan hash:my_hash.each_with_object({}) {|(k, v), h| h[k] = v.upcase }
Kelvin
6
Ada juga sintaks yang sangat rapi untuk mengubah daftar pasangan menjadi hash, sehingga Anda dapat menulisa_new_hash = Hash[ my_hash.map { |k, v| [k, v.upcase] } ]
ditulis ulang
2
dengan Ruby 2 Anda dapat menulis.to_h
roman-roman
35

Anda dapat mengumpulkan nilai, dan mengonversinya dari Array ke Hash lagi.

Seperti ini:

config = Hash[ config.collect {|k,v| [k, v.upcase] } ]
Endel
sumber
23

Ini akan melakukannya:

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

Berbeda dengan injectkeuntungannya adalah Anda tidak perlu mengembalikan hash lagi di dalam blok.

Konrad Reiche
sumber
Saya suka bahwa metode ini tidak mengubah hash asli.
Christian Di Lorenzo
10

Coba fungsi ini:

h = {"a" => "b", "c" => "d"}
h.each{|i,j| j.upcase!} # now contains {"a" => "B", "c" => "D"}.
Chris Doggett
sumber
5
itu bagus, tetapi perlu dicatat bahwa itu hanya bekerja ketika j memiliki metode destruktif yang bertindak sendiri. Jadi tidak bisa menangani case value.some_method umum.
kch
Poin bagus, dan dengan pembaruannya, solusi kedua Anda (non-destruktif) adalah yang terbaik.
Chris Doggett
10

Ada metode untuk itu di ActiveSupport v4.2.0. Ini disebut transform_valuesdan pada dasarnya hanya menjalankan blok untuk setiap pasangan kunci-nilai.

Karena mereka melakukannya dengan eachsaya pikir tidak ada cara yang lebih baik selain untuk mengulang.

hash = {sample: 'gach'}

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

Memperbarui:

Sejak Ruby 2.4.0 Anda dapat menggunakan #transform_valuesdan #transform_values!.

schmijos
sumber
2

Anda mungkin ingin melangkah lebih jauh dan melakukan ini pada hash bersarang. Tentu ini terjadi dalam jumlah yang wajar dengan proyek Rails.

Berikut ini beberapa kode untuk memastikan hash params ada di UTF-8:

  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  
Tokumine
sumber
1

Saya melakukan sesuatu seperti ini:

new_hash = Hash [* original_hash.collect {| key, value | [kunci, nilai + 1]}. ratakan]

Ini memberi Anda fasilitas untuk mengubah kunci atau nilai melalui ekspresi apa pun juga (dan tentu saja itu tidak merusak).


sumber
1

Ruby memiliki tapmetode ( 1.8.7 , 1.9.3 dan 2.1.0 ) yang sangat berguna untuk hal-hal seperti ini.

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"}
Nate
sumber
1

Khusus untuk rel

Jika seseorang hanya perlu memanggil to_smetode ke masing-masing nilai dan tidak menggunakan Rails 4.2 (yang termasuk tautantransform_values metode ), Anda dapat melakukan hal berikut:

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

Catatan: Penggunaan perpustakaan 'json' diperlukan.

Catatan 2: Ini juga akan mengubah kunci menjadi string

lllllll
sumber
1

Jika Anda tahu bahwa nilainya adalah string, Anda dapat memanggil metode ganti pada mereka saat dalam siklus: dengan cara ini Anda akan mengubah nilainya.

Altohugh ini terbatas pada kasus di mana nilainya adalah string dan karenanya tidak menjawab pertanyaan sepenuhnya, saya pikir itu bisa berguna bagi seseorang.

benar-benar bagus
sumber
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"}
cnst
sumber
Apakah ini efisien?
ioquatix