Удалить элемент массива при переборе

Я перебираю вложенный массив с двумя each блоки, и удаления элемента из того же массива внутри внутренней итерации:

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

это дает результаты 1, 3. Массив становится [2].

почему не значение 2 перешел на первый или второй цикл? Это какой-то побочный эффект вложенной итерации?

4 ответов


это из-за индекса удаленного элемента. Я добавил некоторые выходные данные, чтобы показать вам:

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

результат:

1: [1, 2, 3], x: 1
2: [1, 2, 3], y: 1
1
2: [2, 3], y: 3
3
=> [2]

первый удаленный элемент равен 1 (индекс равен 0) во внутреннем каждом блоке. После удаления 2 имеет индекс 0, и теперь каждая итерация переходит к индексу 1, который теперь является элементом 3. 3 будет удалено, и это конец итерации. Итак, вы получаете [2].

то же самое происходит и без вложенных каждый:

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

результат:

1: [1, 2, 3], x: 1
1
1: [2, 3], x: 3
3
=> [2]

Я предлагаю использовать reverse_each для таких операций, чтобы избежать этого:

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

результат:

1: [1, 2, 3], x: 3
3
1: [1, 2], x: 2
2
1: [1], x: 1
1
=> []

это не имеет ничего общего с вложением. Фактически, вы получите тот же результат только с внутренним циклом:

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

в compication из-за изменения массива во время итерации.


причина в том, что Array#each основано на индексе. Во-первых, x становится 1 (которая совершенно не имеет отношения к результату). Во внутреннем цикле сначала у вас есть:

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

здесь index - это индекс, на котором основана внутренняя итерация, и вы удаляете y а также вы получаете:

  • a: [2, 3]

в ближайшие внутренней итерации, у вас есть:

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

отметим, что 2 пропускается, потому что итерация основана на индексе (1). Затем:3 удалял, что дает:

  • a: [2].

когда внешний цикл пытается выполнить следующую итерацию по индексу 1, осталось недостаточно элементов в a, на этом все и заканчивается.


чтобы понять этот случай, возьмем случай простого массива, проходимого с индексом.

у вас есть массив с [1,2,3].

когда вы начинаете итерацию с 0, текущий элемент равен 1. Теперь, вы удаляете элемент 1 в индексе 0, Ваш массив будет [2,3].

на следующей итерации ваш индекс будет равен 1, и это будет указывать на 3. И 3 будут удалены. Ваш массив будет [2].

теперь ваш индекс равен 2, а массив имеет длину 1. Так что ничего не случится. Теперь, когда этот внутренний цикл завершится, внешний цикл возобновится с обновленным индексом 1, а затем до 2. И как массив имеет длину 1, они не будут выполняться.

Итак, исходя из этого, кажется, что использует индекс в качестве итерации.

насколько мне известно, он должен иметь неопределенное поведение (например, в C++ такой код не рекомендуется). Потому что при итерации при удалении текущего элемента он повредит значение указателя (в настоящее время удерживается в параметр функционального блока, переданного в each).


С each повторяется с индексами, и вы удаляете элемент во внутреннем loop на каждый другой элемент в следующей итерации. если вы увеличите количество элементов и включите текущий индекс итерации в цикл, вы сможете увидеть большую картину.

arr = [1,2,3,4,5,6,7,8,9]  
arr.each_with_index do |x,ix|  
  puts "loop1: #{arr.inspect}, x: #{x}, ix: #{ix}"
  arr.each_with_index do |y, iy|  
    puts "loop2: #{arr.inspect}, y: #{y}, iy: #{iy}"
    puts "#{arr.delete(y)}"  
  end  
end

результат

loop1: [1, 2, 3, 4, 5, 6, 7, 8, 9], x: 1, ix: 0
loop2: [1, 2, 3, 4, 5, 6, 7, 8, 9], y: 1, iy: 0
1
loop2: [2, 3, 4, 5, 6, 7, 8, 9], y: 3, iy: 1
3
loop2: [2, 4, 5, 6, 7, 8, 9], y: 5, iy: 2
5
loop2: [2, 4, 6, 7, 8, 9], y: 7, iy: 3
7
loop2: [2, 4, 6, 8, 9], y: 9, iy: 4
9
loop1: [2, 4, 6, 8], x: 4, ix: 1
loop2: [2, 4, 6, 8], y: 2, iy: 0
2
loop2: [4, 6, 8], y: 6, iy: 1
6
 => [4, 8] 

после удаления во время цикла и после каждой итерации индекс увеличивается, но массив на один элемент короче, так, он удаляет следующий (и все) соответствующие элементы доступны и в конце цикла сравнивает и останавливает цикл, когда index >= length