70

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?

2
  • if you subtract [1,1,3] do you want to end up with b = [1,2,2]? Or is that never going to happen? Commented Jan 19, 2012 at 17:16
  • For people googling remove all elements from an array: array.clear Commented Sep 3, 2024 at 14:44

6 Answers 6

82

You may do:

a= [1,1,1,2,2,3]
delete_list = [1,3]
delete_list.each do |del|
    a.delete_at(a.index(del))
end

result : [1, 1, 2, 2]

Sign up to request clarification or add additional context in comments.

5 Comments

So array-subtraction is actually disjoint-Set?
Array Difference---Returns a new array that is a copy of the original array, removing any items that also appear in other_ary. (If you need set-like behavior, see the library class Set.) link
If you wanted to remove all instances of items in delete_list, you could do this. b = a.each.reject{|x| delete_list.each.include? x}
I'm guessing I'm misreading it, but why wouldn't the result be [1,1,2,3] ? if you are removing indexes 1 and 3 from a ?
@moopasta, because he's deleting the first occurring 1 and 3, from beginning to end of array. He's actually calling a delete_at, which deleted the index of the a Array with the FIRST OCCURRENCE of the 1, and then the 3. So he's, in this case, issuing a a.delete_at(0) and then a a.delete_at(4)
8
[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

Comments

3

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]

1 Comment

No, order could be altered. Looks like Norm212 does preserve order.
2

A simple solution I frequently use:

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

arr[1..-1]

=> [3,4,2,45]

Comments

0

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 }

Comments

-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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.