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

时间:2016-03-24 23:26:18

标签: arrays ruby

作为我在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进行了交谈,但我真的不明白这是如何适用的并且想要理解。我对它的使用做了很多阅读,但我仍然不清楚它是如何合适的。谢谢。

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

3 个答案:

答案 0 :(得分:0)

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

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

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

答案 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应用于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#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"h0nv的值( &#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供应量。