N-dimensional aka N-ary Tree

N-dimensional tree is used to represent the UI of any application. Buttons, ComboBoxes and various other components are assembled together in this data structure and then rendered on the screen.

I had to enhance the design of a commercial Tree UI component (add improved searching features) and thought of creating my own n-ary Tree data structure to hold the data which will be passed into the Tree UI component.

class Node(object):
    
    def __init__(self, **kwargs):
        self.value      = kwargs.get('value')
        self.children   = []

    def __str__(self):
        return 'Val: {0} Children: {1}'.format(self.value, self.children)

    def __hash__(self):
        return hash((self.value, self.children,))

    __repr__ = __str__

class nAryTree(object):

    def __init__(self):
        self._root = Node()

    def getRoot(self):
        return self._root

    def insert(self, data):
        self._insertValues(data, self._root)

    def _insertValues(self, data, currNode):
        if not data:
            return
        
        if currNode and not currNode.value:
            tmpChild = None

            for child in currNode.children:
                if data[0] == child.value:
                    tmpChild = child
                    break

            if tmpChild:
                self._insertValues(data, tmpChild)
            else:
                newNode = Node(value=data[0])
                currNode.children.append(newNode)
                self._insertValues(data, newNode)
        else:
            if len(data) > 1:
                tmpChild = None

                for child in currNode.children:
                    if data[1] == child.value:
                        tmpChild = child
                        break
                
                if tmpChild:
                    self._insertValues(data[1:], tmpChild)
                else:
                    newNode = Node(value=data[1])
                    currNode.children.append(newNode)
                    self._insertValues(data[1:], newNode)

    def show(self):
        self.showContainer = []
        self._showTree(self._root)

    def _showTree(self, currNode):
        if currNode and not currNode.value:
            print '*Root'
        else:
            self.showContainer.append('--')
            self.showContainer.append(currNode.value)
            print ''.join(self.showContainer)
            self.showContainer.pop()
            self.showContainer.pop()

        if currNode and currNode.children:
            self.showContainer.append('  |')
            print ''.join(self.showContainer)
        
        for child in currNode.children:
            self._showTree(child)

        if currNode.children and self.showContainer:
            self.showContainer.pop()

    def deleteNode(self, tgt=''):
        searchContainer = []
        result          = {}
        res             = self._search(self._root, tgt, searchContainer, None, result)
        parentNode      = result.get('parentNode')

        for idx in xrange(len(parentNode.children)):
            if parentNode.children[idx].value == tgt:
                del parentNode.children[idx]
                break

        return searchContainer[1:]

    def searchTree(self, tgt=''):
        searchContainer = []
        res             = self._search(self._root, tgt, searchContainer)

        return searchContainer[1:]

    def _search(self, currNode, tgt, path=[], parentNodeOfTgt=None, result={}):
        if (not tgt and tgt.strip()) or not currNode:
            return False

        if currNode.value == tgt:
            path.append(currNode.value)
            result['parentNode']    = parentNodeOfTgt
            result['tgtNode']       = currNode
            print 'Path: {0}'.format('>'.join(path))

            return True
        else:
            path.append(currNode.value if currNode.value else 'Root')
            for child in currNode.children:
                if self._search(child, tgt, path, currNode, result):
                    return True
            else:
                path.pop()

    def nestedTuples(self):
        data = self._naryToNestedTuples(self._root)
        return data[1] if data else ()

    def _naryToNestedTuples(self, t):
        return (t.value, tuple(map(self._naryToNestedTuples, t.children)), ) if t.children and isinstance(t.children, (tuple, list,)) \
            else t.value

    def nestedLists(self):
        data = self._naryToNestedLists(self._root)
        return data[1] if data else []

    def _naryToNestedLists(self, t):
        return [t.value, map(self._naryToNestedLists, t.children)] if t.children and isinstance(t.children, (tuple, list,)) \
            else t.value

def main():
    tree = nAryTree()
    tree.insert(('A', 'B', 'C', 'D',))
    tree.insert(('A', 'B', 'C', 'E',))
    tree.insert(('A', 'B', 'F', 'X',))
    tree.insert(('A', 'B', 'C', 'Y',))
    tree.show()

    print '\nSearch result for {0}: {1}\n\n'.format('X', tree.searchTree('X'))

    print 'Accounting Organizational Structure'
    # http://www.chesterfield.gov/uploadedImages/Department_Information/Management_Services/Accounting/Media/Images/Accounting%20Organizational%20Structure.jpg?n=5909
    tree2 = nAryTree()
    tree2.insert(('Director',))
    tree2.insert(('Director', 'Financial Services & Grants Coordinators',))
    tree2.insert(('Director', 'Assistant Director-I',))
    tree2.insert(('Director', 'Assistant Director-I', 'Financial Systems Section',))
    tree2.insert(('Director', 'Assistant Director-I', 'General Accounting Section',))
    tree2.insert(('Director', 'Assistant Director-II',))
    tree2.insert(('Director', 'Assistant Director-II', 'Accounts Payable Section',))
    tree2.insert(('Director', 'Assistant Director-II', 'Payroll Section',))
    tree2.insert(('Director', 'Assistant Director-II', 'Administration Section',))
    tree2.show()

    print '\nSearch result for {0}: {1}\n\n'.format('Payroll Section', tree2.searchTree('Payroll Section'))
    raw_input()

if __name__ == '__main__':
    main()

 

Output >>

*Root
  |
  |--A
  |  |
  |  |--B
  |  |  |
  |  |  |--C
  |  |  |  |
  |  |  |  |--D
  |  |  |  |--E
  |  |  |  |--Y
  |  |  |--F
  |  |  |  |
  |  |  |  |--X
Path: Root>A>B>F>X

Search result for X: ['A', 'B', 'F', 'X']


Accounting Organizational Structure
*Root
  |
  |--Director
  |  |
  |  |--Financial Services & Grants Coordinators
  |  |--Assistant Director-I
  |  |  |
  |  |  |--Financial Systems Section
  |  |  |--General Accounting Section
  |  |--Assistant Director-II
  |  |  |
  |  |  |--Accounts Payable Section
  |  |  |--Payroll Section
  |  |  |--Administration Section
Path: Root>Director>Assistant Director-II>Payroll Section

Search result for Payroll Section: ['Director', 'Assistant Director-II', 'Payroll Section']

One Response to N-dimensional aka N-ary Tree

  1.  

    I struggled for several hours to find a Python tree that would accept duplicate items. Thank you for this “simple” and effective code.

leave your comment