Однако итератор запускается только один раз. второе значение никогда не доступно
Потому что его там нет . И это хорошо видно, если вы сохраните массив, который вы перебираете, в место, которое вы можете посмотреть позже (memo
в моем примере):
ids = [1]
memo = ids
ids.each do |id|
# Do some things
ids |= search_for_more_ids(id)
# ids is now [1,2] but the loop still exists on the first iteration
end
p memo # [1]
p ids # [1, 2]
В целом, изменение коллекции, через которую вы проходите, в то же время очень подвержено ошибкам, а для некоторых коллекций даже невозможно. Погружение в такие хаки может стоить повышения производительности, но вам, вероятно, сначала нужно рабочее решение. Начните с этого.
Чтобы сделать это правильно Я бы, вероятно, использовал правильные структуры данных для задания: набор для отслеживания уже выполненных поисков и очередь для отслеживания оставшихся поисков (инициализируется одним значением 1
). И полученный алгоритм в значительной степени объясняет себя:
require "set"
require "queue"
processed = Set.new
to_process = Queue.new
to_process.push(1) # Enqueue the initial id to search
loop do
break if to_process.empty?
id = to_process.pop
next unless processed.add?(id) # returns `nil` if it's already there
search_for_more_ids(id).each do |new_id|
to_process.push(new_id)
end
end
Вы получите свой результат в Set
под названием processed
.
Это также, вероятно, быстрее, чем ваш подход, поскольку он устраняет дубликаты без выделения промежуточных контейнеров; через множество поисков. Но это зависит от размеров данных, с которыми вы имеете дело (общее количество идентификаторов, длина отдельных результатов поиска). Некоторые углы могут быть обрезаны в зависимости от специфики. Например, вы можете изменить алгоритм, чтобы дубликаты вообще не проходили через очередь & mdash; что-то, чего я умышленно избегал, чтобы код был чистым