我有一个包含35k元素的数组。如何有效地找到重复项并返回这些重复项?
all = [*1..35000, 1]
This solution的工作原理:
all.select { |v| all.count(v) > 1 }
但是要花很长时间。
答案 0 :(得分:7)
如果您使用的是Ruby 2.4.0+,则可以使用group_by
+ Hash#transform_values
(适用于Ruby 2.4.0):
all.group_by(&:itself).transform_values(&:size).select { |_, freq| freq > 1 }
查看实际效果:
all.group_by(&:itself)
对元素出现的次数进行分组:
{
1 => [1, 1],
2 => [2],
...
35000 => [35000]
}
然后将上述哈希值转换为频率:
all.group_by(&:itself).transform_values(&:size)
:
{
1 => 2,
2 => 1,
...
35000 => 1
}
基准:
def measure_execution_time
started = Time.now
yield
Time.now - started
end
measure_execution_time do
all.select { |value| all.count(value) > 1 }
end
=> 7.235489
measure_execution_time do
all.group_by(&:itself).transform_values(&:size).select { |_, freq| freq > 1 }
end
=> 0.017887
答案 1 :(得分:7)
您的代码需要执行一个eon,因为它正在为每个元素执行count
,从而导致其计算复杂度为O(n 2 )。
arr = [*1..35000, 1, 34999]
如果您想知道哪些值至少两次出现在数组中...
require 'set'
uniq_set = Set.new
arr.each_with_object(Set.new) { |x,dup_set| uniq_set.add?(x) || dup_set.add(x) }.to_a
#=> [1, 34999]
设置查找(在后台使用哈希来实现)非常快。
如果您想知道值出现在数组中的次数至少出现两次...
arr.each_with_object(Hash.new(0)) { |x,h| h[x] += 1 }.select { |_,v| v > 1 }
#=> {1=>2, 34999=>2}
这使用了计数哈希。以默认值作为参数时,请参见Hash::new。
如果您想知道数组中至少出现两次的值的索引...
arr.each_with_index.
with_object({}) { |(x,i),h| (h[x] ||= []) << i }.
select { |_,v| v.size > 1 }
#=> {1=>[0, 35000], 34999=>[34998, 35001]}
当哈希h
还没有密钥x
时,
(h[x] ||= []) << i
#=> (h[x] = h[x] || []) << i
#=> (h[x] = nil || []) << i
#=> (h[x] = []) << i
#=> [] << i where [] is now h[x]
答案 2 :(得分:2)
到目前为止,Cary的解决方案似乎是最快的。这是我的:
large_array.sort.each_cons(2).with_object(Set.new) do |(e1, e2), acc|
acc << e1 if e1 == e2
end.to_a
NB:与Cary的解决方案不同,并且像juanitofatas的解决方案一样,可以很容易地采用它来查找出现次数超过N
的解决方案(只需将参数更改为each_cons
。
此外,如果原始数组可能会被修改,则它将消耗最少的内存(sort
→sort!
)。
基准:
require 'set'
require 'benchmark'
def mudsie arr
arr.sort.each_cons(2).each_with_object(Set.new) do |(e1, e2), acc|
acc << e1 if e1 == e2
end.to_a
end
def cary arr
uniq_set = Set.new
arr.each_with_object(Set.new) do |x,dup_set|
uniq_set.add?(x) || dup_set.add(x)
end.to_a
end
def juanitofatas arr
arr.group_by(&:itself).transform_values(&:size).select do |_, freq|
freq > 1
end.keys
end
arr = [*(1..35000)]
arr = (arr + arr.sample(500)).shuffle
n = 500
Benchmark.bm do |x|
x.report("juanitofatas") { n.times { juanitofatas arr } }
x.report("cary") { n.times { cary arr } }
x.report("mudsie") { n.times { mudsie arr } }
end
user system total real
juanitofatas 4.321030 0.000000 4.321030 ( 4.321232)
cary 3.229409 0.032003 3.261412 ( 3.261995)
mudsie 3.798093 0.000000 3.798093 ( 3.798141)
答案 3 :(得分:1)
你不能自己算一下吗?
counter = Hash.new(0)
array.each { |e| counter[e] += 1 }
counter.select! { |k, v| v > 1 }
counter.keys
# OR
array.each_with_object(Hash.new(0)) { |e, h| h[e] += 1 }
.select! { |k, v| v > 1 }
.keys
它具有O(n)的复杂度,我认为您不能比这快(我的意思是比O(n)快)
答案 4 :(得分:0)
好评!
我的版本受到Cary Swoveland的启发,但更冗长,比select
少Nondv而不是select!
一行,这似乎更快:
def igian(arr)
h = Hash.new(0)
arr.each { |a| h[a] += 1 }
h.select { |_k, v| v > 1 }
end
对所有回复进行基准测试,灵感来自mudasobwa。
n = 500
user system total real
cary1 5.040000 0.200000 5.240000 ( 5.248103)
cary2 4.700000 0.190000 4.890000 ( 4.911883)
juanito 7.430000 0.030000 7.460000 ( 7.483123)
mudsie 5.430000 0.020000 5.450000 ( 5.460839)
nondv1 4.720000 0.190000 4.910000 ( 4.924792)
nondv2 5.110000 0.190000 5.300000 ( 5.317148)
igian 4.310000 0.190000 4.500000 ( 4.522211)
n = 1000
user system total real
cary1 10.460000 0.410000 10.870000 ( 10.900927)
cary2 9.550000 0.410000 9.960000 ( 9.989021)
juanito 15.370000 0.160000 15.530000 ( 15.569288)
mudsie 10.920000 0.020000 10.940000 ( 10.972357)
nondv1 9.590000 0.410000 10.000000 ( 10.017669)
nondv2 10.340000 0.410000 10.750000 ( 10.774538)
igian 8.790000 0.400000 9.190000 ( 9.213292)
以下是基准代码:
require 'benchmark'
require 'set'
arr = [*1..35000, 1]
def cary1(arr)
uniq_set = Set.new
arr.each_with_object(Set.new) { |x,dup_set| uniq_set.add?(x) || dup_set.add(x) }.to_a
end
def cary2(arr)
arr.each_with_object(Hash.new(0)) { |x,h| h[x] += 1 }.select { |_,v| v > 1 }
end
def juanito(arr)
arr.group_by(&:itself).transform_values(&:size).select { |_,v| v > 1 }
end
def mudsie(arr)
arr.sort.each_cons(2).each_with_object(Set.new) do |(e1, e2), acc|
acc << e1 if e1 == e2
end.to_a
end
def nondv1(arr)
counter = Hash.new(0)
arr.each { |e| counter[e] += 1 }
counter.select! { |k, v| v > 1 }
counter.keys
end
def nondv2(arr)
arr.each_with_object(Hash.new(0)) { |e, h| h[e] += 1 }
.select! { |k, v| v > 1 }
.keys
end
def igian(arr)
h = Hash.new(0)
arr.each { |a| h[a] += 1 }
h.select { |_k, v| v > 1 }
end
n = 500 #1000
Benchmark.bm do |x|
x.report("cary1") { n.times { cary1 arr } }
x.report("cary2") { n.times { cary2 arr } }
x.report("juanito") { n.times { juanito arr } }
x.report("mudsie") { n.times { mudsie arr } }
x.report("nondv1") { n.times { nondv1 arr } }
x.report("nondv2") { n.times { nondv2 arr } }
x.report("igian") { n.times { igian arr } }
end