Python:优化树评估器

时间:2010-01-30 22:21:07

标签: python optimization

我知道树是一个研究得很好的结构。

我正在编写一个程序,随机生成许多表达式树,然后通过适应性属性进行排序和选择。

我有一个类MakeTreeInOrder(),它将树变成'eval'可以评估的字符串。

但它被多次调用,应该根据时间进行优化。

下面的

是一个构建树的函数,该树添加连续的数字以用作测试。

我想知道是否有一种优化的方法来评估树结构中的表达式。我想那个

它使用了很多,有些人已经完成了这个。

import itertools
from collections import namedtuple 

#Further developing Torsten Marek's second suggestion

KS = itertools.count()
Node = namedtuple("Node", ["cargo", "args"]) 

def build_nodes (depth = 5):     
    if (depth <= 0): 
        this_node = Node((str(KS.next())), [None, None])
        return this_node 
    else:
        this_node = Node('+', []) 
        this_node.args.extend( 
          build_nodes(depth = depth - (i + 1))                             
          for i in range(2)) 

        return this_node

以下是我认为可以快得多的代码。我希望有一些想法。

class MakeTreeInOrder(object):
    def __init__(self, node):
        object.__init__(self)
        self.node = node
        self.str = ''
    def makeit(self, nnode = ''):
        if nnode == '':
            nnode = self.node
        if nnode == None: return
        self.str +='('
        self.makeit(nnode.args[0])
        self.str += nnode.cargo
        self.makeit(nnode.args[1])
        self.str+=')'
        return self.str

def Main():
    this_tree = build_nodes()
    expression_generator = MakeTreeInOrder(this_tree)
    this_expression = expression_generator.makeit()
    print this_expression
    print eval(this_expression)

if __name__ == '__main__':
    rresult = Main()

2 个答案:

答案 0 :(得分:3)

在这里添加一些面向对象会使事情变得更简单。为树中的每个东西都有Node的子类,并使用'eval'方法来评估它们。

import random

class ArithmeticOperatorNode(object):
    def __init__(self, operator, *args):
        self.operator = operator
        self.children = args
    def eval(self):
        if self.operator == '+':
            return sum(x.eval() for x in self.children)
        assert False, 'Unknown arithmetic operator ' + self.operator
    def __str__(self):
        return '(%s)' % (' ' + self.operator + ' ').join(str(x) for x in self.children)

class ConstantNode(object):
    def __init__(self, constant):
        self.constant = constant
    def eval(self):
        return self.constant
    def __str__(self):
        return str(self.constant)

def build_tree(n):
    if n == 0:
        return ConstantNode(random.randrange(100))
    else:
        left = build_tree(n - 1)
        right = build_tree(n - 1)
        return ArithmeticOperatorNode('+', left, right)

node = build_tree(5)
print node
print node.eval()

要评估树,只需在顶级节点上调用.eval()。

node = build_tree(5)
print node.eval()

我还添加了一个__str__方法将树转换为字符串,以便您可以看到它如何推广到其他树函数。它目前只做'+',但希望很清楚如何将其扩展到全范围的算术运算。

答案 1 :(得分:1)

您的示例导入numpy和random,但从不使用它们。它还有一个“for i in range(2)”,没有身体。这显然不是有效的Python代码。

您没有定义“货物”和节点应包含的内容。似乎'cargo'是一个数字,因为它来自itertools.count()。next()。但这没有任何意义,因为你希望结果是一个可评估的Python字符串。

如果您正在对树进行一次性评估,那么最快的解决方案是直接就地评估它,但如果没有您正在使用的数据的实际示例,我无法展示示例

如果你想让它更快,那就进一步向上游看。为什么要生成树然后对其进行评估?你不能直接在当前生成树结构的代码中评估组件吗?如果您有“+”和“*”之类的运算符,那么请考虑使用operator.add和operator.mul,它可以直接处理数据,而无需使用中间步骤。

==更新==

这建立在Paul Hankin的回答之上。我所做的就是取消中间树结构,直接评估表达式。

def build_tree2(n):
    if n == 0:
        return random.randrange(100)
    else:
        left = build_tree2(n-1)
        right = build_tree2(n-1)
        return left+right

这比Paul的解决方案快5倍左右。

可能需要知道最佳解的实际树结构,或N的前k,其中k <&lt;&lt; N.如果是这种情况,那么如果您还跟踪用于生成结果的RNG状态,则可以事后重新生成这些树。例如:

def build_tree3(n, rng=random._inst):
    state = rng.getstate()
    return _build_tree3(n, rng.randrange), state

def _build_tree3(n, randrange):
    if n == 0:
        return randrange(100)
    else:
        left = _build_tree3(n-1, randrange)
        right = _build_tree3(n-1, randrange)
        return left+right

找到最佳值后,使用键重新生成树

# Build Paul's tree data structure given a specific RNG
def build_tree4(n, rng):
    if n == 0:
        return ConstantNode(rng.randrange(100))
    else:
        left = build_tree4(n-1, rng)
        right = build_tree4(n-1, rng)
        return ArithmeticOperatorNode("+", left, right)

# This is a O(n log(n)) way to get the best k.
# An O(k log(k)) time solution is possible.
rng = random.Random()
best_5 = sorted(build_tree3(8, rng) for i in range(10000))[:5]
for value, state in best_5:
    rng.setstate(state)
    tree = build_tree4(8, rng)
    print tree.eval(), "should be", value
    print "  ", str(tree)[:50] + " ..."

这是我运行时的样子

10793 should be 10793
   ((((((((92 + 75) + (35 + 69)) + ((39 + 79) + (6 +  ...
10814 should be 10814
   ((((((((50 + 63) + (6 + 21)) + ((75 + 98) + (76 +  ...
10892 should be 10892
   ((((((((51 + 25) + (5 + 32)) + ((40 + 71) + (17 +  ...
11070 should be 11070
   ((((((((7 + 83) + (77 + 56)) + ((16 + 29) + (2 + 1 ...
11125 should be 11125
   ((((((((69 + 80) + (11 + 64)) + ((33 + 21) + (95 + ...