递归操作树结构:如何获取“整个”树的状态?

时间:2013-11-27 19:21:36

标签: python recursion functional-programming immutability computer-algebra-systems

首先,背景:

作为一个辅助项目,我正在用Python构建一个计算机代数系统,它可以产生解决方程所需的步骤。

到目前为止,我已经能够将代数表达式和方程式解析为表达式树。它的结构是这样的(不是实际的代码 - 可能没有运行):

# Other operators and math functions are based off this.
# Numbers and symbols also have their own classes with 'parent' attributes.
class Operator(object):
    def __init__(self, *args):
        self.children = args            
        for child in self.children:
            child.parent = self

# the parser does something like this:
expr = Add(1, Mult(3, 4), 5)

除此之外,我还有一系列递归操作以简化表达式的函数。它们不是纯粹的功能,但我试图避免依赖于操作的可变性,而是返回我正在使用的节点的修改副本。每个函数看起来像这样:

def simplify(node):
    for index, child in enumerate(node.children):
        if isinstance(child, Operator):
            node.children[index] = simplify(node)
        else:
            # perform some operations to simplify numbers and symbols
            pass

    return node

挑战来自于“一步一步”的部分。我希望我的“简化”函数都是嵌套的生成器,它们“产生”解决问题所需的步骤。所以基本上,每次每个函数执行一个操作时,我都希望能够做到这样的事情:yield (deepcopy(node), expression, "Combined like terms.")这样依赖于这个库的任何东西都可以输出如下内容:

5x + 3*4x + 3
5x + 12x + 3                 Simplified product 3*4x into 12x
17x + 3                      Combined like terms 5x + 12x = 17x

但是,每个函数只了解它正在运行的node,但不知道整体expression是什么样的。

所以这是我的问题:保持整个表达式树的“状态”以保证每个“步骤”都知道整个表达式的最佳方法是什么?

以下是我提出的解决方案:

  • 执行每个操作,并在类中使用全局变量或实例变量来存储指向等式的指针。我不喜欢这样,因为单元测试更加困难,因为现在我必须首先设置类。你也失去了更实用的方法的其他优点。
  • 将表达式的根传递给每个函数。但是,这要么意味着我必须重复每个操作以更新表达式,或者我必须依赖于可变性。
  • 根据我产生的每个步骤,让顶级函数“重建”表达式树。例如,如果我产生5x + 4x = 9x,请让顶级函数找到(5x + 4x)节点并将其替换为'9x'。这似乎是最好的解决方案,但是如何最好地“重建”每一步?

最后两个相关问题:这有什么意义吗?我现在的系统里有很多咖啡因,不知道我是不是很清楚。

我是否担心可变性?这是一个过早优化的案例吗?

2 个答案:

答案 0 :(得分:1)

你可能会问树拉链。检查:Functional Pearl: Weaving a Web并查看它是否适用于您想要的内容。从阅读你的问题,我认为你要求在树结构上进行递归,但能够根据需要导航回到顶部。拉链充当“面包屑”,让你回到树的祖先。

我在JavaScript中实现了一个。

答案 1 :(得分:0)

您使用Polish notation构建树吗?

对于逐步简化,您只需使用循环,直到不能在树中进行修改(操作)。