78

I have an array that I want to iterate over and delete some of the elements. This doesn't work:

a = [1, 2, 3, 4, 5]
a.each do |x|
  next if x < 3
  a.delete x
  # do something with x
end
a #=> [1, 2, 4]

I want a to be [1, 2]. How can I get around this?

1

4 Answers 4

130

a.delete_if { |x| x >= 3 }

See method documentation here

Update:

You can handle x in the block:

a.delete_if do |element|
  if element >= 3
    do_something_with(element)
    true # Make sure the if statement returns true, so it gets marked for deletion
  end
end
Sign up to request clarification or add additional context in comments.

3 Comments

I need to do something with x if it gets deleted. Should I put that in the block?
@Adrian Or you could use reject!.
One thing to keep in mind is that reject! will return nil if no elements were rejected, while delete_if returns the original array.
7

You don't have to delete from the array, you can filter it so:

a = [1, 2, 3, 4, 5]

b = a.select {|x| x < 3}

puts b.inspect # => [1,2]

b.each {|i| puts i} # do something to each here

3 Comments

Sorry to raise a zombie post but curious as to whether the b = a subset of a approach increases memory usage or whether it uses internal pointers or something to refer to the original a items? Reason I ask is my use case is fairly memory sensitive so curious as to whether deleting from the original array or select is a better method...
@gorlaz .select returns a new ruby object which will use more memory. See this example pastebin.com/23e10bKy
This is also more "functional" in the sense that data is not altered.
5

I asked this question not long ago.

Deleting While Iterating in Ruby?

It's not working because Ruby exits the .each loop when attempting to delete something. If you simply want to delete things from the array, delete_if will work, but if you want more control, the solution I have in that thread works, though it's kind of ugly.

1 Comment

The deletion doesn't break the loop. each effectively iterates by using the index. So if you delete, the indexes are different, and the next iteration will use the new indexes. Try this: arr = [1,2,3,4,5,6]; arr.each_with_index {|e,i| p [arr, e, i]; next if e < 3; arr.delete e }. When item 3 is reached, the index is 2. After 3 is deleted, the next iteration increments index to 3, and the item is 5. So 4 is skipped. 6 is skipped for the same reason.
3

Another way to do it is using reject!, which is arguably clearer since it has a ! which means "this will change the array". The only difference is that reject! will return nil if no changes were made.

a.delete_if {|x| x >= 3 }

or

a.reject! {|x| x >= 3 }

will both work fine.

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.