虽然我喜欢 grep 解决方案的优雅并提醒(或教给我)我忘记(或完全忽略)的 Enumerable 中的方法,但它很慢,很慢,很慢。我 100% 同意创建 Array#mode 方法是一个好主意,但是 - 这是 Ruby,我们不需要作用于数组的函数库,我们可以创建一个 mixin,将必要的函数添加到 Array 类本身。
但是inject(Hash) 替代方案使用了一种我们也并不真正需要的排序:我们只想要出现次数最多的值。
这两种解决方案都没有解决模式可能不止一个值的可能性。也许这不是问题中的问题(无法判断)。不过,我想我想知道是否有平局,无论如何,我认为我们可以在性能上有所提高。
require 'benchmark'
class Array
def mode1
sort_by {|i| grep(i).length }.last
end
def mode2
freq = inject(Hash.new(0)) { |h,v| h[v] += 1; h }
sort_by { |v| freq[v] }.last
end
def mode3
freq = inject(Hash.new(0)) { |h,v| h[v] += 1; h }
max = freq.values.max # we're only interested in the key(s) with the highest frequency
freq.select { |k, f| f == max } # extract the keys that have the max frequency
end
end
arr = Array.new(1_000) { |i| rand(100) } # something to test with
Benchmark.bm(30) do |r|
res = {}
(1..3).each do |i|
m = "mode#{i}"
r.report(m) do
100.times do
res[m] = arr.send(m).inspect
end
end
end
res.each { |k, v| puts "%10s = %s" % [k, v] }
end
下面是示例运行的输出:
user system total real
mode1 34.375000 0.000000 34.375000 ( 34.393000)
mode2 0.359000 0.000000 0.359000 ( 0.359000)
mode3 0.219000 0.000000 0.219000 ( 0.219000)
mode1 = 41
mode2 = 41
mode3 = [[41, 17], [80, 17], [72, 17]]
“优化”模式 3 花费了前一个记录保持者的 60% 的时间。还要注意多个频率最高的条目。
几个月后,我注意到Nilesh's answer,它提供了这个:
def mode4
group_by{|i| i}.max{|x,y| x[1].length <=> y[1].length}[0]
end
它不适用于开箱即用的 1.8.6,因为该版本没有 Array#group_by。对于 Rails 开发人员,ActiveSupport 有它,尽管它似乎比上面的mode3 慢 2-3%。但是,使用(出色的)backports gem 可以产生 10-12% 的增益,并提供一大堆 1.8.7 和 1.9 的附加功能。
以上内容仅适用于 1.8.6 - 并且主要仅适用于安装在 Windows 上的情况。既然我已经安装了它,下面是您从 IronRuby 1.0(在 .NET 4.0 上)获得的内容:
========================== IronRuby =====================================
(iterations bumped to **1000**) user system total real
mode1 (I didn't bother :-))
mode2 4.265625 0.046875 4.312500 ( 4.203151)
mode3 0.828125 0.000000 0.828125 ( 0.781255)
mode4 1.203125 0.000000 1.203125 ( 1.062507)
因此,如果性能非常关键,请在您的 Ruby 版本和操作系统上对选项进行基准测试。 YMMV.