How to do sane "set-difference" in Ruby?

Zabba picture Zabba · Jun 28, 2012 · Viewed 10.3k times · Source

Demo (I expect result [3]):

[1,2] - [1,2,3] => []    # Hmm
[1,2,3] - [1,2] => [3]   # I see

a = [1,2].to_set   => #<Set: {1, 2}>
b = [1,2,3].to_set => #<Set: {1, 2, 3}>
a - b              => #<Set: {}>  WTF!

And:

[1,2,9] - [1,2,3] => [9]  # Hmm. Would like [[9],[3]]

How is one to perform a real set difference regardless of order of the inputs?

Ps. As an aside, I need to do this for two 2000-element arrays. Usually, array #1 will have fewer elements than array #2, but this is not guaranteed.

Answer

Jon Gauthier picture Jon Gauthier · Jun 28, 2012

The - operator applied to two arrays a and b gives the relative complement of b in a (items that are in a but not in b).

What you are looking for is the symmetric difference of two sets (the union of both relative complements between the two). This will do the trick:

a = [1, 2, 9]
b = [1, 2, 3]
a - b | b - a          # => [3, 9]

If you are operating on Set objects, you may use the overloaded ^ operator:

c = Set[1, 2, 9]
d = Set[1, 2, 3]
c ^ d                  # => #<Set: {3, 9}>

For extra fun, you could also find the relative complement of the intersection in the union of the two sets:

( a | b ) - ( a & b )  # => #<Set: {3, 9}>