快速检查大量哈希值中是否存在部分值的方法

时间:2014-03-21 10:06:19

标签: ruby

我有一个包含大约50,000个值(哈希)的大型数组(A)。我有2个其他数组包含黑名单(B)和值(也是字符串)的白名单(W)要检查A.我知道Array.include?函数,但我必须检查B或W中的值是否部分位于A中每个哈希值的关键“文本”中。

例如:

A = [ { :text => 'Cat', :some_other_key => 'bla' }, 
      { :text => 'Dog', :some_other_key => 'blub' }, 
      { :text => 'Wolve', :some_other_key => 'example' }, 
      { :text => 'Bird', :some_other_key => 'test' }, 
      { :text => 'White Whale', :some_other_key => 'test' } ]
W = [ 'Whale', 'Cow' ]
B = [ 'Pig', 'Chicken' ]

我希望将'白鲸'与来自W的'鲸鱼'相匹配。据我所知,包括?只有在值完全相同时才会匹配。

到目前为止我所做的是:

A.each do |value|

    if W.any? { |w| value[:text] =~ /#{w}/ }
        #Do stuff
        next
    elsif B.any? { |w| value[:text] =~ /#{w}/ }
        #Do other stuff
        next
    end
end

这适合我,但如果A,B或W变大,它真的很慢。这最终引出了我的问题:有没有人知道更好更快的方法来实现这个目标?

2 个答案:

答案 0 :(得分:2)

构建正则表达式是一种可以移出内循环的成本:

w_rx = W.map { |w| /#{w}/ } 
b_rx = B.map { |w| /#{w}/ }

A.each do |value|
    if w_rx.any? { |rx| value[:text] =~ rx }
        #Do stuff
        next
    elsif b_rx.any? { |rx| value[:text] =~ rx }
        #Do other stuff
        next
    end 
end

另请查看steenslag的回答,将w_rx = W.map { |w| /#{w}/ }替换为big_w_rx = Regepx.union(W),将w_rx.any? { |rx| value[:text] =~ rx }替换为value[:text] =~ big_w_rx可以更快。我测试它的速度提高了5倍,但这将取决于你的数据。

改进循环的唯一方法是找到一种方法来跳过A,W或B中的条目。这实际上是索引的作用,但是你在这里遇到的问题是A中的每个条目都是新的你,所以任何方案索引的成本可能都高于扫描整个列表的成本。

如果您对每个[:text]的结构有一定的了解,那么您可以将它用于您的优势。例如,如果[:text]的长度通常小于WB中的项,则无需检查与较长字符串的匹配。按长度排序WB,创建每个长度的点在排序列表中的位置的哈希值,并且仅测试内部循环中W和B的最小切片 - '显然非常具体到数据的行为方式,因此我不打算将其全部写出来,它可能对您没有帮助。


基准,Regexp.unionarr_of_regex.any?

require 'benchmark'

W = [ 'Whale', 'Cow', 'Flat', 'Bundle', 'Bubbles', 
      'Clarinet', 'Cash', 'Fish', 'Donkey' ]

# Dummy data (lots of misses to W, so might not be representative)
a = Array.new( 100000 ) { |i| 
  (1..(5+rand(20))).map { |j| 
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".chars.sample 
  }.join 
}

big_rx = Regexp.union(W)

w_rx = W.map { |w| /#{w}/ }

Benchmark.bm do |bm|
  bm.report(:big_rx) { a.select { |s| s =~ big_rx } }
  bm.report(:w_rx) { a.select { |s| w_rx.any? { |rx| s =~ rx } } }
end

输出:

       user     system      total        real
big_rx  0.110000   0.000000   0.110000 (  0.112621)
w_rx  0.650000   0.000000   0.650000 (  0.660112)

如果我将W修改为1000条记录,与上面示例中的a类似,那么差异就会更接近2(仍然非常值得拥有):

       user     system      total        real
big_rx 21.380000   0.010000  21.390000 ( 21.398051)
w_rx 39.720000   0.030000  39.750000 ( 39.759734)

答案 1 :(得分:2)

您可以使用union

构建巨大的正则表达式
B_res = Regexp.union(B)
W_res = Regexp.union(W)

(或以几百个B和W的块为单位),并将这些与A匹配。