[1,2,3,3] - [1,2,3]
produces the empty array []
. Is it possible to retain duplicates so it returns [3]
?
Asked
Active
Viewed 1,060 times
4

osman
- 2,335
- 18
- 25
-
4`Array#-` is a *set difference* operator. There is no standard method for this, but yes "it is possible". – user2864740 May 25 '15 at 00:09
-
I don't think you can do this using some built-in array method. But you can always write your own. – Sergio Tulentsev May 25 '15 at 00:09
-
You may find http://stackoverflow.com/questions/4731553/in-ruby-is-there-a-way-to-remove-only-1-match-in-an-array-easily useful in the implementation. – user2864740 May 25 '15 at 00:13
3 Answers
5
I am so glad you asked. I would like to see such a method added to the class Array
in some future version of Ruby, as I have found many uses for it:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
A description of the method and links to some of its applications are given here.
By way of example:
a = [1,2,3,4,3,2,4,2]
b = [2,3,4,4,4]
a - b #=> [1]
a.difference b #=> [1,2,3,2]
Ruby v2.7 gave us the method Enumerable#tally, allowing us to replace the first line of the method with
h = other.tally

Cary Swoveland
- 106,649
- 6
- 63
- 100
1
As far as I know, you can't do this with a built-in operation. Can't see anything in the ruby docs either. Simplest way to do this would be to extend the array class like this:
class Array
def difference(array2)
final_array = []
self.each do |item|
if array2.include?(item)
array2.delete_at(array2.find_index(item))
else
final_array << item
end
end
end
end
For all I know there's a more efficient way to do this, also

Ben Muschol
- 608
- 1
- 5
- 21
-
You need to add `final_array` after your antepenultimate `end`. – Cary Swoveland May 25 '15 at 01:30
0
EDIT: As suggested by user2864740 in question comments, using Array#slice! is a much more elegant solution
def arr_sub(a,b)
a = a.dup #if you want to preserve the original array
b.each {|del| a.slice!(a.index(del)) if a.include?(del) }
return a
end
My original answer
def arr_sub(a,b)
b = b.each_with_object(Hash.new(0)){ |v,h| h[v] += 1 }
a = a.each_with_object([]) do |v, arr|
arr << v if b[v] < 1
b[v] -= 1
end
end
arr_sub([1,2,3,3],[1,2,3]) # a => [3]
arr_sub([1,2,3,3,4,4,4],[1,2,3,4,4]) # => [3, 4]
arr_sub([4,4,4,5,5,5,5],[4,4,5,5,5,5,6,6]) # => [4]

Community
- 1
- 1

Edgar Mkaka Joel
- 96
- 4
-
Except you are now mutating `a`, so you should operate on `a.dup`. – Cary Swoveland May 25 '15 at 16:26
-
I didn't catch that bug. I made your suggested changes to now operate on `a.dup`. Thank you @CarySwoveland – Edgar Mkaka Joel May 25 '15 at 17:17