Чистое решение этой рубиновой итераторной хитрости?
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!