迭代器不包括循环内添加的值

时间:2018-04-26 08:43:48

标签: ruby iterator

我有:

ids = [1]

我正在ids.each do |id|循环浏览ids并查找要添加到ids的子ID。这是我的代码:

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

在第一次迭代中,ids获得第二个值并变为[1, 2]。但循环仍然存在于第一次迭代中。迭代器只运行一次;永远不会访问第二个值。这很奇怪,因为这曾经起作用。

任何帮助都将不胜感激。

2 个答案:

答案 0 :(得分:2)

  

ids现在是[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

答案 1 :(得分:2)

  

但是,迭代器只运行一次。永远不会访问第二个值

因为它没有。如果您保存阵列,那么它会清晰可见,您可以在以后查看的地方进行迭代(在我的示例中为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}})。由此产生的算法几乎解释了自己:

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 的{​​{1}}获得结果。

它也可能比你的方法更快,因为它消除了没有中间容器分配的重复;通过设置查找。但这取决于您正在处理的数据的大小(ID的总数,单个搜索结果的长度)。根据具体情况,可以切割一些角落。例如,您可以修改算法,以便副本根本不会通过队列 - 这是我有意避免保持代码清晰的。