在一个数组中计算匹配元素数量

6
给定两个等长的数组,如何找到匹配元素的数量而无需考虑其位置?例如:
  1. [0,0,5]和[0,5,5]将返回2个匹配项,因为共有一个0和一个5;
  2. [1,0,0,3]和[0,0,1,4]将返回3个匹配项,因为有两个0和一个1;
  3. [1,2,2,3]和[1,2,3,4]将返回3个匹配项。
我尝试过许多想法,但它们都变得相当复杂。我猜测有一些漂亮的Ruby习语,或者可能有一个正则表达式,可以成为此解决方案的优雅答案。

我的第一个猜测是array_one&array_two.length,但这不包括唯一的元素。 - Richard Hamilton
好问题,陈述清晰。 - Cary Swoveland
@CarySwoveland:完全同意你的观点,但标题太模糊了——有相同问题的人不太可能看到这篇文章。顺便说一句,我也很难想出一个简洁明了的标题。 - potashin
@suslov,你说得有道理。那么,“确定一个数组中映射到第二个数组中相同元素的元素数量。”如何? - Cary Swoveland
Joe,看了你的个人资料后,我想你会对这张照片感兴趣,照片中是我在使用我的第一台电脑。 - Cary Swoveland
1
@CarySwoveland,喜欢那张照片!太棒了。对于我的问题表达不清楚,我很抱歉,但我很难用文字表达出几个例子中更容易的部分。感谢你们的帮助。 - Joe Balsamo
5个回答

3
(arr1 & arr2).map { |i| [arr1.count(i), arr2.count(i)].min }.inject(0, &:+)

(arr1 & arr2) 返回两个数组都包含的独特值列表,arr.count(i) 计算数组中项目 i 的数量。


那样做不行,因为你可以有多个相同的值。 - Richard Hamilton
啊,抱歉。我误解了问题。等一下,我会更新它。 - Andrew Kozin
更新了。有趣的是,我之前不知道 count(i) 这个函数 :) - Andrew Kozin
1
太好了!在我看来,这是目前为止最好的答案,但我建议稍微简化一下:arr1.uniq.reduce(0) { |t,e| t+[arr1.count(e), arr2.count(e)].min } - Cary Swoveland

3
你可以使用 count 来完成它:
a.count{|e| index = b.index(e) and b.delete_at index }

演示

或者使用inject

a.inject(0){|count, e| count + ((index = b.index(e) and b.delete_at index) ? 1 : 0)}

演示

或者使用selectlength(或它的别名size):

a.select{|e| (index = b.index(e) and b.delete_at index)}.size

演示

结果:

  1. a, b = [0,0,5], [0,5,5] 输出: => 2
  2. a, b = [1,2,2,3], [1,2,3,4] 输出: => 3
  3. a, b = [1,0,0,3], [0,0,1,4] 输出: => 3

1
我认为这是解决方案!我需要在上面运行我的rspec,因为在我自己的尝试中遇到了许多边缘情况,但我仍然抱有希望。套件运行后我会告诉你结果。同时非常感谢你。 - Joe Balsamo
1
它通过了我的测试!再次感谢您,suslov,我已经在我的源代码中给您注明了。感激不尽。 - Joe Balsamo
1
不错。你可以使用 a.select{|e| ndx = b.index(e) && b.delete_at ndx }.count 来减少工作量。请注意,如果存在 nil 元素,则此方法无效。此外,它会改变 b 的值,因此您可能需要对 b 的副本进行操作。 - Cary Swoveland
等一下,我可能说得太早了 :-). - Cary Swoveland
1
是的。(我认为后者更符合inject的工作方式。) - Cary Swoveland
1
我只想说,select 是 Ruby 代码中非常棒的一部分!我最初在这里提问的动机是想学习“Ruby方式”来完成它,而我得到了很好的教育。真的太美妙了!我感到很惊讶。再次感谢,我的代码运行得非常好,我很高兴地添加新功能,而不是一直碰壁。 - Joe Balsamo

2

强大且必不可少的Array#difference有另一种用途,我在这里中定义了它。该方法类似于Array#-。这两种方法之间的区别在以下示例中说明:

a = [1,2,3,4,3,2,4,2]
b = [2,3,4,4,4]
a - b          #=> [1]
a.difference b #=>  [1, 3, 2, 2] 

对于当前的应用程序:

def number_matches(a,b)
  left_in_b = b
  a.reduce(0) do |t,e|
    if left_in_b.include?(e)
      left_in_b = left_in_b.difference [e]
      t+1
    else
      t
    end
  end
end

number_matches [0,0,5],   [0,5,5]   #=> 2
number_matches [1,0,0,3], [0,0,1,4] #=> 3
number_matches [1,0,0,3], [0,0,1,4] #=> 3

1
使用 multiset gem:
(Multiset.new(a) & Multiset.new(b)).size

Multiset类似于Set,但允许重复的值。 &是“集合交”运算符(返回两个集合中都有的元素)。

1
从rubygems.org的描述:与普通集合(请参阅Ruby文档中的“set”库)不同,multiset可以包含两个或更多相同的项。 Set [:a,:b,:c,:b,:b,:c]#=>#<Set:{:b,:c,:a}> Multiset [:a,:b,:c,:b,:b,:c]#=>#<Multiset:#3:b,#2:c,#1:a> Multisets通常用于计算集合中元素及其出现次数。非常有趣。谢谢让我意识到这个宝石。 - Joe Balsamo

0

我认为这不是一个理想的答案,因为它有点复杂,但是...

def count(arr)
  arr.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
end

def matches(a1, a2)
  m = 0
  a1_counts = count(a1)
  a2_counts = count(a2)
  a1_counts.each do |e, c|
    m += [a1_counts, a2_counts].min
  end
  m
end

基本上,首先编写一个方法,从每个元素出现的次数数组中创建哈希。然后,使用这些哈希来计算两个数组中每个元素出现的最小次数之和。


谢谢,是的,我只是凭想象拼凑起来的,所以可能会有语法问题。 - MrTheWalrus
我在irb中尝试了这个。现在我得到了“undefined method '+' for nil:Nilclass”的错误。 - Richard Hamilton
走了。在回答之前应该让我的代码工作起来,但我现在认为它正常工作了。 - MrTheWalrus
1
你的count方法有时被称为“计数哈希”,通常写成def count(arr); arr.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }; end - Cary Swoveland
@CarySwoveland 谢谢,我想可能已经有标准的方法来做这件事了。虽然已经有其他更好的答案了,但我已经修改以考虑你的评论。 - MrTheWalrus
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接