检查两个数组的内容是否相同(以任意顺序)


89

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

我的第一个念头是

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

但我想知道是否有更有效或惯用的方式来做到这一点。



尝试array.should =〜another_array检查stackoverflow.com/questions/2978922/…–
雅典娜

您可能通过以下方法避免了很多混乱:1)说明数组的元素是否必须可排序;2)提供一个简单的例子来阐明你的意思,“两个数组是否有相同的元素”(例如,做[1,2][2,1,1]有相同的元素)?
卡里Swoveland

引入了Ruby 2.6,difference它提供了非常快速且易读的解决方案。更多信息在这里。
SRack,

Answers:


140

这不需要转换即可设置:

a.sort == b.sort

没有转换?那是.uniq.sort什么 除了uniq类似于to_set内部加其他.to_a.sort
维克托·莫罗兹

接受它是因为它最接近我最终使用的内容,尽管没有uniqs。实际上,我最终使用创建了一个数组Range#to_a,所以我只需要创建sort另一个。
Taymon

11
如果数组包含无法简单排序的元素(例如,哈希数组),则此方法将无效。sahil dhankhar的解决方案似乎是一个更通用的解决方案。
布拉德

40

对于两个数组A和B:如果满足以下条件,则A和B的内容相同: (A-B).blank? and (B-A).blank?

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

同样如@ cort3z所建议的,此解决方案als0适用于多态数组,即

 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` 

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

正如评论中所建议的那样,上述解决方案对于重复项将失败。他在检查之前使用.uniq运算符的正确答案,也掩盖了重复项。)但是,即使您对重复项感兴趣,只需添加计数检查即可解决该问题(根据问题,只有一个数组可以包含重复项)。因此,最终的解决方案将是: A.size == B.size and ((A-B) + (B-A)).blank?


如果任何一个数组包含重复项,这将失败。例如,如果A=[1]B=[1,1],两者(A-B)(B-A)都将返回空白。请参阅阵列文档
jtpereyda

@dafrazzman完全同意您的看法。我已经修改了我的答案以合并您的反馈。但是,如果您仔细查看问题(或接受的答案),则问问者正在使用:a.to_set == b.to_set 并且接受的答案正在被使用,a.uniq.sort == b.uniq.sort并且给出的结果与((A-B) + (B-A)).blank?A = [1] 完全相同并且B = [1,1]同意吗?由于他只是想对原始解决方案进行改进,因此我的原始解决方案仍然有效:)。同意?
Sahil Dhankhar

1
该解决方案非常不错,因为它可以处理多种类型的对象。假设您有A = [123, "test", [], some_object, nil]B = A#because I am lazy,然后A.uniq.sort将引发错误(字符串和数组比较失败)。
Automatico

那么这将是O(n),因为它取决于数组的大小?(线性)
user3007294 2016年

1
如果数组具有相同的大小,但是重复的元素不相同,则将不起作用。例如,A = [1, 1, 2]以及B = [1, 2, 2]
Boudi

23

速度比较

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

元素的顺位顺序不影响其sort速度
Morozov

令我惊讶的是,由于集合查找O(n)的时间复杂性,我期望按集合进行的比较优于所有其他方法。因此,任何实现良好的排序都需要O(n logn)。而强制转换为集合并查找值将使其整体花费O(n)时间。
Oleg Afanasyev '18年

1
我希望to_set使用足够大的数组开始表现
出色

1
这是有帮助的,但实际上并不是答案吗?也许将其添加到现有解决方案中更好?
SRack,

18

Ruby 2.6以上

Ruby difference在2.6中引入。

这提供了一个非常快速,易读的解决方案,如下所示:

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 = 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? }
end

      sort     13.908k  2.6%) i/s -     69.513k in   5.001443s
     sort!     14.656k  3.0%) i/s -     73.736k in   5.035744s
    to_set     5.125k   2.9%) i/s -     26.023k in   5.082083s
     minus     16.398k  2.2%) i/s -     83.181k in   5.074938s
difference     27.839k  5.2%) i/s -    141.048k in   5.080706s

希望能对某人有所帮助!


2
在这种情况下,差异是打破a = [1,2,3] b = [1,2,3,4,5] a.difference(b).any?=>错误
错误404,


8

如果您期望[: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 2012年

4
@ fl00r如果商品不可比较怎么办?["", :b].frequency == [:b, ""].frequency #=> true
维克多·莫罗兹

2
您也可以做一些有用的事情,例如a.group_by{|i| i} == b.group_by{|i| i}
fl00r 2012年

7

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

( array1 & array2 ) == array1

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

分析:考虑到顺序不变,我猜想这是作为两次迭代实现的,因此始终如一O(n*n),特别是对于大型数组,a1.sort == a2.sort这比在最坏情况下应该执行的效果更糟O(n*logn)


2
并不总是有效:a1 = [1,2,3], a2 = [2, 1, 3] a1 && a2回报[2,1,3]给我的不等于a1
Kalyan Raghu

@Kaylan,你不是说它只有在a1==a2什么时候才有效吗?如果array1将等式右边的替换为array2,则可能会起作用,但是我怀疑由返回的元素的顺序&是否得到保证。
卡里·斯沃夫兰

1
@KalyanRaghu &是数组的交集运算符,&&逻辑与-它们是非常不同的!
Kimball

2

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

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

这将返回一个true数组。如果出现任何错误,则外部include?将返回true。因此,您必须将整个内容倒置以确定是否匹配。


@Victor Moroz,您是正确的,频率计数只是O(n)。
罗恩

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.