【问题标题】:Check if two arrays have the same contents (in any order)检查两个数组是否具有相同的内容(以任何顺序)
【发布时间】:2012-06-10 19:08:21
【问题描述】:

我正在使用 Ruby 1.8.6 和 Rails 1.2.3,并且需要确定两个数组是否具有相同的元素,无论它们的顺序是否相同。保证其中一个数组不包含重复项(另一个可能,在这种情况下答案是否定的)。

我的第一个想法是

require 'set'
a.to_set == b.to_set

但我想知道是否有更有效或更惯用的方法。

【问题讨论】:

标签: ruby arrays comparison


【解决方案1】:

Ruby 2.6+

Ruby 在 2.6 中引入了difference

这里给出了一个非常快速、非常易读的解决方案,如下:

a = [1, 2, 3, 4, 5, 6]
b = [1, 2, 3, 4, 5, 6]

a.difference(b).any?
# => false
a.difference(b.reverse).any?
# => false

a = [1, 2, 3, 4, 5, 6]
b = [1, 2, 3]
a.difference(b).any?
# => true

然而,反之则不然:

a = [1, 2, 3]
b = [1, 2, 3, 4, 5, 6]
a.difference(b).any?
# => false

这意味着要获得两个方向的差异就必须运行:

a.difference(b).any? || b.difference(a).any?

运行基准测试:

a = Array.new(1000) { rand(100) }
b = Array.new(1000) { rand(100) }

Benchmark.ips do |x|
  x.report('sort')   { a.sort == b.sort }  
  x.report('sort!')  { a.sort! == b.sort! }  
  x.report('to_set') { a.to_set == b.to_set }  
  x.report('minus')  { ((a - b) + (b - a)).empty? }  
  x.report('difference') { a.difference(b).any? }
  x.report('difference two way') { a.difference(b).any? || b.difference(a).any? }
end

                sort     10.175k (± 6.2%) i/s -     50.778k in   5.015112s
               sort!     10.513k (± 6.8%) i/s -     53.212k in   5.089106s
              to_set      4.953k (± 8.8%) i/s -     24.570k in   5.037770s
               minus     15.290k (± 6.6%) i/s -     77.520k in   5.096902s
          difference     25.481k (± 7.9%) i/s -    126.600k in   5.004916s
  difference two way     12.652k (± 8.3%) i/s -     63.232k in   5.038155s

我的结论是difference 是单向差异的绝佳选择。

如果您需要双向检查,那就是性能和可读性之间的平衡。对我来说,可读性很重要,但这是一个需要根据具体情况进行的调用。

希望对某人有所帮助!

