SimonMayer
SimonMayer

Reputation: 4925

Comparing two arrays ignoring element order in Ruby

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?

Upvotes: 59

Views: 104696

Answers (8)

tokland
tokland

Reputation: 67900

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).

In Ruby 2.7+, this can be done with tally:

[1, 2, 1].tally == [2, 1, 1].tally
#=> true

For earlier Ruby versions: 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

Upvotes: 38

MMM
MMM

Reputation: 7310

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)

Upvotes: 50

bmalets
bmalets

Reputation: 3297

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

Upvotes: 0

Datt
Datt

Reputation: 881

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

Upvotes: 3

Lukasz Muzyka
Lukasz Muzyka

Reputation: 2783

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

Upvotes: 4

G. Allen Morris III
G. Allen Morris III

Reputation: 1042

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)

Upvotes: 0

Gunchars
Gunchars

Reputation: 11056

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)

Upvotes: 27

Tomislav Dyulgerov
Tomislav Dyulgerov

Reputation: 1026

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!

Upvotes: 6

Related Questions