Làm cách nào để thay đổi giá trị Hash?


126

Tôi muốn thay thế từng cái valuetrong một hàm băm bằng value.some_method.

Ví dụ, với một hàm băm đơn giản:

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

mọi giá trị phải là .upcased, vì vậy nó trông giống như:

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

Tôi đã cố gắng #collect#mapluôn luôn nhận được mảng trở lại. Có một cách thanh lịch để làm điều này?

CẬP NHẬT

Chết tiệt, tôi quên mất: Băm nằm trong một biến thể không nên thay đổi. Tôi cần một hàm băm mới với các giá trị đã thay đổi, nhưng không muốn xác định rõ ràng biến đó và sau đó lặp lại hàm băm. Cái gì đó như:

new_hash = hash.magic{ ... }

ví dụ thứ hai trong câu trả lời của tôi trả về một hàm băm mới. nhận xét về nó nếu bạn muốn tôi mở rộng về phép thuật mà #inject đang làm.
kch

Câu trả lời:


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

hoặc, nếu bạn muốn làm điều đó không phá hủy và trả lại một hàm băm mới thay vì sửa đổi my_hash:

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

Phiên bản cuối cùng này có thêm lợi ích là bạn cũng có thể chuyển đổi các phím.


26
Gợi ý đầu tiên là không phù hợp; sửa đổi một yếu tố có thể đếm được trong khi lặp lại dẫn đến hành vi không xác định - điều này cũng hợp lệ trong các ngôn ngữ khác. Đây là câu trả lời từ Matz (anh ấy trả lời một ví dụ bằng cách sử dụng Mảng, nhưng câu trả lời là chung chung): j.mp/tPOh2U
Marcus

4
@Marcus Tôi đồng ý rằng nói chung nên tránh sửa đổi như vậy. Nhưng trong trường hợp này có lẽ an toàn, vì sửa đổi không thay đổi các lần lặp lại trong tương lai. Bây giờ nếu một khóa khác (không phải là khóa được chuyển cho khối) được gán, thì sẽ có rắc rối.
Kelvin

36
@Adam @kch each_with_objectlà phiên bản tiện lợi hơn injectkhi bạn không muốn kết thúc khối bằng hàm băm:my_hash.each_with_object({}) {|(k, v), h| h[k] = v.upcase }
Kelvin

6
Ngoài ra còn có một cú pháp rất gọn gàng để chuyển đổi danh sách các cặp thành hàm băm, do đó bạn có thể viếta_new_hash = Hash[ my_hash.map { |k, v| [k, v.upcase] } ]
viết lại vào

2
với Ruby 2 bạn có thể viết.to_h
roman-roman


35

Bạn có thể thu thập các giá trị và chuyển đổi nó từ Array sang Hash một lần nữa.

Như thế này:

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

23

Điều này sẽ làm điều đó:

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

Trái ngược với injectlợi thế là bạn không cần phải trả lại hàm băm một lần nữa bên trong khối.


Tôi thích rằng phương pháp này không sửa đổi hàm băm ban đầu.
Christian Di Lorenzo

10

Hãy thử chức năng này:

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

5
đó là tốt, nhưng cần lưu ý rằng nó chỉ hoạt động khi j có một phương thức phá hoại tự hành động. Vì vậy, nó không thể xử lý trường hợp value.some_method chung.
kch

Điểm tốt, và với bản cập nhật của anh ấy, giải pháp thứ hai của bạn (không phá hủy) là tốt nhất.
Chris Doggett

10

Có một phương pháp cho điều đó trong ActiveSupport v4.2.0. Nó được gọi transform_valuesvà về cơ bản chỉ thực thi một khối cho mỗi cặp khóa-giá trị.

Vì họ đang làm điều đó với một eachtôi nghĩ không có cách nào tốt hơn là lặp lại.

hash = {sample: 'gach'}

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

Cập nhật:

Kể từ Ruby 2.4.0, bạn có thể sử dụng #transform_values#transform_values!.


2

Bạn có thể muốn tiến thêm một bước và thực hiện điều này trên hàm băm lồng nhau. Chắc chắn điều này xảy ra một số tiền hợp lý với các dự án Rails.

Đây là một số mã để đảm bảo hàm băm params có trong 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  

1

Tôi làm một cái gì đó như thế này:

new_hash = Hash [* gốc_hash.collect {| khóa, giá trị | [khóa, giá trị + 1]}. làm phẳng]

Điều này cung cấp cho bạn các phương tiện để chuyển đổi khóa hoặc giá trị thông qua bất kỳ biểu thức nào (và tất nhiên nó không phá hủy).


1

Ruby có tapphương thức ( 1.8.7 , 1.9.32.1.0 ) rất hữu ích cho những thứ như thế này.

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

Rails-cụ thể

Trong trường hợp ai đó chỉ cần gọi to_sphương thức đến từng giá trị và không sử dụng Rails 4.2 (bao gồm liên kếttransform_values phương thức ), bạn có thể thực hiện như sau:

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

Lưu ý: Việc sử dụng thư viện 'json' là bắt buộc.

Lưu ý 2: Điều này cũng sẽ biến các phím thành chuỗi


1

Nếu bạn biết rằng các giá trị là chuỗi, bạn có thể gọi thay thế phương thức trên chúng trong khi trong chu trình: theo cách này bạn sẽ thay đổi giá trị.

Altohugh điều này được giới hạn trong trường hợp các giá trị là các chuỗi và do đó không trả lời đầy đủ câu hỏi, tôi nghĩ rằng nó có thể hữu ích cho ai đó.


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

Đây có phải là hiệu quả?
ioquatix
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.