Thay đổi mọi giá trị trong hàm băm trong Ruby


170

Tôi muốn thay đổi mọi giá trị trong hàm băm để thêm '%' trước và sau giá trị

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

phải đổi thành

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

Cách tốt nhất để làm điều này là gì?


1
Vui lòng làm rõ nếu bạn muốn thay đổi các đối tượng chuỗi gốc, chỉ cần thay đổi bản gốc có hoặc không làm thay đổi gì.
Phrogz

1
Sau đó, bạn đã chấp nhận câu trả lời sai. (Không có ý xúc phạm đến @pst, vì cá nhân tôi cũng ủng hộ lập trình kiểu chức năng thay vì biến đổi các đối tượng.)
Phrogz

Nhưng vẫn là cách tiếp cận tốt đẹp
theReverseFlick

Câu trả lời:


178

Nếu bạn muốn chính các chuỗi thực sự biến đổi tại chỗ (có thể và ảnh hưởng đáng kể đến các tham chiếu khác đến cùng các đối tượng chuỗi):

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

Nếu bạn muốn hàm băm thay đổi tại chỗ, nhưng bạn không muốn ảnh hưởng đến các chuỗi (bạn muốn nó có được chuỗi mới):

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

Nếu bạn muốn băm mới:

# 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 Đúng rồi, cảm ơn. Trong Ruby 1.8, Hash.[]không chấp nhận một mảng các cặp mảng, nó yêu cầu số lượng đối số trực tiếp chẵn (do đó sẽ xuất hiện ở phía trước).
Phrogz

2
Trên thực tế, Hash. [Key_value_pairs] đã được giới thiệu vào phiên bản 1.8.7, do đó, chỉ có Ruby 1.8.6 không cần splat & flatten.
Marc-André Lafortune

2
@Aupajo Hash#eachmang lại cả khóa và giá trị cho khối. Trong trường hợp này, tôi không quan tâm đến chìa khóa, và vì vậy tôi không đặt tên cho nó bất cứ điều gì hữu ích. Tên biến có thể bắt đầu bằng dấu gạch dưới và trên thực tế có thể chỉ là dấu gạch dưới. Không có lợi ích hiệu suất khi làm điều này, nó chỉ là một ghi chú tự ghi tinh tế rằng tôi không làm gì với giá trị khối đầu tiên đó.
Phrogz

1
Tôi nghĩ ý bạn là my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }, phải trả lại hàm băm từ khối
aceofspades

1
Thay phiên, bạn có thể sử dụng phương thức Each_value, dễ hiểu hơn một chút so với sử dụng dấu gạch dưới cho giá trị khóa không sử dụng.
Strand McCutchen

265

Trong Ruby 2.1 trở lên, bạn có thể làm

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

5
Cảm ơn, thực sự là những gì tôi đang tìm kiếm. Không biết tại sao bạn không được khuyến khích nhiều như vậy.
simperreault

7
Điều này là, mặc dù, rất chậm và rất đói RAM. Hash đầu vào được lặp đi lặp lại để tạo ra một tập hợp các Mảng lồng nhau sau đó được chuyển đổi thành Hash mới. Bỏ qua việc sử dụng tối đa RAM, thời gian chạy tệ hơn nhiều - điểm chuẩn này so với các giải pháp sửa đổi tại chỗ trong câu trả lời khác hiển thị 2,5 giây so với 1,5 giây so với cùng số lần lặp. Vì Ruby là một ngôn ngữ tương đối chậm, nên việc tránh các bit chậm của ngôn ngữ chậm có rất nhiều ý nghĩa :-)
Andrew Hodgkinson

2
@AndrewHodgkinson trong khi nói chung tôi đồng ý và không ủng hộ việc không chú ý đến hiệu suất thời gian chạy, không theo dõi tất cả những cạm bẫy hiệu suất này bắt đầu trở thành nỗi đau và đi ngược lại triết lý "năng suất của nhà phát triển đầu tiên" của ruby? Tôi đoán đây không phải là một bình luận cho bạn, và nhiều hơn một nhận xét chung về nghịch lý cuối cùng mà điều này đưa chúng ta đến, sử dụng ruby.
elsurudo

4
Câu hỏi hóc búa là: tốt, chúng tôi đã từ bỏ hiệu suất trong quyết định thậm chí sử dụng ruby, vậy "sự khác biệt nhỏ này" có gì khác biệt? Đó là một con dốc trơn trượt, phải không? Đối với hồ sơ, tôi thích giải pháp này hơn cho câu trả lời được chấp nhận, từ góc độ dễ đọc.
elsurudo

1
Nếu bạn sử dụng Ruby 2.4+, nó thậm chí còn dễ sử dụng hơn #transform_values!như được chỉ ra bởi sschmeck ( stackoverflow.com/a/41508214/6451879 ).
Finn

