现有数组:
[["小黄", "2000"], ["小猪", "2500"], ["小猪", "2500"], ["小黄", "1500"]]
想要得到一个哈希,人=>薪资总额 效果如
{"小黄"=>3500,"小猪"=>5000}
请问要怎么破? 感谢!
arr = [["小黄", "2000"], ["小猪", "2500"], ["小猪", "2500"], ["小黄", "1500"]]
arr.group_by(&:first).transform_values { |v| v.sum { |_, v| v.to_i } }
答案给你,不过这个要 2.4 以后的 ruby 才能这么用
至于他是怎么工作的,还是期待你自己解读一下
我一般会先转化到一个中间结构,再做 sum 运算。
# raw_data = [
# [:a, 1],
# [:a, 2],
# [:b, 2]
# ]
# raw_data.to_combined_h
# => { a: [1, 2], b: 2 }
# todo nested array bug
def to_combined_h
hash = {}
self.each { |x, y| hash[x] = hash[x] ? Array(hash[x]) << y : y }
hash
end
https://github.com/qinmingyuan/rails_com/blob/master/lib/rails_com/core_ext/array.rb
纳尼?陈独秀你坐下,我李大钊表示不服
他有两行,而且 each 相当于没有返回值,接下来的操作还要调用 h 才能取结果,也就是三行
我的才一行,返回值就是结果,除非他改用 inject,不然我不会承认是在下输了
哈哈哈哈~
哈哈好好好。 我说的所谓“简洁”是指理解上的方便啦。又是 group by 又是 transform values 乍一眼是看不出来什么意思哦~~
你要的一行版
Hash.new(0).tap{|h| arr.each{|(k,v)| h[k] += v.to_i } }
想了一个无需默认 0 Hash 的一行版:
a.inject({}){|m, (k, v)| m.merge(k => v.to_i) {|k, old, new| old + new} }
最短且最好理解的版本:
arr.inject({}) {|r, (k,v)| r.merge({k => v.to_i + r[k].to_i}) }
or
arr.inject(Hash.new(0)) {|r, (k,v)| r[k] += v.to_i; r }
并没有什么误解,只是故意抬一下杠
如果在一个多人协作的项目中看到一段代码洋洋洒洒 7、8 行,做的事情就是拼一个 hash,而且很有可能是一些 Hash/Array 的现有方法可以很容易完成的内容,一方面还是会觉得不够简洁,另一方面也就或多或少的体现了写代码的人对这些内部 API 不够熟悉吧
所以我才会在一开始就给了一个看似另类的解法,用了两个看上去不常用但其实很有用的方法,我是希望 LZ 能看了自己去查查 API,或许他这一查就查出新大陆呢,哈哈哈~~
写成一个表达式还有一个好处是可以接着链式调用去做其他处理,在我的印象里好像挺多人喜欢这么干的,虽然有时候确实会牺牲一些可读性
Anyway,开心就好
如果在一个多人协作的项目中看到一段代码洋洋洒洒 7、8 行,做的事情就是拼一个 hash,而且很有可能是一些 Hash/Array 的现有方法可以很容易完成的内容,一方面还是会觉得不够简洁,另一方面也就或多或少的体现了写代码的人对这些内部 API 不够熟悉吧
我以前也是这样的,看见别人在方法最后的 if else 用了 return 我也会上去指出这是不简洁。不过我现在我宁可写代码的人多写几行,把工作完成,而不是浪费时间改写成看上去更 smart 的方法。虽然常见的说法是代码越长,bug 越多,但是更多情况是只要方法工作正常,使用者用下来没问题,10 行和 100 行并没有区别。
require 'benchmark/ips'
arr = [["小黄", "2000"], ["小猪", "2500"], ["小猪", "2500"], ["小黄", "1500"]]
Benchmark.ips do |x|
x.report('luikore') { Hash.new(0).tap { |h| arr.each { |(k, v)| h[k] += v.to_i } } }
x.report('angelfan') { arr.each_with_object(Hash.new(0)) { |v, h| h[v[0]] += v[1].to_i } }
x.report('swordray') { arr.group_by(&:first).transform_values { |v| v.map(&:last).sum(&:to_i) } }
x.report('zfjoy520') { arr.each_with_object(Hash.new(0)) { |(k, v), r| r[k] += v.to_i } }
x.compare!
end
^_^,,luikore 的 Hash.new(0).tap { |h| arr.each { |(k, v)| h[k] += v.to_i } } 完胜
Warming up --------------------------------------
luikore 23.217k i/100ms
angelfan 22.538k i/100ms
swordray 16.002k i/100ms
zfjoy520 21.387k i/100ms
Calculating -------------------------------------
luikore 318.464k (± 5.9%) i/s - 1.602M in 5.049108s
angelfan 255.619k (±13.8%) i/s - 1.262M in 5.063771s
swordray 188.619k (± 9.6%) i/s - 944.118k in 5.083054s
zfjoy520 266.406k (±11.2%) i/s - 1.326M in 5.056312s
Comparison:
luikore: 318464.1 i/s
zfjoy520: 266405.9 i/s - 1.20x slower
angelfan: 255619.2 i/s - 1.25x slower
swordray: 188619.3 i/s - 1.69x slower
朱老师 666,居然拿出了 benchmark
B 大说过:『成功人士才跑 benchmark』,我等屌丝写代码就是一把梭,哈哈哈~~~
记得之前看过的文章提过:
所以,@luikore 的应该是所有解法里面最快的(没有之一),我想不出还能怎么更快了,不服的同学可以再战!!
PS:(这个和主题无关,只是因为我喜欢 inject)虽然下标操作更快,但是在 inject 里面最好还是使用 merge、update,因为 inject 以 block 的最后一行作为返回值,用下标操作你就需要在最后一行显示的返回修改后的 hash,会拖得更慢,当然这时还不如直接用 each_with_object 好了
文章在这里,还好我有 Pocket,还能找出来
http://stackoverflow.com/questions/3230863/ruby-rails-inject-on-hashes-good-style
干。。。打脸了,突然想起还有个多行版本
h = Hash.new(0); arr.each{ |(k, v)| h[k] += v.to_i }; h
事实证明这个更快