计算两个数组之间重复元素的最有效方法

作为我在Ruby编写的一个非常基本的程序的一部分,我试图找到两个长度相等的数组之间共享元素的总数,但我需要包含重复。

我目前针对这种情况的示例代码如下:

array_a = ["B","A","A","A","B"] array_b = ["A","B","A","B","B"] counter = 0 array_a.each_index do |i| array_a.sort[i] == array_b.sort[i] counter += 1 end end puts counter 

我希望此实例中此比较的返回值为4,而不是2,因为两个数组共享2个重复字符(“A”两次,“B”两次)。 这似乎有效,但我想知道是否有更有效的解决方案来解决这个问题。 具体来说,是否有任何方法可供您考虑。 我和一个提出不同方法的人进行了交谈,但是我真的不明白这是如何适用的并且想要理解。 我对它的使用做了很多阅读,但我仍然不清楚它是如何合适的。 谢谢。

看看我的代码,我意识到它似乎不适用于我所描述的情况。

对于Enumerable#zipEnumerable#count这是一个完美的工作:

 array_a.zip(array_b).count do |a, b| a == b end # => 2 

zip方法将元素组合在一起,将它们“ zip ”在一起, count方法可以阻止元素是否应该被计数。

inject方法非常强大,但它也是最低级别的。 如果你使用它,几乎所有其他的Enumerable方法都可以使用inject创建,所以它非常灵活,但通常更特殊的方法更适合。 如果应用得当,它仍然是一个有用的工具。

在这种情况下, zipcount做得更好,如果你知道这些方法做了什么,这段代码是不言自明的。

更新

如果你需要计算所有重叠的字母而不管顺序,你需要对它们进行一些分组。 Ruby on Rails在ActiveSupport中提供了方便的group_by方法,但在纯Ruby中你需要自己创建。

这是一种计算所有独特字母的方法,使用chunk对它们进行分组:

 # Convert each array into a map like { "A" => 2, "B" => 3 } # with a default count of 0. counts = [ array_a, array_b ].collect do |a| Hash.new(0).merge( Hash[a.sort.chunk { |v| v }.collect { |k, a| [ k, a.length ] }] ) end # Iterate over one of the maps key by key and count the minimum # overlap between the two. counts[0].keys.inject(0) do |sum, key| sum + [ counts[0][key], counts[1][key] ].min end 

请允许我重申并解释我认为OP的原意是:

给定大小相等的数组

 array_a = ["B","A","A","A","B"] array_b = ["A","B","A","B","B"] 

我们需要显示两个数组之间匹配的元素对的总数。 换句话说, array_a每个B将“用完” array_b的B,并且对于每个A都是相同的。由于array_a有两个B, array_a三个,因此B的计数为2 ,并遵循相同的逻辑,2为A,总和为4。

 (array_a & array_b).map { |e| [array_a.count(e), array_b.count(e)].min }.reduce(:+) 

如果我们得到数组与&的交集,则结果是两个数组中都存在的值列表。 然后我们迭代每个匹配,并选择元素在任一个数组中存在的最小次数—这是可以“使用”的元素的最多次数。 剩下的就是使用reduce(:+)计算配对元素的总数

array_a改为["B", "A", "A", "B", "B"]导致总共5,因为现在有足够的B来耗尽array_b的B的供应。

如果我正确理解了这个问题,您可以执行以下操作。

 def count_shared(arr1, arr2) arr1.group_by(&:itself). merge(arr2.group_by(&:itself)) { |_,ov,nv| [ov.size, nv.size].min }. values. reduce(0) { |t,o| (o.is_a? Array) ? t : t + o } end 

例子

 arr1 = ["B","A","A","A","B"] arr2 = ["A","B","A","B","B"] count_shared(arr1, arr2) #=> 4 (2 A's + 2 B's) arr1 = ["B", "A", "C", "C", "A", "A", "B", "D", "E", "A"] arr2 = ["C", "D", "F", "F", "A", "B", "A", "B", "B", "G"] count_shared(arr1, arr2) #=> 6 (2 A's + 2 B's + 1 C + 1 D + 0 E's + 0 F's + 0 G's) 

说明

对于第一个示例的略微修改版本,步骤如下。

 arr1 = ["B","A","A","A","B","C","C"] arr2 = ["A","B","A","B","B","D"] 

首先将Enumerable#group_by应用于arr1arr2

 h0 = arr1.group_by(&:itself) #=> {"B"=>["B", "B"], "A"=>["A", "A", "A"], "C"=>["C", "C"]} h1 = arr2.group_by(&:itself) #=> {"A"=>["A", "A"], "B"=>["B", "B", "B"], "D"=>["D"]} 

在Ruby v.2.2之前,当引入Object#本身时,你必须写:

 arr.group_by { |e| e } 

继续,

 h2 = h0.merge(h1) { |_,ov,nv| [ov.size, nv.size].min } #=> {"B"=>2, "A"=>2, "C"=>["C", "C"], "D"=>["D"]} 

我将很快回来解释上述计算。

 a = h2.values #=> [2, 2, ["C", "C"], ["D"]] a.reduce(0) { |t,o| (o.is_a? Array) ? t : t + o } #=> 4 

这里可枚举#reduce (aka inject )只是对不是数组的a的值求和。 数组对应于arr1元素,这些元素不出现在arr2 ,反之亦然

正如所承诺的那样,我现在将解释如何计算h2 。 我使用了Hash#merge的forms,它使用一个块(这里是{ |k,ov,nv| [ov.size, nv.size].min } )来计算两个哈希中存在的键的值合并。 例如,当h1的第一个键值对( "A"=>["A", "A"] )被合并到h0 ,因为h0也有一个键"A" ,所以数组

 ["A", ["A", "A", "A"], ["A", "A"]] 

被传递给块并且三个块变量被赋值(使用“并行赋值”,有时称为“多重赋值”):

 k, ov, nv = ["A", ["A", "A", "A"], ["A", "A"]] 

所以我们有

 k #=> "A" ov #=> ["A", "A", "A"] nv #=> ["A", "A"] 

k是关键, ov (“旧值”)是h0"A"的值,而nv (“新值”)是h1"A"的值。 块计算是

 [ov.size, nv.size].min #=> [3,2].min = 2 

所以"A"值现在是2

请注意,在块计算中不使用密钥k (这在使用这种forms的merge时非常常见)。 出于这个原因,我已经将块变量从k更改为_ (合法的局部变量),这两者都是为了减少引入错误的可能性,并向读者发出关键字未在块中使用的信号。 使用该块的h2的其他元素类似地计算。

其他方式

如果我们有一个可以添加到Ruby核心的Array方法,那将是非常简单的:

 array_a = ["B","A","A","A","B"] array_b = ["A","B","A","B","B"] array_a.size - (array_a.difference(array_b)).size #=> 4 

要么

 array_a.size - (array_b.difference(array_a)).size #=> 4 

我在这里的答案中引用了其他应用程序。