[ruby] Removing elements from array Ruby

Let's say I am trying to remove elements from array a = [1,1,1,2,2,3]. If I perform the following:

b = a - [1,3]

Then I will get:

b = [2,2]

However, I want the result to be

b = [1,1,2,2]

i.e. I only remove one instance of each element in the subtracted vector not all cases. Is there a simple way in Ruby to do this?

This question is related to ruby arrays

The answer is


For speed, I would do the following, which requires only one pass through each of the two arrays. This method preserves order. I will first present code that does not mutate the original array, then show how it can be easily modified to mutate.

arr = [1,1,1,2,2,3,1]
removals = [1,3,1]

h = removals.group_by(&:itself).transform_values(&:size)
  #=> {1=>2, 3=>1} 
arr.each_with_object([]) { |n,a|
  h.key?(n) && h[n] > 0 ? (h[n] -= 1) : a << n }
  #=> [1, 2, 2, 1]

arr
  #=> [1, 1, 1, 2, 2, 3, 1] 

To mutate arr write:

h = removals.group_by(&:itself).transform_values(&:count)
arr.replace(arr.each_with_object([]) { |n,a|
  h.key?(n) && h[n] > 0 ? (h[n] -= 1) : a << n })
  #=> [1, 2, 2, 1]

arr
  #=> [1, 2, 2, 1]

This uses the 21st century method Hash#transform_values (new in MRI v2.4), but one could instead write:

h = Hash[removals.group_by(&:itself).map { |k,v| [k,v.size] }]

or

h = removals.each_with_object(Hash.new(0)) { | n,h| h[n] += 1 }

a = [1,1,1,2,2,3]
a.slice!(0) # remove first index
a.slice!(-1) # remove last index
# a = [1,1,2,2] as desired

A simple solution I frequently use:

arr = ['remove me',3,4,2,45]

arr[1..-1]

=> [3,4,2,45]

[1,3].inject([1,1,1,2,2,3]) do |memo,element|
  memo.tap do |memo|
    i = memo.find_index(e)
    memo.delete_at(i) if i
  end
end

Not very simple but:

a = [1,1,1,2,2,3]
b = a.group_by {|n| n}.each {|k,v| v.pop [1,3].count(k)}.values.flatten
=> [1, 1, 2, 2]

Also handles the case for multiples in the 'subtrahend':

a = [1,1,1,2,2,3]
b = a.group_by {|n| n}.each {|k,v| v.pop [1,1,3].count(k)}.values.flatten
=> [1, 2, 2]

EDIT: this is more an enhancement combining Norm212 and my answer to make a "functional" solution.

b = [1,1,3].each.with_object( a ) { |del| a.delete_at( a.index( del ) ) }

Put it in a lambda if needed:

subtract = lambda do |minuend, subtrahend|
  subtrahend.each.with_object( minuend ) { |del| minuend.delete_at( minuend.index( del ) ) }
end

then:

subtract.call a, [1,1,3]