我有:
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]
。但循环仍然存在于第一次迭代中。迭代器只运行一次;永远不会访问第二个值。这很奇怪,因为这曾经起作用。
任何帮助都将不胜感激。
答案 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的总数,单个搜索结果的长度)。根据具体情况,可以切割一些角落。例如,您可以修改算法,以便副本根本不会通过队列 - 这是我有意避免保持代码清晰的。