Ruby - 是找到两个非常大的数组之间差异的有效方法吗?

时间:2016-05-20 19:17:47

标签: arrays ruby algorithm

在找到两个非常大的数组之间的区别时,我遇到了关于效率和算法的问题。我希望对算法有很好理解的人可以指出我如何解决这个问题的正确方向,因为我目前的实现需要花费很长时间。

问题:

我有两个非常大的数组。一个包含具有无效域名的电子邮件列表,另一个是我需要针对第一个阵列检查的混合列表。

accounts_with_failed_email_domains = [279,000 records in here]

unchecked_account_domains = [149,000 records in here]

我需要做的是浏览unchecked_account_domains列表,然后比较每个条目以查看accounts_with_failed_email_domains中是否匹配。我需要在列表之间插入所有匹配项,以便稍后处理。

如何有效地编写可以快速检查这些帐户的内容。这是我到目前为止所尝试的内容。

unchecked_account_domains = [really big array]
unchecked_account_domains = unchecked_account_domains.sort

accounts_with_failed_email_domains = [another huge array].sort

unchecked_account_domains.keep_if do |email|
  accounts_with_failed_email_domains.any? { |failed_email| email == failed_email }
end

# Count to see how many accounts are left
puts unchecked_account_domains.count

以上实施一直在运行。这是第二次尝试,但仍然证明没有更好。

unchecked_account_domains = [really big array]
unchecked_account_domains = unchecked_account_domains.sort

accounts_with_failed_email_domains = [another huge array].sort

unchecked_account_domains.each do |email|
  accounts_with_failed_email_domains.bsearch do |failed_email| 
     final_check << email if email == failed_email 
  end
end

# Count to see how many accounts are left
puts final_check.count

bsearch似乎很有希望,但我很确定我没有正确使用它。另外,我试着调查这个问题comparing large lists,但这是python,我似乎找不到set的Ruby等价物。有没有人对如何解决这个问题有任何想法?

4 个答案:

答案 0 :(得分:6)

好像你可以使用execute

Array#-

答案 1 :(得分:6)

我在这里没有新的解决方案,因为已经采取了良好的答案。但是,我想看看两个基于代码的解决方案之间是否存在性能差异。

此答案是一个基准,用于突出显示Array#-使用中的任何性能差异以及Set#include?的两种用法。第一个Set#include?基准测试始终执行设置转换,第二个转换为一次并保留设置以供后续搜索。

这里是每次测试运行50次的代码:

require 'set'
require 'benchmark'

string = 'asdfghjkl'
Times = 50

a = 279_000.times.map {|n| "#{n}#{string}" }
b = 149_000.times.map {|n| "#{n*2}#{string}" }

puts RUBY_DESCRIPTION
puts "============================================================"
puts "Running tests for trimming strings"

Benchmark.bm(20) do |x|
  x.report("Array#-:")      { Times.times {|n| a - b } }
  x.report("Set#include? #1:") do
    Times.times do |n|
      d = []
      c = Set.new(b)
      a.each {|email| d << email if c.include?(email) }
    end
  end
  x.report("Set#include? #2:") do
    c = Set.new(b)
    Times.times do |n|
      d = []
      a.each {|email| d << email if c.include?(email) }
    end
  end
end

结果如下:

ruby 2.2.5p319 (2016-04-26 revision 54774) [x86_64-darwin14]
============================================================
Running tests for trimming strings
                           user     system      total        real
Array#-:              12.350000   0.250000  12.600000 ( 13.001546)
Set#include? #1:      16.090000   0.330000  16.420000 ( 17.196469)
Set#include? #2:       8.250000   0.100000   8.350000 (  8.726609)

显然,如果您只需要进行单一差异比较,请使用Array#-方法。但是,如果您需要多次执行此类事件,预转换集合会产生巨大差异,并且性能优于Array#-。将Array转换为Set的成本相当高(相对而言),但是一旦你有了Set,它就会更快地执行差异比较。

答案 2 :(得分:1)

Set如果您知道数组中包含唯一的项目(或者您不会因为丢失重复项而烦恼 - 我不会认为您是这样的话),那么此处就很有用了大阵并做:

require 'set'
unchecked_account_domains = [really big array]

accounts_with_failed_email_domains = Set.new([another huge array])
final_check = []

  unchecked_account_domains.each do |email| 
     final_check << email if accounts_with_failed_email_domain.include?(email)  # .include? on a set is in O(1) look up time 
  end

答案 3 :(得分:0)

将失败的电子邮件数组转换为集合(我认为Ruby命令是.to_set,在Ruby文档中阅读它)。然后使用.include?检查每个未经检查的电子邮件。

它永远运行的原因是它会在每个检查的整个或大部分列表中运行。 set类应该对列表进行哈希处理,使查询更快。