Ruby - 优化两个数组的重复比较

时间:2012-07-05 17:35:12

标签: ruby

我有以下数组:

A = "cheddar".split(//)  # ["c", "h", "e", "d", "d", "a", "r"]
B = "cheddaar".split(//) # ["c", "h", "e", "d", "d", "a", "a", "r"]

A数组是B数组的子集。如果A数组有另一个“d”元素,那么它就不是一个子集。

我想比较并发现一个是否是另一个的子集,即使它们有重复。 A - B或A& B不捕获它只是比较它们的重复项并找到它们匹配。所以我写了以下内容,它捕获了重复项:

B.each do |letter|
    A.delete_at(A.index(letter)) rescue ""
end

p A.empty?

这是最佳方式还是可以优化?

7 个答案:

答案 0 :(得分:3)

不知道这实际上是否比你的方法更快,但它的运行时应该是O(N + M),其中N,M是a,b的大小。 (假设哈希查找和插入是默认的O(1),这不是严格正确的,因为哈希通常是密钥大小的函数;尽管在示例中,所有密钥都是单个字符。)#index方法的循环#delete_at具有实质性额外的数据运动,看起来可能是最坏的情况O(N ^ 2 * M)。

def ary_subset?(a,b) # true iff a is subset of b
  a_counts = a.reduce(Hash.new(0)) { |m,v| m[v] += 1; m }
  b_counts = b.reduce(Hash.new(0)) { |m,v| m[v] += 1; m }
  a_counts.all? { |a_key,a_ct| a_ct <= b_counts[a_key] }
end

OP要求最快的方式,所以我在this gist提供了一点微观基准。

我测试了OP的原始方法(op_del),我使用reduce count(ct)的版本,以及变体where the count array is reused(ct_acc)和MultiSet approach(mset), EDIT 并添加了very concise find of count comparisons(slow_ct)。将每个变量与OP的小数组输入示例相比较,基数为10,000(b)的较大集合,以及对大集合(sb)的小集合。 (不得不将大集合案例的迭代次数减少一个数量级,以便在合理的时间内完成_slow_ct_。)结果:

                     user     system      total        real
s_op_del         1.850000   0.000000   1.850000 (  1.853931)
s_ct             2.260000   0.000000   2.260000 (  2.264028)
s_ct_acc         1.700000   0.000000   1.700000 (  1.706881)
s_mset           5.460000   0.000000   5.460000 (  5.484833)
s_slow_ct        1.720000   0.000000   1.720000 (  1.731367)
b_op_del         0.310000   0.000000   0.310000 (  0.312804)
b_ct             0.120000   0.000000   0.120000 (  0.123329)
b_ct_acc         0.100000   0.000000   0.100000 (  0.101532)
b_mset           0.310000   0.000000   0.310000 (  0.319697)
b_slow_ct       82.910000   0.000000  82.910000 ( 83.013747)
sb_op_del        0.710000   0.020000   0.730000 (  0.734022)
sb_ct            0.050000   0.000000   0.050000 (  0.054416)
sb_ct_acc        0.040000   0.000000   0.040000 (  0.059032)
sb_mset          0.110000   0.000000   0.110000 (  0.117027)
sb_slow_ct       0.010000   0.000000   0.010000 (  0.011287)

减少计数,重复使用计数累加器是明显的赢家。 Multiset令人失望地缓慢。

答案 1 :(得分:2)

如果我正确理解了要求,您可以使用multiset gem。

require 'multiset'
a = Multiset.new "cheddar".split(//)
b = Multiset.new "cheddaar".split(//)

a.subset? b #=> true

答案 2 :(得分:2)

如果我没记错的话,你的解决方案是O(n ^ 2) 这个有点麻烦,但效率更高,至少对于大输入(这是O(n))。 它可能需要更多的工作......

def is_subset?(a, b)
    letters = Hash.new(0)
    a.each_char{|x| letters[x] += 1}
    b.each_char{|x| letters[x] -= 1}
    letters.values.all?{|v| v >= 0 }
end

编辑:效率更高:

def is_subset?(a, b)
    letters = Hash.new(0)
    a.each_char{|x| letters[x] += 1}
    b.each_char.all?{|x| (letters[x] -= 1) > 0}
end

答案 3 :(得分:2)

绝对希望在这里利用枚举器 - 最好的方法是使用group_by并比较每个字母出现的次数:

def subset?(a, b)
   a = a.each_char.group_by { |char| char }
   b = b.each_char.group_by { |char| char }
   a.each_key.all? do |letter|
     b[letter] && a[letter].size < b[letter].size
   end
end

因此,如果我们将哈希查找计为O(1)运算,那么这是一个O(m + n)算法

答案 4 :(得分:1)

试试这个:

class String
  def subset_of?(str)
    e2 = str.each_char
    c2 = c2p = nil
    each_char do |c1|
      c2p, c2 = c2, e2.next
      next if c2 == c1    
      c2p, c2 = c2, e2.next until (c2 != c2p) # move until we exclude duplicates
      return false if c2 != c1
    end
    true
  rescue StopIteration
    false
  end
end

测试功能:

>> "chedddar".subset_of?("cheddaaaaaar")
=> false
>> "cheddar".subset_of?("cheddaaaaaar")
=> true
>> "cheddar".subset_of?("cheddaaaaaarkkkk")
=> true
>> "chedddar".subset_of?("cheddar")
=> false
>> "chedddar".subset_of?("chedd")
=> false

修改1

根据提供的其他信息更新了解决方案。

class String
  def subset_of?(str)
    h1, h2 = [self, str].map {|s| s.each_char.reduce(Hash.new(0)){|h, c| h[c] += 1; h}}
    h1.all?{|c, k| h2[c] >= k}
  end
end

答案 5 :(得分:1)

几个星期前发布了类似的问题,我得到了接受的回答:

def is_subset?(a,b)
  !a.find{|x| a.count(x) > b.count(x)}
end

基准更新

require 'benchmark'

def random_char
  ('a'..'z').to_a.sample
end

A = 8.times.map{random_char}
B = 8.times.map{random_char}

def ary_subset?(a,b) # true iff a is subset of b
  a_counts = a.reduce(Hash.new(0)) { |m,v| m[v] += 1; m }
  b_counts = b.reduce(Hash.new(0)) { |m,v| m[v] += 1; m }
  a_counts.all? { |a_key,a_ct| a_ct <= b_counts[a_key] }
end

Benchmark.bm do |x|
  x.report('me')     {100000.times{is_subset?(A,B)}}
  x.report('dbenhur'){100000.times{ary_subset?(A,B)}}
end

       user     system      total        real
me  0.375000   0.000000   0.375000 (  0.384022)
dbenhur  2.558000   0.000000   2.558000 (  2.550146)

答案 6 :(得分:-1)

如何首先移除傻瓜?

(A.uniq - B.uniq).empty?