【发布时间】:2011-07-25 03:38:25
【问题描述】:
arr = [1,2,1,3,5,2,4]
如何通过排序按组值对数组进行计数?我需要以下输出:
x[1] = 2
x[2] = 2
x[3] = 1
x[4] = 1
x[5] = 1
【问题讨论】:
-
为什么没有循环?某处会出现循环。
标签: ruby
arr = [1,2,1,3,5,2,4]
如何通过排序按组值对数组进行计数?我需要以下输出:
x[1] = 2
x[2] = 2
x[3] = 1
x[4] = 1
x[5] = 1
【问题讨论】:
标签: ruby
x = arr.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h }
【讨论】:
inject 将一个累加器“注入”到一个 Enumerable 中,在我们的例子中它是一个默认值为 0 的 Hash。在每次迭代中,我们使用当前元素的键 (e) 将值加一。最后我们返回累加器。 ruby-doc.org/core/classes/Enumerable.html#M001494
.each_with_object 而不是 inject。请参阅下面@sawa 的答案。
我相信有更好的方法,
>> 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 值分配给哈希。
【讨论】:
sort 不必在group_by 之前。 arr.group_by {...} 会做同样的事情
[2,1].group_by {|x|x} #=> {2=>[2], 1=>[1]}kurumi,有什么更好的方法?
group_by 返回一个没有订单的Hash。 Hash 中的条目的迭代顺序是不可预测的。
group_by preserves order,至少在 MRI 1.9+ 中。 AFAIK,它没有记录,但应该是,因为它是规范的一部分。
Hash。所以它与[2,1].group_by {|x|x} #=> {2=>[2], 1=>[1]} 没有任何关系
x = Hash[arr.uniq.map{ |i| [i, arr.count(i)] }]
最新的 Ruby 有 to_h 方法:
x = arr.uniq.map{ |i| [i, arr.count(i)] }.to_h
【讨论】:
count 方法。也许使用内置方法有其优势。 :)
count 的版本,但认为它不能很好地适应数组长度,所以用我当前的答案替换它。你能用更大的数组运行你的基准测试并再次比较吗?
O(n2) 而言,它在小型阵列的基准测试中更快,但在大型阵列中速度会非常慢。我的错是我在百万循环台上测试现有阵列 - 所以它快了 20%。
应该这样做
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}
【讨论】:
仅在 ruby 1.9 下可用
与Michael's answer基本相同,只是方式略短:
x = arr.each_with_object(Hash.new(0)) {|e, h| h[e] += 1}
在类似的情况下,
Array、Hash、String,您可以使用each_with_object,如上例所示。当起始元素是Numeric等不可变对象时,您必须使用inject,如下所示。
sum = (1..10).inject(0) {|sum, n| sum + n} # => 55
【讨论】:
each_with_object 以避免h[e] += 1 ; h
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}
【讨论】:
每当你发现有人断言某事在这种类型的原始程序中是最快的,我总是觉得确认这一点很有趣,因为没有确认我们大多数人实际上只是在猜测。所以我把这里所有的方法都拿来做基准测试。
我从需要按计数分组的网页中提取了一组 120 个链接,并使用 seconds = Benchmark.realtime do 循环实现了所有这些链接,并且得到了所有时间。
假设 links 是我需要计算的数组的名称:
#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}
【讨论】:
为了记录,我最近读到了Object#taphere。我的解决方案是:
Hash.new(0).tap{|h| arr.each{|i| h[i] += 1}}
#tap 方法将调用者传递给块,然后返回它。当您必须增量构建数组/哈希时,这非常方便。
【讨论】:
另一种 - 与其他方法相似 - 方法:
result=Hash[arr.group_by{|x|x}.map{|k,v| [k,v.size]}]
result[1]=2 ... 访问。【讨论】:
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 }
【讨论】:
tally 不接受 2.7 中的块 docs.ruby-lang.org/en/2.7.0/Enumerable.html#method-i-tally
arr.group_by(&:itself).transform_values(&:size)
#=> {1=>2, 2=>2, 3=>1, 5=>1, 4=>1}
【讨论】: