【发布时间】:2011-03-04 02:55:37
【问题描述】:
我想更改哈希中的每个值,以便在值之前和之后添加'%'
{ :a=>'a' , :b=>'b' }
必须改为
{ :a=>'%a%' , :b=>'%b%' }
最好的方法是什么?
【问题讨论】:
-
请说明您是想改变原始字符串对象,还是改变原来的 has,或者什么都不改变。
我想更改哈希中的每个值,以便在值之前和之后添加'%'
{ :a=>'a' , :b=>'b' }
必须改为
{ :a=>'%a%' , :b=>'%b%' }
最好的方法是什么?
【问题讨论】:
在 Ruby 2.1 及更高版本中,您可以这样做
{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h
【讨论】:
#transform_values! 会更容易,正如 sschmeck (stackoverflow.com/a/41508214/6451879) 所指出的那样。
如果您希望实际的字符串本身在适当的位置发生变异(可能并且希望影响对相同字符串对象的其他引用):
# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |_,str| str.gsub! /^|$/, '%' }
my_hash.each{ |_,str| str.replace "%#{str}%" }
如果您希望哈希更改到位,但又不想影响字符串(您希望它获取新字符串):
# 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 }
如果你想要一个新的哈希:
# 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}%"] } ]
【讨论】:
Hash.[] 不接受数组对数组,它需要偶数个直接参数(因此前面有 splat)。
Hash#each 生成块的键和值。在这种情况下,我不关心密钥,所以我没有给它命名任何有用的东西。变量名可以以下划线开头,实际上可能只是一个下划线。这样做并没有性能上的好处,它只是一个微妙的自我记录说明,我没有对第一个块值做任何事情。
my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h },必须从块中返回哈希
Ruby 2.4 引入了方法Hash#transform_values!,您可以使用它。
{ :a=>'a' , :b=>'b' }.transform_values! { |v| "%#{v}%" }
# => {:a=>"%a%", :b=>"%b%"}
【讨论】:
Hash#transform_values(不带刘海),它不会修改接收者。否则,一个很好的答案,谢谢!
就地修改哈希值的最佳方法是
hash.update(hash){ |_,v| "%#{v}%" }
更少的代码和清晰的意图。也更快,因为除了必须更改的值之外没有分配新对象。
【讨论】:
gsub!之类的mutators。
update 比merge! 更能传达意图。我认为这是最好的答案。
k,请改用_。
一个更易读的,map 它是一个单元素哈希数组,reduce 是 merge
the_hash.map{ |key,value| {key => "%#{value}%"} }.reduce(:merge)
【讨论】:
Hash[the_hash.map { |key,value| [key, "%#{value}%"] }]
Array(对于map)然后是Hash。然后,reduce 操作的每一步都会复制“memo”Hash 并将新的键值对添加到其中。至少在reduce 中使用:merge! 将最终的Hash 修改到位。最后,您不是在修改现有对象的值,而是创建了一个新对象,这不是问题所要求的。
the_hash 为空,则返回nil
这个任务有一个新的'Rails way'方法:) http://api.rubyonrails.org/classes/Hash.html#method-i-transform_values
【讨论】:
Hash#transform_values。这应该是从现在开始的方式。
一种不会给原始方法带来副作用的方法:
h = {:a => 'a', :b => 'b'}
h2 = Hash[h.map {|k,v| [k, '%' + v + '%']}]
Hash#map 也可能是一个有趣的阅读,因为它解释了为什么 Hash.map 不返回哈希(这就是为什么 [key,value] 对的结果数组被转换为新的哈希)并提供了替代方法相同的一般模式。
编码愉快。
[免责声明:我不确定Hash.map 的语义是否在 Ruby 2.x 中发生变化]
【讨论】:
Hash.map 语义在 Ruby 2.x 中是否发生变化?
my_hash.each do |key, value|
my_hash[key] = "%#{value}%"
end
【讨论】:
each_with_object 可以避免直接访问名称,Map#merge 也可以工作。不确定复杂的细节有何不同。
Hash.merge!是最干净的解决方案
o = { a: 'a', b: 'b' }
o.merge!(o) { |key, value| "%#{ value }%" }
puts o.inspect
> { :a => "%a%", :b => "%b%" }
【讨论】:
像这样用 RSpec 测试后:
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:
class Hash
def map_values
Hash[map { |k, v| [k, yield(v)] }]
end
end
这个函数可以这样使用:
{:a=>'a' , :b=>'b'}.map_values { |v| "%#{v}%" }
# {:a=>"%a%", :b=>"%b%"}
【讨论】:
如果您想知道哪个 inplace 变体是最快的,那就是:
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
【讨论】: