Skip to main content
 首页 » 编程设计

Python:优化,或者至少获得树生成器的新想法

2025年05月04日10shanyou

我编写了一个生成随机表达式然后使用遗传技术选择适合度的程序。

程序的以下部分生成随机表达式并将其存储在树结构中。

由于这在运行期间可能被调用数十亿次,我认为应该针对时间进行优化。

我是编程新手,我自己工作(玩),就像我在 inernet 上搜索一样多

想法,我想要一些意见,因为我觉得我是在孤立地做这件事。

瓶颈似乎是Node.init(),(占总时间的22%)和random.choice(),(占总时间的14%)

import random 
 
def printTreeIndented(data, level=0): 
    '''utility to view the tree 
    ''' 
    if data == None: 
         return 
    printTreeIndented(data.right, level+1) 
    print '  '*level + '  '+ str(data.cargo)#+ '  '+ str(data.seq)+ '  '+ str(data.branch) 
    printTreeIndented(data.left, level+1) 
 
#These are the global constants used in the Tree.build_nodes() method. 
Depth = 5 
Ratio = .6 #probability of terminating the current branch. 
Atoms = ['1.0','2.0','3.0','4.0','5.0','6.0','7.0','8.0','9.0','x','x','x','x'] 
#dict of operators. the structure is: operator: number of arguements 
Operators = {'+': 2, '-': 2, '*': 2, '/': 2, '**': 2} 
 
 
class KeySeq: 
    '''Iterator to produce sequential  
    integers for keys in Tree.thedict 
    ''' 
    def __init__(self, data = 0): 
        self.data = data 
    def __iter__(self): 
        return self 
    def next(self): 
        self.data = self.data + 1 
        return self.data 
KS = KeySeq() 
 
class Node(object): 
    ''' 
    ''' 
    def __init__(self, cargo, left=None, right=None): 
        object.__init__(self) 
        self.isRoot = False 
        self.cargo = cargo 
        self.left  = left 
        self.right = right 
        self.parent = None 
        self.branch = None 
        self.seq = 0 
 
 
class Tree(object): 
    def __init__(self): 
        self.thedict = {}     #provides access to the nodes for further mutation and 
        # crossbreeding. 
        #When the Tree is instantiated, it comes filled with data. 
        self.data = self.build_nodes() 
 
# Uncomment the following lines to see the data and a crude graphic of the tree. 
#        print 'data: ' 
#        for v in  self.thedict.itervalues(): 
#            print v.cargo, 
#        print 
#        print 
#        printTreeIndented(self.data) 
 
    def build_nodes (self,  depth = Depth, entry = 1,  pparent = None, 
     bbranch = None): 
        ''' 
        ''' 
        r = float() 
        r = random.random() 
 
        #If r > Ratio, it forces a terminal node regardless of 
        #the value of depth. 
        #If entry = 1, then it's the root node and we don't want 
        # a tree with just a value in the root node. 
 
        if (depth <= 0) or ((r > Ratio) and (not (entry))): 
            ''' 
            Add a terminal node. 
            ''' 
            this_atom = (random.choice(Atoms)) 
            this_atom = str(this_atom) 
            this_node = Node(this_atom) 
            this_node.parent = pparent 
            this_node.branch = bbranch 
            this_node.seq = KS.next() 
            self.thedict[this_node.seq] = this_node 
            return this_node 
 
        else: 
            ''' 
            Add a node that has branches. 
            ''' 
            this_operator = (random.choice(Operators.keys())) 
 
            this_node = Node(this_operator) 
            if entry: 
                this_node.isRoot = True 
            this_node.parent = pparent 
            this_node.branch = bbranch 
            this_node.seq = KS.next() 
            self.thedict[this_node.seq] = this_node 
            #branch as many times as 'number of arguements' 
            # it's only set up for 2 arguements now. 
            for i in range(Operators[this_operator]): 
                depth =(depth - 1) 
                if i == 0: 
                    this_node.left = (self.build_nodes(entry = 0, depth =(depth), 
                     pparent = this_node, bbranch = 'left')) 
                else: 
                    this_node.right = (self.build_nodes(entry = 0, depth =(depth), 
                     pparent = this_node, bbranch = 'right')) 
            return this_node 
 
 
