Ruby - 数组交集(带有重复项)

时间:2016-06-24 18:56:19

标签: arrays ruby intersection

我有array(1 and 2)。如何从中获取array3

array1 = [2,2,2,2,3,3,4,5,6,7,8,9]

array2 = [2,2,2,3,4,4,4,4,8,8,0,0,0]

array3 = [2,2,2,3,4,8]

array1 & array2返回[2,3,4,8],但我需要抓住重复项。

6 个答案:

答案 0 :(得分:12)

(array1 & array2).flat_map { |n| [n]*[array1.count(n), array2.count(n)].min }
  #=> [2,2,2,3,4,8]

步骤:

a = array1 & array2 
  #=> [2, 3, 4, 8]  

a2)的第一个元素被传递给块并分配给块变量:

n = 2

并执行块计算:

[2]*[array1.count(2), array2.count(2)].min
  #=> [2]*[4,3].min
  #=> [2]*3
  #=> [2,2,2]

因此2已映射到[2,2,2]a的其余三个元素的计算类似。当我使用flat_map时,会返回[2,2,2,3,4,8]

您是否难以记住Enumerable#flat_mapEnumerable#map的区别?假设我使用了map而不是flat_map。然后

a.map { |n| [n]*[array1.count(n), array2.count(n)].min }
  #=> [[2, 2, 2], [3], [4], [8]]

flat_map没有做任何更多的事情,在每个阵列的前面放置一个splat:

[*[2, 2, 2], *[3], *[4], *[8]]
  #=> [2, 2, 2, 3, 4, 8] 

如果数组array1array2很大且效率问题,我们可以做一些O(N)预处理:

def cnt(arr)
  arr.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
end

cnt1 = cnt(array1)
  #=> {2=>4, 3=>2, 4=>1, 5=>1, 6=>1, 7=>1, 8=>1, 9=>1} 
cnt2 = cnt(array2)
  #=> {2=>3, 3=>1, 4=>4, 8=>2, 0=>3} 

(array1 & array2).flat_map { |n| [n]*[cnt1[n], cnt2[n]].min }
  #=> [2,2,2,3,4,8]

答案 1 :(得分:4)

这是一个有趣的; Cary的flat_map解决方案特别聪明。这是使用常规旧版map的替代单线版,并得到each_with_object的一些帮助:

array1.each_with_object(array2.dup).map{|v,t| v if (l = t.index v) && t.slice!(l) }.compact
 #=> [2,2,2,3,4,8]

这里的复杂性很大程度上涉及用于为map提供足够信息以完成任务的内联体操:

 #
 # we want to duplicate array2 since we'll be
 # mutating it to track duplicates       
 #                       \        array1     array2
 #                        \        value     copy  
 #                         \            \   /
array1.each_with_object(array2.dup).map{|v,t| ... }
 #         |                         /      
 # Enumerator for array1    Iterate over              
 # with a copy of array2    Enumerator with map  

我们可以使用each_with_object为array1提供一个Enumerator,它也可以让我们的方法链访问array2的副本。然后Map可以遍历each_with_object枚举器(引用array1),将每个值加载到局部变量v中,并将array2复制到局部变量t中。从那里:

 #                map the value IF...
 #               /  it exists in     and we were able to
 #              / our array2 copy    remove it from our copy
 #            /          |              |
map{|v,t| v if (l = t.index v) && t.slice!(l) }.compact
 #   |  \         \                               |
 # array1 \        \                          dump nils
 # value   array2   \
 #         copy      load index position into temporary variable l

我们遍历array1的每个值,并搜索该值是否存在于array2中(通过t)。如果它存在,我们从array2的副本中移除值的第一次出现,并将值map移除到我们的结果数组。

注意t.index(v)之前的t.slice!(t.index(v))检查用作短路保护,以防t(我们的array2副本)中的值不存在。我们还使用在线技巧将索引值分配给局部变量l(l = t.index v)所以我们可以在随后的布尔检查中引用lt.slice!(l)

最后,因为只要array2中不存在array1值,这种方法就会映射nil值,我们compact会删除nils的结果。

对于那些好奇的人,这里是迄今为止提出的解决方案的一些基准测试。首先,以下是在样本阵列上执行操作100,000次的时钟速度:

