4

[1,2,3,3] - [1,2,3] produces the empty array []. Is it possible to retain duplicates so it returns [3]?

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 Answers3

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

Credit:

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