views:

104

answers:

3

The canonical Array difference example in Ruby is:

[ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ]  #=>  [ 3, 3, 5 ]

What's the best way to get the following behavior instead?

[ 1, 1, 2, 2, 3, 3, 4, 5 ].subtract_once([ 1, 2, 4 ])  #=>  [ 1, 2, 3, 3, 5 ]

That is, only the first instance of each matching item in the second array is removed from the first array.

+1  A: 

This is all I can think of so far:

[1, 2, 4].each { |x| ary.delete_at ary.index(x) }
yjerem
That might get a bit slow if `m` (the size of [1,2,4]) is big
glebm
+2  A: 

In O(n log m + m log m) time:

require 'set'

class Array
  def subtract_once(*values)
    values = Set.new values 
    reject { |e| values.include?(e) && values.delete(e) }
  end
end
glebm
I'm going to accept this but it would be nice if the argument could contain duplicate values which get applied in turn (they get squashed by the conversion to Set). Not sure how you can keep the duplicates while still keeping performance. (Also I wanted to accept an array and not values as separate arguments, but that's an easy change)
Tom Shaw
Well, you did it in your answer. Please note that if you pass `*b` instead of `b` you will still be able to accept arrays, but also anything that replies to `to_ary` or `to_splat` depending on your version of ruby
glebm
+1  A: 
class Array
  def subtract_once(b)
    h = b.inject({}) {|memo, v|
      memo[v] ||= 0; memo[v] += 1; memo
    }
    reject { |e| h.include?(e) && (h[e] -= 1) >= 0 }
  end
end

I believe this does what I want. Many thanks to @glebm

Tom Shaw
Didn't see this one -- it's good.
glebm
glebm