Ruby 数组->哈希, 指定元素求和的问题

Y4m1n · 2018年05月19日 · 最后由 lithium4010 回复于 2018年05月23日 · 2178 次阅读

现有数组:

[["小黄", "2000"], ["小猪", "2500"], ["小猪", "2500"], ["小黄", "1500"]]

想要得到一个哈希,人=>薪资总额 效果如

{"小黄"=>3500,"小猪"=>5000}

请问要怎么破? 感谢!

我一般会先转化到一个中间结构,再做 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

group by , map , reduce values, to_h

楼上回答最简洁。遍历并解构数组,结果累加在 Hash 中。

zhandao 回复

纳尼?陈独秀你坐下,我李大钊表示不服

他有两行,而且 each 相当于没有返回值,接下来的操作还要调用 h 才能取结果,也就是三行

我的才一行,返回值就是结果,除非他改用 inject,不然我不会承认是在下输了

哈哈哈哈~

IChou 回复

哈哈好好好。 我说的所谓 “简洁” 是指理解上的方便啦。又是 group by 又是 transform values 乍一眼是看不出来什么意思哦~~

IChou 回复

你要的一行版

Hash.new(0).tap{|h| arr.each{|(k,v)| h[k] += v.to_i } }
zhandao 回复

一行就是简洁?你对简洁或者 Ruby 是不是有什么误解?

nouse 回复

我什么时候说一行就是简洁?你对我是不是有什么误解?

zhandao 回复

at 错楼了,可惜无法修改

一行版

h=Hash.new(0);arr.each{|(k,v)|h[k]+=v.to_i};h

想了一个无需默认 0 Hash 的一行版:

a.inject({}){|m, (k, v)| m.merge(k => v.to_i) {|k, old, new| old + new} }
arr.each_with_object(Hash.new(0)) { |v, h| h[v[0]] += v[1].to_i }

最短且最好理解的版本:

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 }
nouse 回复

并没有什么误解,只是故意抬一下杠

如果在一个多人协作的项目中看到一段代码洋洋洒洒 7、8 行,做的事情就是拼一个 hash,而且很有可能是一些 Hash/Array 的现有方法可以很容易完成的内容,一方面还是会觉得不够简洁,另一方面也就或多或少的体现了写代码的人对这些内部 API 不够熟悉吧

所以我才会在一开始就给了一个看似另类的解法,用了两个看上去不常用但其实很有用的方法,我是希望 LZ 能看了自己去查查 API,或许他这一查就查出新大陆呢,哈哈哈~~

写成一个表达式还有一个好处是可以接着链式调用去做其他处理,在我的印象里好像挺多人喜欢这么干的,虽然有时候确实会牺牲一些可读性

Anyway,开心就好

IChou 回复

如果在一个多人协作的项目中看到一段代码洋洋洒洒 7、8 行,做的事情就是拼一个 hash,而且很有可能是一些 Hash/Array 的现有方法可以很容易完成的内容,一方面还是会觉得不够简洁,另一方面也就或多或少的体现了写代码的人对这些内部 API 不够熟悉吧

我以前也是这样的,看见别人在方法最后的 if else 用了 return 我也会上去指出这是不简洁。不过我现在我宁可写代码的人多写几行,把工作完成,而不是浪费时间改写成看上去更 smart 的方法。虽然常见的说法是代码越长,bug 越多,但是更多情况是只要方法工作正常,使用者用下来没问题,10 行和 100 行并没有区别。

IChou 回复

可以再迭代一下,减少中间变量和嵌套层次,尤其是避免同名变量。个人认为你这种思路是最符合直觉的了。

arr.group_by(&:first).transform_values { |v| v.map(&:second).sum(&:to_i) }

#1 楼 @IChou 忘记 at 了

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
IChou 回复

干的漂亮,充分体现了 ruby 简洁高效的特点

21楼 已删除
zfjoy520 回复

朱老师 666,居然拿出了 benchmark

B 大说过:『成功人士才跑 benchmark』,我等屌丝写代码就是一把梭,哈哈哈~~~

记得之前看过的文章提过:

  1. 下标操作比 merge、update 等操作快
  2. each 应该是这些个枚举方法里面最快的,毕竟被用得最多

所以,@luikore 的应该是所有解法里面最快的(没有之一),我想不出还能怎么更快了,不服的同学可以再战!!

PS:(这个和主题无关,只是因为我喜欢 inject)虽然下标操作更快,但是在 inject 里面最好还是使用 merge、update,因为 inject 以 block 的最后一行作为返回值,用下标操作你就需要在最后一行显示的返回修改后的 hash,会拖得更慢,当然这时还不如直接用 each_with_object 好了

干 。。。 打脸了,突然想起还有个多行版本

h = Hash.new(0); arr.each{ |(k, v)| h[k] += v.to_i }; h

事实证明这个更快

nouse 回复

10 行比 100 行好一个数量级

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册