Итератор не включает значения, добавленные к нему внутри цикла - PullRequest
0 голосов
/ 26 апреля 2018

у меня есть:

ids = [1]

Я делаю ids.each do |id|, чтобы перебрать ids и найти дочерние идентификаторы для добавления к ids. Вот мой код:

ids.each do |id|
  # Do some things
  ids |= search_for_more_ids(id)
end

Во время первой итерации ids получает второе значение и становится [1, 2]. Но цикл все еще существует на первой итерации. Итератор запускается только один раз; второе значение никогда не доступно. Это странно, потому что раньше это работало.

Любая помощь будет оценена.

Ответы [ 2 ]

0 голосов
/ 26 апреля 2018

Однако итератор запускается только один раз. второе значение никогда не доступно

Потому что его там нет . И это хорошо видно, если вы сохраните массив, который вы перебираете, в место, которое вы можете посмотреть позже (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; что-то, чего я умышленно избегал, чтобы код был чистым

0 голосов
/ 26 апреля 2018

Идентификаторы теперь [1,2]

да, имя ids теперь указывает на массив, который содержит содержимое 1, 2, но это совершенно другой массив. Не тот, для которого вы создали итератор, когда вы ввели .each.

Я бы подумал (дважды) перед тем, как поменять коллекцию, которую я повторяю , но если вы настаиваете, то, по крайней мере, измените ту же коллекцию и не создавайте новую. (это значит, используйте push, а не |=) .


Ладно, что тогда будет лучшим решением для чего-то подобного?

Это похоже на очередь заданий. Это традиционно реализуется с помощью стека или очереди. Для простоты мы будем использовать массив.

ids = [1]

loop do
  id = ids.shift
  puts id # your processing
  ids.concat(search_for_more_ids(id))
  break if ids.empty?
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...