58

I need to check whether two arrays contain the same data in any order. Using the imaginary compare method, I would like to do:

arr1 = [1,2,3,5,4]
arr2 = [3,4,2,1,5]
arr3 = [3,4,2,1,5,5]

arr1.compare(arr2) #true    
arr1.compare(arr3) #false

I used arr1.sort == arr2.sort, which appears to work, but is there a better way of doing this?

lulalala
  • 17,572
  • 15
  • 110
  • 169
SimonMayer
  • 4,719
  • 4
  • 33
  • 45

8 Answers8

48

The easiest way is to use intersections:

@array1 = [1,2,3,4,5]
@array2 = [2,3,4,5,1]

So the statement

@array2 & @array1 == @array2

Will be true. This is the best solution if you want to check whether array1 contains array2 or the opposite (that is different). You're also not fiddling with your arrays or changing the order of the items.

You can also compare the length of both arrays if you want them to be identical in size:

@array1.size == @array2.size && @array1 & @array2 == @array1

It's also the fastest way to do it (correct me if I'm wrong)

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
MMM
  • 7,221
  • 2
  • 24
  • 42
  • 12
    So, for a complete answer, `a.size==b.size and a&b==a` implemented as a `compare(a,b)` method or a monkey patch. – Mark Thomas Feb 01 '12 at 12:22
  • `arr1 & arr3 == arr1` returns true, which I don't want. Mark's suggestion is an improvement, but it is worth noting that it doesn't cope very well with duplicate values. See example `arr4 = [1,2,3,5,4,4]; p arr4.size==arr3.size and arr4&arr3==arr4`, which returns true – SimonMayer Feb 01 '12 at 12:45
  • @SimonMayer I beg to differ. `arr3 = [3,4,2,1,5,5]; arr4 = [1,2,3,5,4,4]; p arr4 & arr3 == arr4` #=> **false** – Mark Thomas Feb 01 '12 at 19:58
  • @MarkThomas I used arr3 and arr4 to test your suggestion (involving `a.size==b.size and a&b==a`) - it gave an unexpected result. The example you use there does work for arr3 and arr4, but not `arr1 = [1,2,3,5,4]; arr3 = [3,4,2,1,5,5]; p arr1 & arr3 == arr1 #=> true` (should be false) – SimonMayer Feb 01 '12 at 20:50
  • @SimonMayer, I was disproving that you get true from the intersection. The *full* solution works in both cases. `arr1 =[1,2,3,5,4]; arr3 = [3,4,2,1,5,5]; p arr1.size==arr3.size and arr1 & arr3==arr1` #=> **false** – Mark Thomas Feb 01 '12 at 20:57
  • 1
    @MarkThomas It is working in some cases, but not all. Can you get the full solution to work for arr3 and arr4? I get `arr3 = [3,4,2,1,5,5]; arr4 = [1,2,3,5,4,4]; p arr3.size==arr4.size and arr3 & arr4==arr1 #=> true`. It should be false. – SimonMayer Feb 01 '12 at 21:10
  • 4
    No, it doesn't. `a = [1,2,3,4,5,1]; b = [1,1,2,3,4,5]; (a.size==b.size) && (a&b==a)` `=> false`, but should be `true` since the arrays contain the same items, just in differing orders. The union (`&`) solution cannot work elegantly if the arrays are allowed to contain duplicate entries. – Andrew Hodgkinson May 20 '14 at 23:54
  • 2
    This solution (even with the size check) does not work in all cases - the answer should be updated to clarify that so it doesn't send people awry (see SimonMayer's example of [3,4,2,1,5,5] and [1,2,3,5,4,4]) – David Ljung Madison Stellar Jan 06 '17 at 09:08
36

Sorting the arrays prior to comparing them is O(n log n). Moreover, as Victor points out, you'll run into trouble if the array contains non-sortable objects. It's faster to compare histograms, O(n).

You'll find Enumerable#frequency in Facets, but implement it yourself, which is pretty straightforward, if you prefer to avoid adding more dependencies:

require 'facets'
[1, 2, 1].frequency == [2, 1, 1].frequency 
#=> true
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
tokland
  • 66,169
  • 13
  • 144
  • 170
  • Thanks. This is the only solution that returns true for only the first `arr1` and `arr2`, when I compare each of these arrays: `arr1 = [1,2,3,5,4]; arr2 = [3,4,2,1,5]; arr3 = [3,4,2,1,5,5]; arr4 = [1,2,3,5,4,4]` - I'm not sure whether I would always go to the trouble of installing the facets gem, but if I'm using it anyway for something, this seems a much better method. – SimonMayer Feb 01 '12 at 13:07
  • 1
    Besides n log n `sort` doesn't work well with different types, e.g. if there are `nil`s. Frequency should work fine, good idea. – Victor Moroz Feb 01 '12 at 14:58
  • An easy implementation of 'frequency' is with inject: class Array; def frequency; self.inject(Hash.new(0)){|p,v| p[v]+=1; p} end; end – David Ljung Madison Stellar Jan 06 '17 at 09:23
  • 7
    The Ruby 2.7 equivalent will be `arr1.tally` == `arr2.tally` – inopinatus Sep 21 '19 at 22:31
24

If you know that there are no repetitions in any of the arrays (i.e., all the elements are unique or you don't care), using sets is straight forward and readable:

Set.new(array1) == Set.new(array2)
Gunchars
  • 9,555
  • 3
  • 28
  • 27
6

You can actually implement this #compare method by monkey patching the Array class like this:

class Array
  def compare(other)
    sort == other.sort
  end
end

Keep in mind that monkey patching is rarely considered a good practice and you should be cautious when using it.

There's probably is a better way to do this, but that's what came to mind. Hope it helps!

4

The most elegant way I have found:

arr1 = [1,2,3,5,4]
arr2 = [3,4,2,1,5]
arr3 = [3,4,2,1,5,5]


(arr1 - arr2).empty? 
=> true

(arr3 - arr2).empty?
=> false
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Lukasz Muzyka
  • 2,783
  • 1
  • 30
  • 41
  • 18
    i coded like this first, but this code has a serious exception. [1,1,1,1] - [1,2] = [], which is not same. – Canna May 06 '15 at 10:37
3

You can open array class and define a method like this.

class Array
  def compare(comparate)
    to_set == comparate.to_set
  end
end

arr1.compare(arr2)
irb => true

OR use simply

arr1.to_set == arr2.to_set
irb => true
Datt
  • 851
  • 9
  • 21
0

Here is a version that will work on unsortable arrays

class Array
  def unordered_hash
    unless @_compare_o && @_compare_o == hash
      p = Hash.new(0)
      each{ |v| p[v] += 1 }
      @_compare_p = p.hash
      @_compare_o = hash
    end
    @_compare_p
  end
  def compare(b)
    unordered_hash == b.unordered_hash
  end
end

a = [ 1, 2, 3, 2, nil ]
b = [ nil, 2, 1, 3, 2 ]
puts a.compare(b)
G. Allen Morris III
  • 1,012
  • 18
  • 30
0

Use difference method if length of arrays are the same https://ruby-doc.org/core-2.7.0/Array.html#method-i-difference

arr1 = [1,2,3]
arr2 = [1,2,4]
arr1.difference(arr2) # => [3]
arr2.difference(arr1) # => [4]

# to check that arrays are equal:
arr2.difference(arr1).empty?

Otherwise you could use

# to check that arrays are equal:
arr1.sort == arr2.sort
bmalets
  • 3,207
  • 7
  • 35
  • 64