计算两个数组之间重复元素的最有效方法
作为我在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#zip
和Enumerable#count
这是一个完美的工作:
array_a.zip(array_b).count do |a, b| a == b end # => 2
zip
方法将元素组合在一起,将它们“ zip
”在一起, count
方法可以阻止元素是否应该被计数。
inject
方法非常强大,但它也是最低级别的。 如果你使用它,几乎所有其他的Enumerable方法都可以使用inject
创建,所以它非常灵活,但通常更特殊的方法更适合。 如果应用得当,它仍然是一个有用的工具。
在这种情况下, zip
和count
做得更好,如果你知道这些方法做了什么,这段代码是不言自明的。
更新 :
如果你需要计算所有重叠的字母而不管顺序,你需要对它们进行一些分组。 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应用于arr1
和arr2
:
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
我在这里的答案中引用了其他应用程序。