127

Ruby 2.4 đã giới thiệu phương pháp Hash#transform_values!mà bạn có thể sử dụng.

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

Chính xác những gì tôi đang tìm kiếm!
Dolev

4
Tất nhiên cũng có Hash#transform_values(không có tiếng nổ), không sửa đổi máy thu. Nếu không, một câu trả lời tuyệt vời, cảm ơn!
iGEL

1
Điều này thực sự sẽ làm giảm việc tôi sử dụng reduce:-p
iGEL

85

Cách tốt nhất để sửa đổi các giá trị của Hash tại chỗ là

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

Ít mã và ý định rõ ràng. Cũng nhanh hơn vì không có đối tượng mới được phân bổ ngoài các giá trị phải được thay đổi.


Không chính xác: chuỗi mới được phân bổ. Tuy nhiên, một giải pháp thú vị mà hiệu quả. +1
Phrogz

@Phrogz điểm tốt; Tôi cập nhật câu trả lời. Việc phân bổ giá trị không thể tránh được nói chung vì không phải tất cả các phép biến đổi giá trị đều có thể được biểu diễn dưới dạng các trình biến đổi như gsub!.
Sim

3
Giống như câu trả lời của tôi nhưng với một từ đồng nghĩa khác, tôi đồng ý rằng updatetruyền đạt ý định tốt hơn merge!. Tôi nghĩ rằng đây là câu trả lời tốt nhất.

1
Nếu bạn không sử dụng k, sử dụng _thay thế.
sekrett

28

Một chút dễ đọc hơn, mapnó là một mảng băm đơn yếu tố và reducevớimerge

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

2
Hash[the_hash.map { |key,value| [key, "%#{value}%"] }]
Rõ ràng

Đây là một cách cực kỳ không hiệu quả để cập nhật giá trị. Với mỗi cặp giá trị, đầu tiên nó tạo một cặp Array(for map) sau đó a Hash. Sau đó, mỗi bước của thao tác giảm sẽ nhân đôi "ghi nhớ" Hashvà thêm cặp khóa-giá trị mới vào đó. Ít nhất sử dụng :merge!trong reduceđể thay đổi trận chung kết Hashtại chỗ. Và cuối cùng, bạn không sửa đổi các giá trị của đối tượng hiện tại mà tạo ra một đối tượng mới, đó không phải là những gì câu hỏi được hỏi.
Sim

nó trả về nilnếu the_hashtrống
DNNX


16

Một phương pháp không giới thiệu tác dụng phụ cho bản gốc:

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

Bản đồ Hash # cũng có thể là một cách đọc thú vị vì nó giải thích lý do tại sao Hash.mapkhông trả lại Hash (đó là lý do tại sao Mảng các [key,value]cặp kết quả được chuyển đổi thành Hash mới) và cung cấp các cách tiếp cận thay thế cho cùng một mẫu chung.

Chúc mừng mã hóa.

[Tuyên bố miễn trừ trách nhiệm: Tôi không chắc liệu Hash.mapngữ nghĩa có thay đổi trong Ruby 2.x]


7
Matz thậm chí có biết liệu Hash.mapngữ nghĩa thay đổi trong Ruby 2.x không?
Andrew Grimm

1
Phương thức Hash # [] rất hữu ích, nhưng thật xấu xí. Có một phương pháp đẹp hơn để chuyển đổi các mảng thành băm theo cùng một cách không?
Martijn

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

Tôi không thích tác dụng phụ, nhưng +1 cho cách tiếp cận :) Có each_with_objecttrong Ruby 1.9 (IIRC) tránh việc truy cập trực tiếp vào tên và Map#mergecũng có thể hoạt động. Không chắc chắn làm thế nào các chi tiết phức tạp khác nhau.

1
Hàm băm ban đầu được sửa đổi - điều này ổn nếu hành vi được dự đoán nhưng có thể gây ra các vấn đề tế nhị nếu "bị lãng quên". Tôi thích giảm khả năng biến đổi đối tượng, nhưng nó có thể không phải lúc nào cũng thực tế. (Ruby hầu như không phải là ngôn ngữ "không có tác dụng phụ" ;-)

8

Hash.merge! là giải pháp sạch nhất

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

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

@MichieldeMare Tôi xin lỗi, tôi đã không kiểm tra điều này đủ kỹ lưỡng. Bạn đã đúng, khối cần lấy hai tham số. Đã sửa.

5

Sau khi thử nghiệm nó với RSpec như thế này:

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

Bạn có thể triển khai Hash # map_values ​​như sau:

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

Các chức năng sau đó có thể được sử dụng như thế này:

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

1

Nếu bạn tò mò biến thể inplace nào là nhanh nhất ở đây:

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
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.