我正在寻找一种数组交集的方法,与普通的&
方法不同,它只匹配那两个数组中相同且具有相同索引的元素。 E.g:
["a", "b", "c", "d"].intersect(["a", "b", "f", "d", "c"])
应该返回
["a", "b", "d"]
因为所有三个数组都存在于相同索引(0,1和3)的两个数组中,而"c"
被排除,因为它在两个数组(2和4)中具有不同的位置:
# 0 1 2 3 4
["a", "b", "c", "d"]
["a", "b", "f", "d", "c"]
我可以想到这个问题的几种解决方案。第一个是循环,另一个是首先修改数组以在元素中包含它们的索引,然后使用&
进行交叉。 E.g:
["a", "b", "c"] #=> ["0a", "1b", "2c"]
然而,我想知道是否存在更好的方法。我正在寻找使用正负索引。
答案 0 :(得分:7)
鉴于您的两个数组a
和b
:
a = ["a", "b", "c", "d"]
b = ["a", "b", "f", "d", "c"]
以下是不同的尝试:
zip
和select
您可以使用zip
组合两个数组和select
中的相应元素来检索相同的元素:
a.zip(b).select { |x, y| x == y }.map(&:first)
#=> ["a", "b", "d"]
一步一步:
a.zip(b) #=> [["a", "a"], ["b", "b"], ["c", "f"], ["d", "d"]]
.select { |x, y| x == y } #=> [["a", "a"], ["b", "b"], ["d", "d"]]
.map(&:first) #=> ["a", "b", "d"]
请注意,如果zip
更短,nil
将使用nil
值填充第二个数组。如果您的数组包含a = [2, 1, nil]
b = [2]
a.zip(b).select { |x, y| x == y }.map(&:first)
#=> [2, nil]
值,则可能会出现问题:
b
这是因为[2, nil, nil]
在zip
内变为each_with_index
。
&
和[element, index]
或者您可以使用each_with_index
构建ai = a.each_with_index.to_a
#=> [["a", 0], ["b", 1], ["c", 2], ["d", 3]]
bi = b.each_with_index.to_a
#=> [["a", 0], ["b", 1], ["f", 2], ["d", 3], ["c", 4]]
对数组:
ai & bi
#=> [["a", 0], ["b", 1], ["d", 3]]
并改为相交:
map
我们可以使用(ai & bi).map(&:first)
#=> ["a", "b", "d"]
来提取第一个元素:
(a.each_with_index.to_a & b.each_with_index.to_a).map(&:first)
#=> ["a", "b", "d"]
在单个表达式中:
each_with_index
each_object
和each_with_index
将a.each_with_index.with_object([]) { |(x, i), arr| arr << x if x == b[i] }
#=> ["a", "b", "d"]
和with_object
结合起来的另一种方式:
x
将a
中的每个元素b
与each_twin
中的相应元素进行比较,如果它们相等,则将其添加到结果数组中。
这可以避免创建中间数组,但我觉得它的可读性较差。
def each_twin(a, b)
return enum_for(__method__, a, b) unless block_given?
loop do
x, y = a.next, b.next
yield x if x == y
end
end
each_twin(a.to_enum, b.to_enum).to_a
#=> ["a", "b", "d"]
可能不是最好的名字(我在命名方面不好):
a
在循环中,从b
和a
检索下一个值。如果b
的值等于a
,则会产生b
的值。 Enumerator#next
导致循环在{{1}}或{{1}}结束时退出。