How can I use Array#delete while iterating over the array?
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
Delete element of array while iterating
It's because of the index of the deleted element. I added some output to show you:
arr = [1,2,3]
arr.each do |x|
puts "1: #{arr.inspect}, x: #{x}"
arr.each do |y|
puts "2: #{arr.inspect}, y: #{y}"
puts "#{arr.delete(y)}"
end
end
Result:
1: [1, 2, 3], x: 1
2: [1, 2, 3], y: 1
1
2: [2, 3], y: 3
3
=> [2]
The first deleted element is 1 (index is 0) in the inner each block. After the deletion 2 has the index 0 and now the each iteration goes to index 1 which is now element 3. 3 will be deleted and that's the end of the iteration. So you get [2].
The same happens without nested each:
arr = [1,2,3]
arr.each do |x|
puts "1: #{arr.inspect}, x: #{x}"
puts "#{arr.delete(x)}"
end
Result:
1: [1, 2, 3], x: 1
1
1: [2, 3], x: 3
3
=> [2]
I suggest to use reverse_each
for such operations to avoid this behavior:
arr = [1,2,3]
arr.reverse_each do |x|
puts "1: #{arr.inspect}, x: #{x}"
puts "#{arr.delete(x)}"
end
Result:
1: [1, 2, 3], x: 3
3
1: [1, 2], x: 2
2
1: [1], x: 1
1
=> []
Deleting While Iterating in Ruby?
use delete_if
array.delete_if do |v|
if v.should_be_deleted?
true
else
v.update
false
end
end
Ruby deleting array element while iterating
This is [part of] implementation of each
for (i=0; i<RARRAY_LEN(ary); i++) {
rb_yield(RARRAY_AREF(ary, i));
}
return ary;
So it simply moves a "reading head" along the array until it reaches the end.
When you delete "green"
, elements of the array are shifted to take its place and "blue"
is now where green was. But we already read the element at this location. Next element to be read is purple.
That is exactly why you should never mutate the collection you're iterating (unless this effect is what you actually desire).
I can't understand the native code
Here's a little visualization of that "reading head" mental model.
v
red green blue purple # print red
# next
v
red green blue purple # print green
v
red blue purple # delete green in the same iteration
# next
v
red blue purple # print purple
Delete array elements while looping in ruby
class Dog
attr_reader :array
def initialize
@array = [1, 2, 3]
end
def delete_entry(entry)
@array.delete(entry)
end
end
d = Dog.new
d.array.length.times do
d.delete_entry(d.array[0])
end
p d.array
--output:--
[]
I have read that removing elements in the midst of iteration is
disallowed by design in ruby.
Ridiculous. It just won't work as you may expect. Here's why:
deleting elements from an array while iterating over the array
Is it safe to delete from an Array inside each?
You should not rely on unauthorized answers too much. The answer you cited is wrong, as is pointed out by Kevin's comment to it.
It is safe (from the beginning of Ruby) to delete elements from an Array while each
in the sense that Ruby will not raise an error for doing that, and will give a decisive (i.e., not random) result.
However, you need to be careful because when you delete an element, the elements following it will be shifted, hence the element that was supposed to be iterated next would be moved to the position of the deleted element, which has been iterated over already, and will be skipped.
Ruby: deleting object while looping over list with that object
In Ruby you will actually find a glitch if you do what you are saying.
Try this:
objects = [1,2,3,4]
objects.each { |el| objects.delete(el) }
=> [2, 4]
You would expect the result to be an empty array, but is not. We are messing up with the elements of the arr
and each
gets confused, because the length of the array has changed. The each iterator looks something like this in pseudocode:
count = 0
while length(objects) > count
yield objects[count]
count + 1
end
So, in the example I shown above, the reason why we get [2, 4]
can be explained on a step by step analysis on what objects.each { |el| objects.delete(el) }
is doing:
- We start with 4 (length of objects) > 0.
- Number 1 is yielded, and deleted.
- count = 1
- 3 (length of objects) > 1
- Number 3 is yielded and deleted.
- count = 2
- 2 (length of objects) is not bigger than count
- We are done, so we have
[2, 4]
There is a better way to do what you are trying, by using delete_if:
new_objects = objects.delete_if {|o| o.withinBounds }
Ruby didn't loop all elements?
The fact that only about half (5/9) of your items have disappeared is a dead giveaway that the problem is deleting while iterating over the collection.
The iteration will be processing indexes 1, 2, 3, 4 and so on. If you delete index 2 while processing it, that will shift all later indexes down by one.
So, when you then move on to index 3 in the next iteration, you will skip the original index 3 because that will have been shifted down to index 2.
In other words, let's start with a simpler example with two consecutive items to remove:
index | 0 | 1 | 2 | 3 |
value | 1 | 7 | 7 | 9 |
You check the first index and the value is 1
so you do nothing. You then check the second index and the value is 7
so you delete it, giving:
index | 0 | 1 | 2 |
value | 1 | 7 | 9 |
You then check the third index and the value is 9
so you do nothing. You've also reached the end so it stops.
So you can see there that you actually skipped the second item you wanted to delete, because you shifted things around while iterating. This isn't a Ruby-specific problem, a great many languages have this same issue.
In general, each complete pair of adjacent items will only have the first of the pair deleted while an item on its own (not followed by another of the same value) will be deleted normally. That's why only 5/9
of your 7
s are deleted, one for each of the four pairs and the final standalone one.
The correct way (in Ruby) to delete all items of a single given value is to use the array delete method:
a.delete(7)
You can also use conditional delete for more complex conditions such as deleting everything greater than 7
:
a.delete_if {|val| val > 7}
And, if you really want to do it yourself (as an educational exercise), you just have to realise that the problem is because you process the array in a forward manner - when you do that, changes beyond where you're deleting may cause issues.
If you were to find some way to process the array in a backwards manner, this issue would not occur. Luckily, Ruby has such a beast:
a.to_enum.with_index.reverse_each do |item, index|
That line will process the array in such a way that deletions will not affect future operations. Just be aware that deleting while iterating can still be a problem if the data structure you're processing is not a simple indexed array.
I still warrant that delete
and delete_if
are the correct way to go since they're baked into Ruby already, and therefore incredibly unlikely to have bugs.
Ruby: What happens when I delete element of an array when iterating over the same array?
It is not safe. Do not change underlying container while you iterate it. This could cause unexpected behaviour.
For example, following code causes infinite loop:
a = [1]
a.each { a.push 1 }
You code can be written using Array#delete_if
:
array1 = [5, 4, 3, 1, 2, 7, 8]
min = 4
array1.delete_if { |arrayElement|
arrayElement < min
}
array # => [5, 4, 7, 8]
Related Topics
Jruby: Import VS Include VS Java_Import VS Include_Class
How to Get the Unique Elements from an Array of Hashes in Ruby
How to Bypass Mass Assignment Protection
Why Does Openuri Treat Files Under 10Kb in Size as Stringio
How to Access Class Variables in Ruby 1.9
Ruby on Rails: How to Check If a File Is an Image
How to Validate the Presence of a Belongs to Association with Rails
How to Install Ruby 1.9.3 on Ubuntu Without Rvm
Rails 4 User Roles and Permissions
Rails 3 Joins -- Select Only Certain Columns
Weird Rails Error "Permission Denied: Bin/Rails" for Old Rails Apps
Adding a Submit Button Image to a Rails Form
How to Handle Multiple Models in One Rails Form
Ruby Create Recursive Directory Tree
How to Convert a Net::Http Response to a Certain Encoding in Ruby 1.9.1
Rails 3.1 Actioncontroller::Routingerror (No Route Matches [Get] "/Assets/Rails.Png"):