Чистое решение этой рубиновой итераторной хитрости?

k = [1,2,3,4,5]
for n in k
  puts n
  if n == 2
    k.delete(n)
  end
end
puts k.join(",")

# Result:
# 1
# 2
# 4
# 5
# [1,3,4,5]

# Desired:
# 1
# 2
# 3
# 4
# 5
# [1,3,4,5]

Этот же эффект происходит с другим итератором массива, k.each:

k = [1,2,3,4,5]
k.each do |n|
  puts n
  if n == 2
    k.delete(n)
  end
end
puts k.join(",")

имеет тот же результат.

Причина, по которой это происходит, довольно ясна... Ruby фактически не перебирает объекты, хранящиеся в массиве, а просто превращает их в симпатичный итератор индекса массива, начиная с индекса 0 и каждый раз увеличивая индекс пока он не закончится. Но когда вы удаляете элемент, он все равно увеличивает индекс, поэтому он не оценивает один и тот же индекс дважды, и я хочу его.

Это может быть не то, что происходит, но это лучшее, что я могу придумать.

Есть ли чистый способ сделать это? Есть ли уже встроенный итератор, который может это сделать? Или мне придется загрязнить его и сделать итератор индекса массива, а не увеличивать, когда элемент будет удален? (или итерации через клон массива и удаление из исходного массива)


Разъяснение

Я не просто хочу удалять элементы из массива; извините, если это было ясно. То, что я хотел бы сделать, это перебрать каждый элемент и "обработать" его; этот процесс иногда может удалить его. Чтобы быть более точным:

class Living_Thing

  def initialize tracker,id
    @tracker = tracker
    @id = id

    @tracker << self
  end

  def process
    do_stuff
    puts @id
    if @id == 2
      die
    end
  end

  def die
    do_stuff_to_die
    @tracker.delete(self)
  end

  def inspect
    @id
  end
end

tracking_array = Array.new()

foo = Living_Thing.new(tracking_array,1)
bar = Living_Thing.new(tracking_array,2)
rab = Living_Thing.new(tracking_array,3)
oof = Living_Thing.new(tracking_array,4)

puts tracking_array.join(",")              # => [1, 2, 3, 4]

for n in tracking_array
  n.process
end

# result: only foo, bar, and oof are processed

В идеале, я хочу, чтобы все элементы в tracking_array обрабатывались.

Когда Living_Thing удаляется из track_array, необходимо вызывать Living_Thing # die; do_stuff_to_die очищает вещи, которые нужно класть вверх.

Ответ 1

это может быть более подходящим для обработки (Ref обновленное разъяснение)

k = [1,2,3,4,5] 
k.dup.each do |n| 
  puts n 
  if n == 2
    k.delete(n) 
  end 
end 
puts k.join(",")

он ставит вопрос, который у вас был (об итерации через объекты против итерации по индексам)

Ответ 2

Это ошибка на большинстве языков, чтобы мутировать коллекцию во время ее итерации. В большинстве языков решение состоит в том, чтобы создать копию и изменить ее или создать список индексов и выполнить операции над этими индексами, когда вы закончите итерацию, но итераторы Ruby дают вам немного больше. Несколько решений очевидны. Самая идиоматическая ИМО:

puts k
puts k.reject {|n| n == 2}.join(',')

Более непосредственно переведенный из вашего примера:

k.delete_if do |n|
  puts n
  n == 2
end
puts k.join(',')

(delete_if - это в основном деструктивная версия reject, которая возвращает массив объектов, для которых блок не возвращал значение true.)

Ответ 3

Хорошо, так скажем, вы хотите удалить все 2 из вашего массива:

arr = [1,2,3,4,5]
arr.delete(2)
puts arr.join(", ")
# => "1, 3, 4, 5"
arr = [1,2,3,2,4,2,5,2]
arr.delete(2)
puts arr.join(", ")
# => "1, 3, 4, 5"

Но я подозреваю, что вы хотите итерации, поэтому я бы:

arr = [1,2,3,4,5]
arr.each {|x| a[a.index(x)] = nil if x == 2}.compact!
Может быть, это слишком грязно? Назначение нильу сохраняет правильное количество итераторов, а compact! уничтожает нули после факта. Курс map держит его немного короче и чище:
arr.map {|x| x if x != 2}.compact!