哪种解决方案性能最好,为啥要在复杂列表中查找重复项的数量?
Posted
技术标签:
【中文标题】哪种解决方案性能最好,为啥要在复杂列表中查找重复项的数量?【英文标题】:which solution is the most performing and why in order to find the number of duplicates in a complex list?哪种解决方案性能最好,为什么要在复杂列表中查找重复项的数量? 【发布时间】:2021-12-04 06:23:09 【问题描述】:我有以下数组:
a = [1, 1, 1, 1, 3]
b = [2, 3, 2, 3, 3]
c = [1, 1, 1, 1, 3]
我的目标是计算每列的额外重复次数。 在这种情况下意味着 [1,2,1] 出现两次,意味着 1 个重复,对于 [1,3,1] 也是如此 所以总共重复的数量是2,一次用于[1,2,1],一次用于[1,3,1]。 我开发了以下 2 种解决方案,但老实说,我不知道哪一种效果最好以及为什么:
解决方案 1:
sum = 0
zip = a.zip(b, c)
zip.group_by |e| e
.select |_, value| value.size > 1
.each_value |value| sum += (value.size - 1)
return sum
解决方案 2:
zip = a.zip(b, c)
hash = Hash.new(0)
zip.each |e| hash.store(e, hash[e]+1)
hash.each|e, _| hash[e] -= 1
return hash.sum |e, _| hash[e]
提前致谢
【问题讨论】:
1 3 1 不是重复的。再次检查。您可能提供了错误的数据。 @Rajagopalan 抱歉,小错字,非常感谢! 您不妨计算h = [a, b, c].transpose.each_with_object(Hash.new(0)) |col,h| h[col] += 1 #=> [1, 2, 1]=>2, [1, 3, 1]=>2, [3, 3, 3]=>1
。关于Hash::new带参数(默认值,此处为0)且无块的使用,此计算等效于h = [a, b, c].transpose.each_with_object() |col,h| h[col] = 0 unless h.key?(col); h[col] += 1 #=> [1, 2, 1]=>2, [1, 3, 1]=>2, [3, 3, 3]=>1
。
您为什么不使用#uniq
来寻找不同之处? (zip.count - zip.uniq.count # => 2
)
【参考方案1】:
说明基准测试:
require 'benchmark'
v1 = [1, 1, 1, 1]
v2 = [2, 3, 2, 3]
v3 = [1, 1, 1, 1 ]
def sol_1(a,b,c)
sum = 0
zip = a.zip(b, c)
zip.group_by |e| e
.select |_, value| value.size > 1
.each_value |value| sum += (value.size - 1)
return sum
end
def sol_2(a,b,c)
zip = a.zip(b, c)
hash = Hash.new(0)
zip.each |e| hash.store(e, hash[e]+1)
hash.each|e, _| hash[e] -= 1
return hash.sum |e, _| hash[e]
end
n=1_000
Benchmark.bmbm do |x|
x.report("sol_1")n.timessol_1(v1, v2, v3)
x.report("sol_2")n.timessol_2(v1, v2, v3)
end
结果:
Rehearsal -----------------------------------------
sol_1 0.011076 0.000000 0.011076 ( 0.011091)
sol_2 0.012276 0.000000 0.012276 ( 0.012355)
-------------------------------- total: 0.023352sec
user system total real
sol_1 0.007206 0.000000 0.007206 ( 0.007212)
sol_2 0.011452 0.000000 0.011452 ( 0.011453)
【讨论】:
竖起这个问题,因为它显示了基准测试,最终指向了解决方案!如果我没有选择您的答案作为解决方案,我很抱歉,原因是下面的解释确切地解释了为什么我们需要进行基准测试以找出复杂性【参考方案2】:因此,只需阅读它,这两种解决方案的方法就非常相似。虽然我不是 100% 确定您所说的 most performing
是什么意思,但我猜您的意思是这两种解决方案的计算复杂度 - 因此大输入的计算成本。当有很多列时,解决方案中唯一需要时间的元素是遍历列数组 - 相比之下,其他所有内容都需要很少的时间。
因此,在第一个解决方案中,您将迭代 3 次 - 一次对列进行分组,第二次选择具有重复项的列,然后第三次计算重复次数(但是,在最坏的情况下,您迭代的数组最多有 N/2 个元素)。因此,总共有 2.5 次迭代列数组。
在第二个解决方案中,您还要迭代 3 次。首先,在列数组上计算它们出现的次数,然后在结果(在最坏的情况下具有相同数量的元素)上从每个数字中减去一个,最后对数字求和 - 这大约有 3 次迭代.
因此,第一个解决方案可能性能稍高一些 - 但是在处理复杂性时,我们会着眼于忽略它前面的数字的函数类型 - 在这种情况下,两个解决方案都是线性的。此外,在 ruby 中,不同的方法以不同的方式进行了优化。因此,确定哪一个性能更高的唯一希望是使用基准测试 - 对(相同的)10000 列重复这些算法 100 次,第一个解决方案需要 10.5 秒,第二个解决方案需要 18 秒。
【讨论】:
这正是我正在寻找的解释。在我的脑海中,我试图通过检查“zip”或“group_by”函数在哪里执行(迭代输入或......?)来找出计算复杂性。因此,很难找出每个操作在 Big O 方面花费了多少,然后估计整个方法的复杂性。在这种情况下,我从您的回答中了解到的一件事是,找出哪种解决方案性能更高的最佳方法是进行基准测试。感谢您的回答!【参考方案3】:这是@steenslag 基准测试的稍微快一点 (20%) 的解决方案:
require 'matrix'
def sol_3(matrix)
Matrix.
columns(matrix).
to_a.
each_with_object() |e, a|
digest = e.hash
a[digest] = a[digest].nil? ? 1 : a[digest] + 1
.sum |_, v| v > 1 ? 1 : 0
end
user system total real
sol_1 0.006908 0.000008 0.006916 ( 0.006917)
sol_2 0.011866 0.000018 0.011884 ( 0.011902)
sol_3 0.005532 0.000008 0.005540 ( 0.005555)
完整脚本:https://gist.github.com/jaredbeck/edc708df10fcc0267db80bf1c31c8298
【讨论】:
以上是关于哪种解决方案性能最好,为啥要在复杂列表中查找重复项的数量?的主要内容,如果未能解决你的问题,请参考以下文章