def Main(): 
    for i in range(100000): 
        t = Tree() 
    return t 
 
if __name__ == '__main__': 
    rresult = Main() 

请您参考如下方法:

下面,我总结了一些更明显的优化工作,但并未真正触及算法。所有计时均在 Linux x86-64 系统上使用 Python 2.6.4 完成。

初始时间:8.3s

唾手可得的果实

jellybean已经指出了一些。只需修复这些问题就可以稍微改善运行时间。通过一次又一次地使用相同的列表来替换对 Operators.keys() 的重复调用也可以节省一些时间。

时间:6.6s

使用 itertools.count

由 Dave Kirby 指出, 只需使用 itertools.count还可以节省您一些时间:

from itertools import count 
KS = count() 

时间:6.2s

改进构造函数

因为你没有在 ctor 中设置 Node 的所有属性,你可以只将属性声明移动到类主体中:

class Node(object): 
    isRoot = False 
    left  = None 
    right = None 
    parent = None 
    branch = None 
    seq = 0 
 
    def __init__(self, cargo): 
        self.cargo = cargo 

就您而言,这不会改变类的语义,因为类主体中使用的所有值都是不可变的(False、None、0),如果你需要其他值,阅读this answer on class attributes首先。

时间:5.2s

使用命名元组

在您的代码中,您不再更改表达式树,因此您不妨使用一个不可变的对象。 Node 也没有任何行为,所以使用 namedtuple是一个不错的选择。但这确实有一个含义,因为现在必须删除 parent 成员。从您可能会引入带有两个以上参数的运算符这一事实来看,您无论如何都必须将 left/right 替换为子节点列表,这又是可变的,并且允许在所有子节点之前创建父节点。

from collections import namedtuple 
Node = namedtuple("Node", ["cargo", "left", "right", "branch", "seq", "isRoot"]) 
# ... 
    def build_nodes (self,  depth = Depth, entry = 1,  pparent = None, 
         bbranch = None): 
        r = random.random() 
 
        if (depth <= 0) or ((r > Ratio) and (not (entry))): 
            this_node = Node( 
                random.choice(Atoms), None, None, bbranch, KS.next(), False) 
            self.thedict[this_node.seq] = this_node 
            return this_node 
 
        else: 
            this_operator = random.choice(OpKeys) 
 
            this_node = Node( 
              this_operator, 
              self.build_nodes(entry = 0, depth = depth - 1, 
                               pparent = None, bbranch = 'left'), 
              self.build_nodes(entry = 0, depth = depth - 2, 
                               pparent = None, bbranch = 'right'), 
              bbranch,  
              KS.next(),  
              bool(entry)) 
 
            self.thedict[this_node.seq] = this_node     
            return this_node 

我保留了操作数循环的原始行为,即在每次迭代时递减深度。我不确定这是想要的行为,但更改它会增加运行时间,因此无法进行比较。

结束时间:4.1s

从这里去哪里

如果您希望支持两个以上的运算符和/或支持 parent 属性,请使用类似于以下代码的内容:

from collections import namedtuple 
Node = namedtuple("Node", ["cargo", "args", "parent", "branch", "seq", "isRoot"]) 
 
    def build_nodes (self,  depth = Depth, entry = 1,  pparent = None, 
         bbranch = None): 
        r = random.random() 
 
        if (depth <= 0) or ((r > Ratio) and (not (entry))): 
            this_node = Node( 
                random.choice(Atoms), None, pparent, bbranch, KS.next(), False) 
            self.thedict[this_node.seq] = this_node 
            return this_node 
 
        else: 
            this_operator = random.choice(OpKeys) 
 
            this_node = Node( 
              this_operator, [], pparent, bbranch, 
              KS.next(), bool(entry)) 
            this_node.args.extend( 
              self.build_nodes(entry = 0, depth = depth - (i + 1), 
                               pparent = this_node, bbranch = i) 
              for i in range(Operators[this_operator])) 
 
            self.thedict[this_node.seq] = this_node     
            return this_node 

此代码还随着运算符(operator)的位置减少深度。