【问题标题】:Workarounds to suspend (serialize) and resume a recursive generator stack?暂停(序列化)和恢复递归生成器堆栈的解决方法?
【发布时间】:2013-11-02 18:42:13
【问题描述】:

我有一个递归生成器函数,它创建一个 ChainMap 上下文树,最后对树末尾的上下文做一些事情。看起来是这样的(parent_context 是一个 ChainMap,hierarchy 是一个列表):

def recursive_generator(parent_context, hierarchy):
    next_level = hierarchy[0]
    next_level_contexts = get_contexts(next_level) # returns a list of dicts

    for context in next_level_contexts:
        child_context = parent_context.new_child().update(context)
        if next_level == hierarchy[-1]:
            yield do_something(**child_context)
        else:
            yield from recursive_generator(child_context, hierarchy[1:])

现在我想标记层次结构的一个级别,以便操作在完成该级别后暂停,将状态序列化到磁盘以便稍后在它停止的地方拾取。有没有办法在不失去递归优雅的情况下做到这一点?

我知道你不能腌制生成器,所以我考虑重构为一个迭代器对象。但我认为yield from 是这里递归所必需的(编辑:至少不需要对堆栈进行一些繁琐的管理),所以我认为它需要是一个生成器,不是吗?有解决办法吗?

【问题讨论】:

    标签: python recursion tree generator python-3.3


    【解决方案1】:

    您似乎正在使用 DFS 探索一棵树。所以你可以在内存中构造树并使 DFS 显式。然后只需存储树并在最左侧的节点处重新启动(我认为?)。

    这实际上是“乏味的堆栈管理”,但它有一个很好的图片可以帮助实现它(至少对我来说,看着你的问题,因为树的 DFS 使得实现看起来相当明显 - 在我想到之前就这样,看起来很复杂——但我可能遗漏了一些东西)。

    对不起,如果这很明显且不够充分......

    [编辑]

    class Inner:
    
        def __init__(self, context, hierarchy):
            self.children = []
            next_level = hierarchy[0]
            next_level_contexts = get_contexts(next_level)
            for context in next_level_contexts:
                child_context = parent_context.new_child().update(context)
                if next_level == hierarchy[-1]:
                    self.children.append(Leaf(context))
                else:
                    self.children.append(Inner(child_context, hierarchy[1:]))
    
        def do_something(self):
            # this will do something on the left-most leaf                         
            self.children[0].so_something()
    
        def prune(self):
            # this will remove the left-most leaf                                  
            if isinstance(self.children[0], Leaf):
                self.children.pop(0)
            else:
                self.children[0].prune()
                if not self.children[0]:
                    self.children.pop(0)
    
        def __bool__(self):
            return bool(self.children)
    
    class Leaf:
    
        def __init__(self, context):
            self.context = context
    
        def do_something(): 
            do_something(**self.context)
    

    上面的代码没有经过测试。我最终使用了节点类,因为元组似乎太混乱了。您通过创建父节点来创建树。然后你可以通过调用do_something“做某事”,之后你会想用prune删除“完成”的叶子:

    tree = Inner(initial_context, initial_hierarchy)
    while tree:
        tree.do_something()
        tree.prune()
    

    我很确定它会包含错误,但希望它足以展示这个想法。对不起,我不能做更多,但我需要重新种植植物....

    ps 很有趣,您可以使用生成器编写代码,但不知道 DFS 是什么。你可能会喜欢阅读“算法设计手册”——它既是教科书又是参考书,它不会把你当作白痴(我也没有受过正规的计算机科学教育,我认为这是一本好书)。

    [编辑以更改为最左边的优先,我认为这是您之前的内容]

    alko 有一个很好的观点......

    【讨论】:

    • 不,这不是很明显,我对学术计算机科学没有太多经验,我必须查看深度优先搜索是什么。但是,是的,这就是我正在做的。顺序很重要——我只对树底部的节点感兴趣(尽管在某种意义上,上面的节点是通过 ChainMap 考虑的)。
    • 您是否建议使用某种数据结构或模块来对内存中的树进行建模?我还认为也许我可以通过收集底​​部节点中的上下文(而不是调用do_something)将“树”放入内存,将它们保存到列表中,然后分段运行列表并保存当前位置。懒惰地执行do_something的序列调用比实际的树搜索更重要。
    • @HenryS.Harrison 因为 DFS 自然是用堆栈实现的,您可以使用简单的 lists 或 collections.deque 来完成此任务。如果您更喜欢直接浏览树,只需将访问过的节点保存在列表中:to_visit=deque(); visited=deque(); ...; n = to_visit.popleft(); to_visit.extend(children(n)); visited.append(n);
    • 感谢 Andrew,这是一个很好的解决方案,我认为最好保留整个树结构。但是因为我只需要叶子(按顺序),所以我的解决方案更加紧凑和可读。也谢谢推荐书。我只是通过玩耍和阅读文档学到了很多东西,但这并不能教你算法。
    • Andrew,我也喜欢我可以在树中的任何节点运行do_something,实际上我通过将上下文保存在 MultiIndex pandas DataFrame 中来实现类似的东西(上下文的字段总是相同),因此用户可以按任何索引开始和停止(每个索引 = 层次结构中的一个级别)。
    【解决方案2】:

    这是我最终做的:

    def recursive_generator(parent_context, hierarchy):
        next_level = hierarchy[0]
        next_level_contexts = get_contexts(next_level) # returns a list of dicts
    
        for context in next_level_contexts:
            child_context = parent_context.new_child().update(context)
            if next_level == hierarchy[-1]:
                yield child_context
            else:
                yield from recursive_generator(child_context, hierarchy[1:])
    
    def traverse_tree(hierarchy):
        return list(recursive_generator(ChainMap(), hierarchy)
    
    def do_things(contexts, start, stop):
        for context in contexts[start:stop]:
            yield do_something(**context)
    

    然后我可以腌制traverse_tree 返回的列表,然后加载它并使用do_things 分段运行它。这一切都在一个课程中,当然还有更多内容,但这就是它的要点。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-13
      • 1970-01-01
      • 2021-06-30
      • 2017-01-17
      • 1970-01-01
      相关资源
      最近更新 更多