Удалить элемент массива при переборе
Я перебираю вложенный массив с двумя 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