作为我在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”两次)。这似乎有效,但我想知道是否有更有效的解决方案来解决这个问题。具体来说,是否有任何方法可供您考虑。我与一个提出了不同方法的人inject
进行了交谈,但我真的不明白这是如何适用的并且想要理解。我对它的使用做了很多阅读,但我仍然不清楚它是如何合适的。谢谢。
查看我的代码,我意识到它似乎不适用于我所描述的情况。
答案 0 :(得分:0)
对于Enumerable#zip
和Enumerable#count
来说,这是一个完美的工作:
array_a.zip(array_b).count do |a, b|
a == b
end
# => 2
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
答案 1 :(得分:0)
如果我正确理解了这个问题,您可以执行以下操作。
<强>代码强>
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#itself时,你必须写:
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
此处Enumerable#reduce(又名inject
)仅将a
的值不是数组的总和。数组对应于arr1
中未显示在arr2
或反之亦然的元素。
正如所承诺的那样,我现在将解释如何计算h2
。我使用Hash#merge的形式使用了一个块(此处为{ |k,ov,nv| [ov.size, nv.size].min }
)来计算两个哈希值中合并的键值。例如,当h1
("A"=>["A", "A"]
)的第一个键值对合并到h0
时,由于h0
也有一个键"A"
,阵列
["A", ["A", "A", "A"], ["A", "A"]]
被传递给块,并且三个块变量被赋值(使用&#34;并行赋值&#34;,有时称为&#34;多个赋值&#34;):
k, ov, nv = ["A", ["A", "A", "A"], ["A", "A"]]
所以我们有
k #=> "A"
ov #=> ["A", "A", "A"]
nv #=> ["A", "A"]
k
是关键,ov
(&#34;旧值&#34;)是"A"
和h0
中nv
的值( &#34;新值&#34;)是"A"
中h1
的值。块计算是
[ov.size, nv.size].min
#=> [3,2].min = 2
所以"A"
的价值现在为2
。
请注意,密钥k
未在块计算中使用(这在使用此merge
形式时非常常见)。出于这个原因,我已经将块变量从k
更改为_
(合法的局部变量),以减少引入错误的机会并向读者发出关键是没用在街区。使用此块的h2
的其他元素的计算方法类似。
另一种方式
如果我们提供Array
方法I've proposed be added to the Ruby core:
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
我在答案here中引用了其他应用程序。
答案 2 :(得分:0)
请允许我重申并解释我认为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_b
,这使得我们对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供应量。