【问题标题】:How to find which items in a MASSIVE array appear more than once?如何查找 MASSIVE 数组中的哪些项目多次出现?
【发布时间】:2016-12-26 12:45:00
【问题描述】:

这是一个非常简单的问题;哪些项目多次出现在列表中?

array = ["mike", "mike", "mike", "john", "john", "peter", "clark"]

正确答案是["mike", "john"]

似乎我们可以这样做:

array.select{ |e| ary.count(e) > 1 }.uniq

问题解决了。可是等等!如果数组真的很大怎么办:

1_000_000.times { array.concat("1234567890abcdefghijklmnopqrstuvwxyz".split('')) }

碰巧我需要弄清楚如何在合理的时间内做到这一点。我们谈论的是数百万条记录。

不管怎样,这个庞大的阵列实际上是 10-20 个较小阵列的总和。如果比较这些更容易,请告诉我 - 我很难过。

我们说的是每个文件 10,000 到 10,000,000 行,数百个文件。

【问题讨论】:

  • 有点头脑风暴,但是如果我们将这些值输入到哈希表中,那么如果它们开始冲突,我们会假设它是重复的?
  • 头脑风暴继续:假设 1,000,000,000 个条目的 32 位散列将需要一个最多 4 GB 的表。通过使用每个位代表一个占用的散列的位集,这可以归结为 116 兆字节。这种方法需要两次遍历数据以过滤掉潜在的重复项。
  • 什么是您的用例可接受的运行时?不确定我是否理解较小数组的比较注意:如果您确定每个数组包含未出现在其他数组中的项目,这只会返回相同的结果。
  • 我无法忘记你的头像。
  • @CarySwoveland 嘿,Cary,我前段时间给你发了电子邮件,寻求 Ruby 的帮助。我想象我的化身唱男高音。你怎么看?

标签: arrays ruby performance sorting unique


【解决方案1】:

做类似的事情

items = 30_000_000

array = items.times.map do
  rand(10_000_000)
end

puts "Done with seeding"
puts
puts "Checking what items appear more than once. Size: #{array.size}"
puts

t1 = Time.now
def more_than_once(array)
  counts = Hash.new(0)
  array.each do |item|
    counts[item] += 1
  end

  counts.select do |_, count|
    count > 1
  end.keys
end

res = more_than_once(array)
t2 = Time.now


p res.size
puts "Took #{t2 - t1}"

为你工作?

在我的机器上持续时间约为 40 秒。

【讨论】:

  • 喜欢默认的哈希初始化counts = Hash.new { |hash, key| hash[key] = 0 }
  • 您可以简化为counts = Hash.new(0)
  • 并且可以使用 each_with_object 进行计数:counts = array.each_with_object(Hash.new) { |item, akku| akku[项目] += 1 }
  • 您在上次评论中忘记了(0) in Hash.new(0)
  • 啊。再次感谢。
【解决方案2】:

这里还有另外两个解决方案,它们与@Pascal 的方法进行了基准比较。

使用集合

require 'set'

def multi_set(arr)
  s1 = Set.new
  arr.each_with_object(Set.new) { |e, smulti| smulti.add(e) unless s1.add?(e) }.to_a
end

arr = ["mike", "mike", "mike", "john", "john", "peter", "clark"]    
multi(arr)
  #=> ["mike", "john"]

s1 正在构建以包含arr 的所有不同元素。如果s1 已经包含e,则s1.add?(e) 返回nil,在这种情况下,如果smulti 尚未包含该元素,则将e 添加到smulti。 (参见Set#add?。)smulti 由方法返回。

使用Array#difference

Array#difference 是我将 proposed 添加到 Ruby 核心的方法。另见我的回答here

class Array
  def difference(other)
    h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
    reject { |e| h[e] > 0 && h[e] -= 1 }
  end
end

def multi_difference(arr)
  arr.difference(arr.uniq).uniq
end

基准测试

def more_than_once(arr)
  counts = Hash.new { |hash, key| hash[key] = 0 }
  arr.each do |item|
    counts[item] += 1
  end
  counts.select do |_, count|
    count > 1
  end.keys
end

require 'fruity'

items = 30_000_000
arr = items.times.map { rand 10_000_000 }

compare do 
  Pascal     { more_than_once(arr) }
  Set        { multi_set(arr) }
  Difference { multi_difference(arr) }
end

Running each test once. Test will take about 4 minutes.
Pascal is faster than Set by 19.999999999999996% ± 10.0%
Set is faster than Difference by 30.000000000000004% ± 10.0%

当然,difference,如果是 Ruby 核心的一部分,将会用 C 编码并进行优化。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-11
    • 2021-07-30
    • 2012-03-04
    • 1970-01-01
    • 2023-03-19
    • 2021-02-01
    • 2010-09-29
    • 1970-01-01
    相关资源
    最近更新 更多