Давайте шаг через него:
arr = [10, 20, 30, 40]
enum0 = arr.each
#=> #<Enumerator: [10, 20, 30, 40]:each>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [10, 20, 30, 40]:each>:with_index>
Мы можем увидеть содержимое enum1
путем преобразования его в массив:
enum1.to_a
#=> [[10, 0], [20, 1], [30, 2], [40, 3]]
Это говорит нам о том, что Enumerator#each (который будет вызывать Array#each) будет проходить четыре элемента перечислителя enum1
в блок, присваивая их по очереди блочным переменным. Первый:
elmt, i = enum1.next
#=> [10, 0]
puts elmt, i
# 10
# 0
elmt == 20
#=> false
так arr.delete_at(i)
не выполняется.
Ни arr
, ни enum1
были изменены:
arr
#=> [10, 20, 30, 40]
enum1.to_a
#=> [[10, 0], [20, 1], [30, 2], [40, 3]]
each
Сейчас проходит следующий элемент enum1
в блок:
elmt, i = enum1.next
#=> [20, 1]
elmt == 20
#=> true
так мы выполняем:
arr.delete_at(i)
#=> [10, 20, 30, 40].delete_at(1)
#=> 20
arr
#=> [10, 30, 40]
enum1.to_a
#=> [[10, 0], [30, 1], [40, 2]]
Ач ! Таким образом, счетчик был изменен, а также arr
. Это имеет смысл, потому что, когда создается счетчик, устанавливается ссылка на исходный приемник вместе с правилами для того, что с ним нужно делать. Поэтому изменения в ресивере будут влиять на счетчик.
Мы можем использовать Enumerator#peek, чтобы посмотреть, что будет следующий элемент enum1
, что each
будет проходить в блок:
enum1.peek
#=> [40, 2]
Таким образом, вы видите, что each
переходит к следующему индексированной позиции, не обращая внимания на то, что предыдущий элемент удален, что приводит к тому, что более поздние элементы сдвигаются на одну позицию, в результате чего each
пропускает [30,1]
.
elmt, i = enum1.next
#=> [40, 2]
elmt == 20
#=> false
arr
#=> [10, 30, 40]
enum1.to_a
#=> [[10, 0], [30, 1], [40, 2]]
На данный момент each
достигает конца счетчику, так что работа закончена. Поэтому возвращает оригинальный приемник, arr
, но который был изменен, так что мы получаем:
[10, 30, 40]
Лучший пример может быть:
arr = [10, 20, 20, 40]
где:
[10, 20, 40]
будет вернулся.
Изменение 'Array' (или вообще любой' Enumerable') при итерации по нему вызывает неопределенное поведение (по крайней мере, в MRI). [Cite] (https://groups.google.com/d/msg/ruby-core-google/wYAJdScsv9w/C-Yy9CgP9yIJ). Matz (создатель Ruby): «Неопределенное поведение итераторов, если блок изменяет итерационный объект, который может разбиться или упасть до бесконечного цикла ». –
@Holger хорошая информация. Хотелось бы, чтобы это было в документации для соответствующих методов. Разве я дошел до того, что назвал это ошибкой в документе? – user3546411