快速查找大型数组中的重复项的方法

时间:2018-08-16 02:28:47

标签: ruby

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

all = [*1..35000, 1]

This solution的工作原理:

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

但是要花很长时间。

5 个答案:

答案 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]

设置查找(在后台使用哈希来实现)非常快。

请参见Set#add?Set#add

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

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

此外,如果原始数组可能会被修改,则它将消耗最少的内存(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)

答案 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的启发,但更冗长,比selectNondv而不是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