Hash#merge被广泛用来在ruby中模拟keyword arguments,大家已经很熟练了:

  1. def func(given_opt)
  2.    opt={:opt_1=>"v1",:opt_2=>"v2"}
  3.    opt=opt.merge(given_opt)
  4.    #blahblah...
  5. end

这样given_opt就能把opt里面的内容进行扩充或覆盖,rails framework里面那些help方法广泛采用了这个写法。

再考虑下面这个很平常的需求:一堆hash,要求合并,合并规则是相同key的值相加,该怎么实现呢?

  1. data=[{:a=>1,:b=>2},{:a=>3,:c=>4},{:b=>5,:c=>6}]

要求对data处理后结果为

  1. {:a=>4,:b=>7,:c=>10}

一般思路为

  1. data=[{:a=>1,:b=>2},{:a=>3,:c=>4},{:b=>5,:c=>6}]
  2. rst_hash=Hash.new(0)
  3. data.each do |a_hash|
  4.   a_hash.each do |k,v|
  5.     rst_hash[k]+=v
  6.   end
  7. end

倒也清晰,只是看着罗嗦,于是想能不能把merge中的覆盖效果改成其他(比如相加)效果,比如

  1. a={:a=>1,:b=>2}
  2. b={:a=>3,:c=>4}

要求a._merge_mod_(b)的结果为{:a=>4(1+3),:b=>2,:c=>4}该怎么办呢?其实只要给merge传递一个block就可以达到这个效果了:

  1. a.merge(b){ |key,old_value,new_value| old_value+new_value}

所以,上面那个需求就有了更简单方法了:

  1. data=[{:a=>1,:b=>2},{:a=>3,:c=>4},{:b=>5,:c=>6}]
  2. data.inject(Hash.new(0)){|t,i| t.merge(i){ |k,o,n| o+n}}

一行代码足矣 :)