Welcome, guest | Sign In | My Account | Store | Cart

this recipe describes a tree backbone class that supports two modes of iteration and topological comparison. and it has a nice node access inteface.

Python, 94 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
## for python 2.2+
from __future__ import generators
__version__ = '1.0.12'

#--------------------------------------------------------------CTree---
class CTree:
    '''Generate a Class Tree.
    
    Tree characteristics and restrictions:
        - all children of any single node must have unique names.
        - the tree nodes are sorted in order of definition.
        - due to the fact that  in standard python the recursion depth 
          is limited (1000  by default) this tree can  not exceed this 
          limit.                                                       
        - all node names must comply  with identifier naming rules for
          the python language (i.e. [_a-zA-Z][_0-9a-zA-Z]*).
    the interface is as follows:
        - on init the path is a dot separated branch to be created.
        - on call if the path exists return the last mentioned element 
          else create the part that does not exist and return the last 
          element.                                                     
    '''
    def __init__(self, path='', separator='.'):
        '''
        Generate a tree branch corresponding to the path given.

        path      : a <separator> delimited path to be created.
        separator : the path delimiter ('.' by default)
        '''
        self.__leafs = '_' + self.__class__.__name__ + '__leafs' in self.__dict__ and self.__leafs or []
        try:
            c_path = (path.split(separator,1)[0], path.split(separator,1)[1])
            if not c_path[0] in self.__dict__:
                # generate the tail of the branch
                setattr(self, c_path[0], self.__class__(c_path[1], separator))
                self.__leafs.append(self.__dict__[c_path[0]])
            else:
                # go down one level
                getattr(self, c_path[0]).__init__(c_path[1], separator) 
        except IndexError:
            # do this if we are at the end of the path/branch
            c_path = path.split(separator,1)[0]
            path != '' and not c_path in self.__dict__ and \
                not self.__dict__.update({c_path: self.__class__()}) and \
                    not self.__leafs.append(self.__dict__[c_path])
    # this acts as a proxy to __init__
    def __call__(self, path='', separator='.'):
        '''
        Generate a tree branch corresponding to the path given.

        path      : a <separator> delimited path to be created.
        separator : the path delimiter ('.' by default)
        '''
        self.__init__(path, separator)
        return eval('self' + (path!='' and '.' + (separator != '.' and path.replace(separator,'.') or path) or ''))
    def __eq__(self, other):
        return self.__class__ == other.__class__ and \
                len(self.__leafs) == len(other.__leafs) and \
                    not ( 0 in [ l0 == l1 for l0, l1 in zip(self.__leafs, other.__leafs) ] )
    def __ne__(self, other):
        return not (self == other)
    def __iter__(self, func=lambda lst, tail: lst + tail):
        '''
        this generator  will iterate  through the tree  (left-to-right
        depth-first).
        func : a function taking two parameters, the first is the list
               of sub-nodes  of the  current node,  the second  is the 
               stack representing the tail  of the returning node list 
               (excluding  the   returned  nodes).   And  returning  a 
               combined list of nodes.
        '''
        node_list = [self]
        while 1:
            try:
                cur = node_list.pop(0)
                node_list = func(cur.__leafs, node_list)
                yield cur
            except AttributeError:
                yield cur
            except IndexError:
                return
    def flat_iter(self):
        '''
        left-to-right breadth-first generator.
        '''
        return self.__iter__(lambda lst, tail: tail + lst)
    def __delattr__(self, name):
        try:
            self.__dict__[name] in self.__leafs and self.__leafs.remove(self.__dict__[name])
            del self.__dict__[name]
        except KeyError:
            raise AttributeError

#----------------------------------------------------------------------

this code is mostly self explanatory, though in some spots I admit it might be a bit difficult to grasp.

Examples:

  • generate a tree:

my_tree = CTree('node0.node00.node000') my_tree('node1.node10.node100')

my_tree('node0.node01.node010')

now here is an equivalent to the above generating only "node011"

my_tree.node0.node01('node011')

the following three lines are equivalent:

my_tree('node0.node01.node011')

my_tree.node0('node01.node011')

my_tree.node0.node01('node011')

  • get a node by its path:

the following lines are equivalent and return the same node :)

my_tree('node1.node10.node100') my_tree.node1('node10.node100') my_tree.node1.node10('node100') my_tree.node1.node10.node100

Thanks and have fun!!

Created by Alex Naanou on Fri, 10 Jan 2003 (PSF)
Python recipes (4591)
Alex Naanou's recipes (4)

Required Modules

Other Information and Tasks