Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delete element of array while iterating

Tags:

ruby

I am iterating over a nested array with two each blocks, and deleting an element from the same array inside the inner iteration:

arr = [1,2,3]  
arr.each do |x|  
  arr.each do |y|  
    puts "#{arr.delete(y)}"  
  end  
end

This produces the results 1, 3. The array becomes [2].

Why isn't the value 2 passed to the first or the second loop? Is this some sort of side effect of nested iteration?

like image 926
Abhishek Avatar asked Feb 19 '16 07:02

Abhishek


People also ask

How do you delete an element from an array loop?

Use unset() function to remove array elements in a foreach loop. The unset() function is an inbuilt function in PHP which is used to unset a specified variable. The behavior of this function depends on different things.

Can we remove from list while iterating?

In Java 8, we can use the Collection#removeIf API to remove items from a List while iterating it.

Can we delete element from array?

Java arrays do not provide a direct remove method to remove an element. In fact, we have already discussed that arrays in Java are static so the size of the arrays cannot change once they are instantiated. Thus we cannot delete an element and reduce the array size.

How do you take a loop in an array?

For Loop to Traverse Arrays. We can use iteration with a for loop to visit each element of an array. This is called traversing the array. Just start the index at 0 and loop while the index is less than the length of the array.


3 Answers

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
=> []
like image 87
guitarman Avatar answered Oct 02 '22 23:10

guitarman


It has nothing to do with nesting. In fact, you will get the same result with just the inner loop:

arr = [1,2,3]  
arr.each do |y|  
  puts "#{arr.delete(y)}"  
end  
# => outputs 1, 3
a # => [2]

The compication is due to modifying the array during iteration.


The reason is because Array#each is based on the index. First, x becomes 1 (which is entirely irrelevant to the result). Within the inner loop, first you have:

  • a: [1, 2, 3], index: 0, y: 1

where index is the index on which the inner iteration is based, and you delete y and you get:

  • a: [2, 3]

In the next inner iteration, you have:

  • a: [2, 3], index: 1, y: 3

Note that 2 is skipped because iteration is based on the index (1). Then, 3 is deleted, which gives:

  • a: [2].

When the outer loop tries the next iteration at index 1, there is not enough elements left in a, so it ends there.

like image 43
sawa Avatar answered Oct 03 '22 00:10

sawa


To understand this case, let us take case of a simple array being traversed with index.

You have an array with [1,2,3].

When you start iteration with 0, current element is 1. Now, you delete the element 1 at index 0, your array will become [2,3].

In the next iteration, your index will be 1 and that will point to 3. And 3 will be deleted. Your array will be [2].

Now, your index is 2 and array has length 1. So, nothing will happen. Now, when this inner loop will complete, outer loop will resume at updated index 1 and then to 2. And as array has length of 1, they will not be executed.

So, going by this, it seems that is using index as iteration.

As per my knowledge, it should have undefined behaviour (like in C++ such code is not recommended). Because, while iterating if you delete the current element, it will corrupt the pointer value (currently being held in the parameter of function block passed to each).

like image 32
doptimusprime Avatar answered Oct 03 '22 01:10

doptimusprime