Cary:        1.050000   0.010000   1.060000 (  1.061217)
Cary+:       1.580000   0.010000   1.590000 (  1.603645)
Cam:         0.550000   0.010000   0.560000 (  0.552062)
Mudasobwa:   2.540000   0.050000   2.590000 (  2.610395)
Sergii:      0.660000   0.000000   0.660000 (  0.665408)
Sahil:       1.750000   0.010000   1.760000 (  1.769624)
#Tommy:      0.290000   0.000000   0.290000 (  0.290114)

如果我们扩展测试数组以保持10000个整数与高度交叉 ...

array1 = array2 = []
10000.times{ array1 << rand(10) }
10000.times{ array2 << rand(10) }

并循环100次,简单的循环解决方案(Sahil)开始区分自己。 Cary的解决方案也很好用,特别是在预处理方面:

                 user     system      total        real
Cary:        1.590000   0.020000   1.610000 (  1.615798)
Cary+:       0.870000   0.010000   0.880000 (  0.879331)
Cam:         6.680000   0.090000   6.770000 (  6.838829)
Mudasobwa:   6.740000   0.080000   6.820000 (  6.898394)
Sergii:      6.760000   0.100000   6.860000 (  6.962025)
Sahil:       0.740000   0.030000   0.770000 (  0.785975)
#Tommy:      0.430000   0.010000   0.440000 (  0.446482)

对于数组的1/10,大小为1000整数且低交点,但是......

array1 = array2 = []
1000.times{ array1 << rand(10000) }
1000.times{ array2 << rand(10000) } 

当我们循环10次时,flat_map解决方案变得扁平......除非我们使用预处理(Cary +):

                 user     system      total        real
Cary:      135.400000   0.700000 136.100000 (137.123393)
Cary+:       0.270000   0.010000   0.280000 (  0.268255)
Cam:         0.670000   0.000000   0.670000 (  0.676438)
Mudasobwa:   0.670000   0.010000   0.680000 (  0.684088)
Sergii:      0.660000   0.010000   0.670000 (  0.673881)
Sahil:       1.970000   2.130000   4.100000 (  4.121759)
#Tommy:      0.050000   0.000000   0.050000 (  0.045970)

以下是基准测试的要点:https://gist.github.com/camerican/139463b4bd9e0fd89424377931042ce4

答案 2 :(得分:1)

array1 = [2,2,2,2,3,3,4,5,6,7,8,9]
array2 = [2,2,2,3,4,4,4,4,8,8,0,0,0]

a1, a2 = array1.dup, array2.dup # we’ll mutate them

loop.with_object([]) do |_, memo|
  break memo if a1.empty? || a2.empty?
  e = a2.delete_at(a2.index(a1.shift)) rescue nil
  memo << e if e
end
#⇒ [2,2,2,3,4,8]

答案 3 :(得分:1)

    array1 = [2,2,2,2,3,3,4,5,6,7,8,9]
    array2 = [2,2,2,3,4,4,4,4,8,8,0,0,0]

获取样本数组中每个元素的频率:

    a1_freq=Hash.new(0); a2_freq=Hash.new(0); dup_items=[];
    array1.each {|a| a1_freq[a]+=1 }
    array2.each {|b| a2_freq[b]+=1 }

最后比较元素是否存在于另一个数组中。如果是,则获取两个样本数组中找到的公共元素的最小计数。

    a1_freq.each {|k,v| a2_freq[k] ? dup_items+=[k]*[v,a2_freq[k]].min : nil}
    #dup_items=> [2, 2, 2, 3, 4, 8]

答案 4 :(得分:0)

这有点冗长,但假设你指的是值在同一位置的位置:

def combine(array1, array2)
    longer_array = array1.length > array2.length ? array1 : array2

    intersection = []
    count = 0
    longer_array.each do |item|
        if array1 == longer_array
            looped_array = array2
        else
            looped_array = array1
        end
        if item == looped_array[count]
            intersection.push(item)
        end
        count +=1
    end
    print intersection
end


array_1 = [2,2,2,2,3,3,4,5,6,7,8,9]
array_2 = [2,2,2,3,4,4,4,4,8,8,0,0,0]


combine(array_1, array_2)

我只想指出,我不知道你是如何得到数组3的,因为所有三个数组上的索引位置3都不同:

array_1[3] = 2

array_2[3] = 3

array_3[3] = 3

答案 5 :(得分:0)

我会尝试以这种方式达到预期的结果:

array1, array2 = [array1, array2].sort_by(&:size)
arr_copy = array2.dup

array1.each.with_object([]) do |el, arr|
    index = arr_copy.find_index(el)
    arr << arr_copy.slice!(index) if index
end
# => [2, 2, 2, 3, 4, 8]