【问题标题】:Fast way to find duplicate in large array在大型数组中查找重复项的快速方法
【发布时间】:2018-08-16 02:28:47
【问题描述】:

我有一个包含 35k 个元素的数组。如何有效地找到重复项并返回这些重复项?

all = [*1..35000, 1]

This solution 工作:

all.select { |v| all.count(v) > 1 }

但这需要很长时间。

【问题讨论】:

  • "This solution 有效:[...] 但这需要很长时间。" – 你看到那个页面上的 cmets / 其他答案了吗?

标签: ruby


【解决方案1】:

您的代码需要一个 eon 来执行,因为它正在为每个元素执行 count,导致它的计算复杂度为 O(n2)。

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]

集合查找(在后台使用哈希实现)非常快。

请参阅 Set#add?Set#add

如果您想知道值在数组中出现至少两次的次数...

arr.each_with_object(Hash.new(0)) { |x,h| h[x] += 1 }.select { |_,v| v > 1 }
  #=> {1=>2, 34999=>2}

这使用了一个计数哈希1。当它采用 默认值 作为参数时,请参阅 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]

1. Ruby v2.7 给了我们方法Enumerable#tally,让我们可以写arr.tally.select { |_,v| v &gt; 1 }

【讨论】:

  • 我在写类似的东西。当涉及到大集合中元素的唯一性时,只需使用Set
【解决方案2】:

如果您使用的是 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(&amp;:itself) 分组元素出现的次数:

{
  1 => [1, 1],
  2 => [2],

  ...

  35000 => [35000]
}

那么我们把上面的哈希值转化为频率:

all.group_by(&amp;:itself).transform_values(&amp;: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

【讨论】:

  • 该死,这很聪明。与返回值匹配的小改进是 select { |_, freq| freq &gt; 1 }.keys 再次返回一个数组,而不是一个哈希。
  • 这里的目标是输出数组中的[1],所以就像Brian 所说,只需一步即可完成。
【解决方案3】:

Cary 的解决方案似乎是迄今为止最快的。这是我的:

large_array.sort.each_cons(2).with_object(Set.new) do |(e1, e2), acc|
  acc << e1 if e1 == e2
end.to_a

注意: 与 Cary 的解决方案和 juanitofatas 的解决方案不同,这可能很容易适应查找出现次数超过 N 的那些(只需将参数更改为 each_cons

另外,如果原始数组可能会发生变异,这会消耗最少的内存(sortsort!。)


基准测试:

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)

【讨论】:

  • 感谢基准测试,mudsie。它们都非常接近,如此接近以至于任何可能以不同的参数上升到顶部。
【解决方案4】:

很好的回复!

我的版本受Cary Swoveland 启发,但更冗长,只比Nondv 少一行,用select 而不是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

【讨论】:

  • 有趣的是selectselect! 快。
  • 其中一些方法输出重复出现的值的列表,而另一些方法输出一个哈希值,其中值作为键和计数。我在其中添加了 .keys 以比较苹果和苹果并获得了类似的基准,但这些测试中只有一个骗局。所以我将数组更改为arr = [*1..35000, *1..10000] 有趣的是,nondv1 在很多重复的情况下对我来说是表现最好的,它与.keys 转换关系不大。
【解决方案5】:

你不能自己数一数吗?

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) 更快)

【讨论】:

  • 这里的错字:counter.select! { |k, v| &gt; 1 } ;)
猜你喜欢
  • 1970-01-01
  • 2019-02-11
  • 1970-01-01
  • 1970-01-01
  • 2013-07-22
  • 2012-05-07
  • 2014-03-18
  • 2019-05-28
  • 2020-06-06
相关资源
最近更新 更多