【讨论】:

    【解决方案2】:

    结合&size 也可能很快。

    require 'benchmark/ips'
    require 'set'
    
    Benchmark.ips do |x|
      x.report('sort')   { a.sort == b.sort }  
      x.report('sort!')  { a.sort! == b.sort! }  
      x.report('to_set') { a.to_set == b.to_set }  
      x.report('minus')  { ((a - b) + (b - a)).empty? }
      x.report('&.size') { a.size == b.size && (a & b).size == a.size }  
    end  
    
    Calculating -------------------------------------
                    sort    896.094k (±11.4%) i/s -      4.458M in   5.056163s
                   sort!      1.237M (± 4.5%) i/s -      6.261M in   5.071796s
                  to_set    224.564k (± 6.3%) i/s -      1.132M in   5.064753s
                   minus      2.230M (± 7.0%) i/s -     11.171M in   5.038655s
                  &.size      2.829M (± 5.4%) i/s -     14.125M in   5.010414s
    

    【讨论】:

      【解决方案3】:

      如果您知道数组长度相等且两个数组都不包含重复项,那么这也可以:

      ( array1 & array2 ) == array1
      

      解释:在这种情况下,& 运算符返回 a1 的副本,其中不包含在 a2 中找不到的任何项目,这与原始 a1 相同,前提是两个数组具有相同的内容而没有重复。

      分析:鉴于顺序没有改变,我猜这是作为双迭代实现的,因此始终如一O(n*n),对于大型数组来说明显比a1.sort == a2.sort 更糟糕,后者应该表现最差-caseO(n*logn).

      【讨论】:

      • 并不总是有效:a1 = [1,2,3], a2 = [2, 1, 3] a1 && a2 为我返回 [2,1,3] 不等于 a1
      • @Kaylan,你不是说它只在a1==a2 时有效吗?等号右边的array1换成array2可能会起作用,但我怀疑&返回的元素的顺序是否有保证。
      • @KalyanRaghu & 是数组的集合交集运算符,&& 是逻辑与 - 它们非常不同!
      【解决方案4】:

      这不需要转换即可设置:

      a.sort == b.sort
      

      【讨论】:

      • 没有转换?那么.uniq.sort 是什么?此外uniq 内部类似于to_set 加上额外的.to_a.sort
      • 接受这个,因为它最接近我最终使用的,尽管没有uniqs。实际上我最终用Range#to_a 创建了一个数组,所以我只需要sort 另一个。
      • 如果数组包含不能简单排序的元素(例如哈希数组),这将不起作用。 sahil dhankhar 的解决方案似乎是一个更通用的解决方案。
      【解决方案5】:

      对于两个数组 A 和 B: A 和 B 具有相同的内容,如果: (A-B).blank? and (B-A).blank?

      或者您可以检查: ((A-B) + (B-A)).blank?

      也正如@cort3z 所建议的,这个解决方案也适用于多态数组,即

       A = [1 , "string", [1,2,3]]
       B = [[1,2,3] , "string", 1]
       (A-B).blank? and (B-A).blank? => true
       # while A.uniq.sort == B.uniq.sort will throw error `ArgumentError: comparison of Fixnum with String failed` 
      

      ::::::::::: 编辑 ::::::::::::::

      正如 cmets 中所建议的那样,上述解决方案对于重复项失败。尽管根据提问者甚至不需要的问题,因为询问者对重复项不感兴趣(他在检查之前将他的数组转换为设置,并且屏蔽重复项,即使您在检查之前查看他正在使用 .uniq 运算符的公认答案,并且它也掩盖了重复项。)。但是,如果您对重复项感兴趣,只需添加计数检查即可解决问题(根据问题,只有一个数组可以包含重复项)。所以最终的解决方案是: A.size == B.size and ((A-B) + (B-A)).blank?

      【讨论】:

      • 如果任一数组包含重复项,这将失败。例如,如果 A=[1]B=[1,1](A-B)(B-A) 都将返回空白。见Array Documentation
      • @dafrazzman 完全同意你的看法。我已经修改了我的答案以纳入您的反馈。但如果您仔细查看问题(或接受的答案),提问者使用:a.to_set == b.to_set,而接受的答案使用a.uniq.sort == b.uniq.sort,两者都给出完全相同的结果((A-B) + (B-A)).blank? 对于 A=[1] 和 B=[1,1] 同意吗?由于他只是要求对他的原始解决方案进行改进,因此我的原始解决方案仍然有效:)。同意吗?
      • 这个解决方案非常好,因为它可以处理多种类型的对象。假设你有A = [123, "test", [], some_object, nil]B = A#because I am lazy,那么A.uniq.sort 会抛出错误(字符串和数组的比较失败)。
      • 这会是 O(n),因为它取决于数组大小吗? (线性)
      • 如果数组的大小相同但重复的元素不相同,则不起作用。例如A = [1, 1, 2]B = [1, 2, 2]
      【解决方案6】:

      速度对比

      require 'benchmark/ips'
      require 'set'
      
      a = [1, 2, 3, 4, 5, 6]
      b = [1, 2, 3, 4, 5, 6]
      
      Benchmark.ips do |x|
        x.report('sort')   { a.sort == b.sort }  
        x.report('sort!')  { a.sort! == b.sort! }  
        x.report('to_set') { a.to_set == b.to_set }  
        x.report('minus')  { ((a - b) + (b - a)).empty? }  
      end  
      
      Warming up --------------------------------------
                  sort    88.338k i/100ms
                 sort!   118.207k i/100ms
                to_set    19.339k i/100ms
                 minus    67.971k i/100ms
      Calculating -------------------------------------
                  sort      1.062M (± 0.9%) i/s -      5.389M in   5.075109s
                 sort!      1.542M (± 1.2%) i/s -      7.802M in   5.061364s
                to_set    200.302k (± 2.1%) i/s -      1.006M in   5.022793s
                 minus    783.106k (± 1.5%) i/s -      3.942M in   5.035311s
      

      【讨论】:

      • btw 元素的顺序不会影响sort 的速度
      • 令我惊讶...由于集合查找 O(n) 的时间复杂度,我预计 by-set 比较会优于所有其他比较。因此,任何实现良好的排序都需要 O(n logn)。而转换为集合和查找值总体上会在 O(n) 时间内完成。
      • 我希望 to_set 在足够大的数组中表现出色,其中 O(n logn) 开始比将数组转换为集合所需的努力更重要
      • 这很有帮助,但它本身并不是一个真正的答案?或许将其添加到现有解决方案中更好?
      【解决方案7】:

      ab的元素为Comparable时,

      a.sort == b.sort
      

      根据@steenslag 的评论更正@mori 的回答

      【讨论】:

      • ...当ab可以排序时。
      【解决方案8】:

      一种方法是遍历没有重复的数组

      # assume array a has no duplicates and you want to compare to b
      !a.map { |n| b.include?(n) }.include?(false)
      

      这将返回一个 true 数组。如果出现任何 false,则外部 include? 将返回 true。因此,您必须反转整个事物以确定它是否匹配。

      【讨论】:

      • @Victor Moroz,你是对的,频率计数只是 O(n)。
      【解决方案9】:

      如果您期望[:a, :b] != [:a, :a, :b] to_set 不起作用。您可以改用频率:

      class Array
        def frequency
          p = Hash.new(0)
          each{ |v| p[v] += 1 }
          p
        end
      end
      
      [:a, :b].frequency == [:a, :a, :b].frequency #=> false
      [:a, :b].frequency == [:b, :a].frequency #=> true
      

      【讨论】:

      • 如果他关心频率,为什么不只是a.sort == b.sort
      • @fl00r 如果项目不可比较怎么办? ["", :b].frequency == [:b, ""].frequency #=> true
      • 你也可以像a.group_by{|i| i} == b.group_by{|i| i}一样做一些功能性的事情
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-29
      • 2014-11-05
      • 2014-02-03
      • 1970-01-01
      • 2021-10-21
      • 2017-04-25
      相关资源
      最近更新 更多