如何在不使用循环的情况下按数组进行分组

时间:2011-03-29 09:55:37

标签: ruby

arr = [1,2,1,3,5,2,4]

如何通过排序按组值计算数组?我需要以下输出:

x[1] = 2  
x[2] = 2  
x[3] = 1  
x[4] = 1  
x[5] = 1

11 个答案:

答案 0 :(得分:106)

x = arr.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h }

答案 1 :(得分:31)

仅适用于ruby 1.9

基本上与Michael's answer相同,但稍微短一些:

x = arr.each_with_object(Hash.new(0)) {|e, h| h[e] += 1}

在类似的情况下,

  • 如果起始元素是可变对象,例如ArrayHashString,则可以使用each_with_object,如上面的案例。
  • 如果起始元素是不可变对象,例如Numeric,则必须使用inject,如下所示。

    sum = (1..10).inject(0) {|sum, n| sum + n} # => 55

答案 2 :(得分:13)

另一个 - 与其他人相似 - 接近:

result=Hash[arr.group_by{|x|x}.map{|k,v| [k,v.size]}]
  1. 按每个元素的值分组。
  2. 将分组映射到 [value,counter] 对的数组。
  3. 将巴黎数组转换为哈希中的键值,即可通过result[1]=2 ...访问。

答案 3 :(得分:10)

x = Hash[arr.uniq.map{ |i| [i, arr.count(i)] }]

答案 4 :(得分:9)

每当你发现有人声称某种东西在这种原始程序中是最快的时候,我总是觉得有趣的是要确认,因为没有确认我们大多数人真的只是在猜测。所以我在这里采用了所有方法并对它们进行了基准测试。

我从一个网页中提取了120个链接的数组,我需要按计数进行分组,并使用秒= Benchmark.realtime do循环实现所有这些,并且一直都有。

假设链接是我需要计算的数组的名称:

#0.00077
seconds = Benchmark.realtime do
  counted_links = {}
  links.each { |e| counted_links[e] = links.count(e) if counted_links[e].nil?}
end
seconds

#0.000232
seconds = Benchmark.realtime do
  counted_links = {}
  links.sort.group_by {|x|x}.each{|x,y| counted_links[x] = y.size}
end

#0.00076
seconds = Benchmark.realtime do 
  Hash[links.uniq.map{ |i| [i, links.count(i)] }]
end

#0.000107 
seconds = Benchmark.realtime do 
  links.inject(Hash.new(0)) {|h, v| h[v] += 1; h}
end

#0.000109
seconds = Benchmark.realtime do 
  links.each_with_object(Hash.new(0)) {|e, h| h[e] += 1}
end

#0.000143
seconds = Benchmark.realtime do 
  links.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h }
end

然后有点红宝石来找出答案:

times = [0.00077, 0.000232, 0.00076, 0.000107, 0.000109, 0.000143].min
==> 0.000107

所以实际上最快的方法,当然是ymmv:

links.inject(Hash.new(0)) {|h, v| h[v] += 1; h}

答案 5 :(得分:8)

arr.group_by(&:itself).transform_values(&:size)
#=> {1=>2, 2=>2, 3=>1, 5=>1, 4=>1}

答案 6 :(得分:5)

我相信有更好的方法,

>> arr.sort.group_by {|x|x}.each{|x,y| print "#{x} #{y.size}\n"}
1 2
2 2
3 1
4 1
5 1

根据需要将x和y值分配给哈希值。

答案 7 :(得分:5)

仅供记录,我最近阅读了Object#tap here。我的解决方案是:

Hash.new(0).tap{|h| arr.each{|i| h[i] += 1}}

#tap方法将调用者传递给块然后返回它。当你必须逐步构建数组/散列时,这非常方便。

答案 8 :(得分:4)

这应该这样做

arr = [1,2,1,3,5,2,4]

puts arr.inject(Hash.new(0)) {|h, v| h[v] += 1; h}
#=> {1=>2, 2=>2, 3=>1, 5=>1, 4=>1}

答案 9 :(得分:4)

ruby 2.7 => Enumerable#tally中有一个简短的版本。

[1,2,1,3,5,2,4].tally  #=> { 1=>2, 2=>2, 3=>1, 5=>1, 4=>1 }

# Other possible usage

(1..6).tally { |i| i%3 }   #=> { 0=>2, 1=>2, 2=>2 }

答案 10 :(得分:1)

arr = [1,2,1,3,5,2,4]
r = {}
arr.each { |e| r[e] = arr.count(e) if r[e].nil?}

输出

p r
#==> {1=>2, 2=>2, 3=>1, 5=>1, 4=>1}