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

    Python 2-3 Tree implementation

    2-3 Tree is a balanced tree each node of which may contain 2 elements
    and 3 references on its children.

    Element lookup speed is log2(N) < x < log3(N)
    Insertion and deletion is about 2 * log2(N)

    See http://en.wikipedia.org/wiki/2-3_tree for more info

    2011 by Boris Tatarintsev

"""

class Pair(object):

    # use this class if associative tree (or map) is needed
    # over 2-3 tree

    def __init__(self, key, value):
        self.key = key
        self.value = value

    def __lt__(self, other):
        if type(other) is Pair:
            return self.key < other.key
        else:
            return self.key < other

    def __gt__(self, other):
        if type(other) is Pair:
            return self.key > other.key
        else:
            return self.key > other

    def __eq__(self, other):
        if type(other) is Pair:
            return self.key == other.key
        else:            
            return self.key == other
        return None

    def __str__(self):
        return 'key: %s, value: %s' % (str(self.key), str(self.value))

    def key(self):
        return self.key

    def val(self):
        return self.value


class Node(object):

    def __init__(self, v = None, parent = None):
        self.values, self.valcnt = None, 0
        self.links, self.refcnt = None, 0
        self.parent = parent
        self.insertValue(v)

    def __str__(self):
        out = []
        if self.values is not None:        
            for v in self.values:
                if v is not None:
                    out.append(' %s ' % str(v))
            return ''.join(out)
        else: return 'empty' 

    def __iter__(self):
        if self.values is not None:
            for item in self.values:
                yield item

    def __getlink(self, a):
        for idx in xrange(self.valcnt):
            if idx is 0: 
                if a < self.values[idx]: return idx
            else: 
                if self.values[idx - 1] < a < self.values[idx]:  return idx
            if idx == self.valcnt - 1: return idx + 1
        return -1

    def __addLink(self, link):
        if self.links is None: self.links = [None] * 4
        self.links[self.refcnt] = link
        self.refcnt += 1

    def __insertLink(self, idx, anotherNode):
        if self.links is None: self.links = [None] * 4
        if idx == 0:
            self.links[0],self.links[1],self.links[2], self.links[3] = anotherNode,self.links[0],self.links[1], self.links[2]
        elif idx == 1:
            self.links[1], self.links[2], self.links[3] = anotherNode, self.links[1], self.links[2]
        elif idx == 2:
            self.links[2], self.links[3] = anotherNode, self.links[2]
        else:
            self.links[3] = anotherNode
        self.refcnt += 1

    def __removeLink(self, idx):
        if idx == 0:
            self.links[0], self.links[1], self.links[2], self.links[3] = self.links[1], self.links[2], self.links[3], None
        elif idx == 1:
            self.links[1], self.links[2], self.links[3] = self.links[2], self.links[3], None
        elif idx == 2:
            self.links[2], self.links[3] = self.links[3], None
        else:
            self.links[3] = None
        self.refcnt -= 1

    def __rearrangeLinks(self, a):
        """ Rearrange links when adding a new node """
        if self.valcnt != 0:            
            if a < self.values[0] and not self.isLeafNode() and self.refcnt < 3: 
                # shift all the links to the right when adding new in element
                self.__insertLink(0, None)
            elif self.valcnt == 2 and self.refcnt == 3 and self.values[self.valcnt - 1] > a > self.values[0]:
                # rearrange middle links when adding med element
                self.__insertLink(1, None)

    def __sort3(self, arr, l):
        """ Sort 2 or 3 arrays (very rubost and fast) """
        if l >= 2:
            if arr[0] > arr[1]: arr[0], arr[1] = arr[1], arr[0]
        if l == 3:
            if arr[1] > arr[2]: arr[1], arr[2] = arr[2], arr[1]
            if arr[0] > arr[1]: arr[0], arr[1] = arr[1], arr[0]


    # interface methods & properties

    def insertValue(self, a):
        """ Insert a value into node """
        if a is not None and self.valcnt < 3:
            if self.valcnt is 0: self.values = [None] * 3
            self.__rearrangeLinks(a)
            self.values[self.valcnt] = a
            self.valcnt += 1
            self.__sort3(self.values, self.valcnt)
        return self

    def removeValue(self, val):
        """ Remove value from node """
        if self.contains(val):
            idx = self.values.index(val)
            if idx == 0:
                self.values[0], self.values[1], self.values[2] = self.values[1], self.values[2], None
            elif idx == 1:
                self.values[1], self.values[2] = self.values[2], None
            else:
                self.values[2] = None
            self.valcnt -= 1
        return self

    def removeLink(self, node):
        """ Remove link from self to another node """
        self.__removeLink(self.getLinkIdx(node))
        return self

    def isConsistent(self):
        """ Check whether the node is consistent, this means it doesn't contain 3 items or 4 links """
        return not (self.valcnt > 2 or self.refcnt > 3)

    def isLeafNode(self):
        """ Check whether this is a leaf node or not """
        return self.refcnt == 0

    def isEmptyNode(self):
        """ Returns true if node doesn't containt any  value """
        return self.valcnt == 0

    def getLink(self, linkIdx):
        """ Get link by its index, return None if there is no link with such an index """
        if linkIdx < self.refcnt:
            return self.links[linkIdx]

    def getLinkIdx(self, destNode):
        """ Get index of the link which points to the given node """
        return self.links.index(destNode)

    def addLink(self, anotherNode):
        """ Add link to another node """
        if anotherNode is not None:
            if self.links is None: self.links = [None] * 4
            idx = self.__getlink(anotherNode.values[0])
            if idx != -1:
                if idx < self.refcnt and self.links[idx] is None:
                    self.links[idx] = anotherNode
                else:
                    self.__insertLink(idx, anotherNode)
                anotherNode.parent = self
        return self

    def contains(self, a):
        """ Check if node contains a given value """
        if self.valcnt is not 0:
            if (self.values[0] > a or self.values[self.valcnt - 1] < a) or a not in self.values:
                return None            
            return self.values[self.values.index(a)]

    def chooseChild(self, a):
        """ Choose where to go according to the value a """
        idx = self.__getlink(a)
        if 0 <= idx < self.refcnt: 
            return self.links[idx]
  
    def getItem(self, a):
        if self.contains(a):
            return self.values[self.values.index(a)]
        return None


class TTTree(object):

    def __init__(self):
        self.root = Node()
        self.lastSearchDepth = 0

    def __iter__(self):
        stack = [self.root]
        while len(stack):
            node = stack.pop()
            yield node    
            for j in xrange(node.refcnt - 1, -1, -1):
                stack.append(node.getLink(j))

    def __str__(self):
        """ String representation of a tree (parentheses form) """
        out, stack = [], [self.root]
        while stack:
            node = stack.pop()
            if node == ')':
                out.append(')')
                continue
            out.append('%s(' % str(node))
            stack.append(')')
            for j in xrange(node.refcnt - 1, -1, -1):
                stack.append(node.getLink(j))        
        return ''.join(out)

    def __nextSucc(self, node):
        self.lastSearchDepth += 1
        if not node.isLeafNode():            
            return self.__nextSucc(node.links[0])
        return node

    def __find(self, curNode, a):
        if curNode is not None:
            if curNode.contains(a):
                return curNode
            nextNode = curNode.chooseChild(a)
            if nextNode is None:
                return curNode            
            self.lastSearchDepth += 1
            return self.__find(nextNode, a)

    def __getLeftSibling(self, node):
        """ Returns left sibling of a node """
        if (node and node.parent) is not None:
            return node.parent.getLink(node.parent.getLinkIdx(node) - 1)
    
    def __getRightSibling(self, node):
        """ Returns right sibling of a node """
        if (node and node.parent) is not None:
            return node.parent.getLink(node.parent.getLinkIdx(node) + 1)

    def __getSiblings(self, node):
        """ Returns node's siblings """
        # check whether one of our siblings has two items
        lS, rS, lCnt, rCnt = None, None, 0, 0
        if self.__getRightSibling(node) is not None:
            rS = self.__getRightSibling(node)
            rCnt = rS.valcnt
        if self.__getLeftSibling(node) is not None:
            lS = self.__getLeftSibling(node)
            lCnt = lS.valcnt
        return lS, lCnt, rS, rCnt

    def __swapValues(self, node1, a1, node2, a2):
        """ Swap any two values in nodes """
        if node1 is not node2:
            idx1, idx2 = node1.values.index(a1), node2.values.index(a2)
            node1.values[idx1], node2.values[idx2] = node2.values[idx2], node1.values[idx1]

    def __fixNodeRemove(self, node, parent = -1):
        
        """ Fix deletion """
      
        if node.isEmptyNode():

            if node is not self.root:

                if parent == -1:
                    parent = node.parent

                if node.isEmptyNode() or not node.isConsistent():

                    lS, lCnt, rS, rCnt = self.__getSiblings(node)
                    rSS, lSS = self.__getRightSibling(rS), self.__getLeftSibling(lS)

                    redistribute = True
                    
                    if (rS or lS) is not None:
                        if   rCnt == 2 or (rCnt == 1 and rSS != None and rSS.valcnt == 2):
                            sib = rS
                        elif lCnt == 2 or (lCnt == 1 and lSS != None and lSS.valcnt == 2):
                            sib = lS
                        elif lCnt == 1:
                            sib, redistribute = lS, False
                        elif rCnt == 1:
                            sib, redistribute = rS, False

                    if redistribute:
                        # case 1: redistribute
                        # left and right case
                        if parent.valcnt == 1:
                            if node == parent.getLink(0):
                                parent_val, sib_val = parent.values[0], sib.values[0]
                                child = sib.chooseChild(sib_val - 1)
                            elif node == parent.getLink(1):                                
                                parent_val, sib_val = parent.values[parent.valcnt - 1], sib.values[sib.valcnt - 1]
                                child = sib.chooseChild(sib_val + 1)  
                        else:
                            if sib == parent.getLink(1):
                                # left
                                if node == parent.getLink(0):
                                    parent_val, sib_val = parent.values[0], sib.values[0]
                                    child = sib.chooseChild(sib_val - 1)
                                # right
                                elif node == parent.getLink(2):
                                    parent_val, sib_val = parent.values[parent.valcnt - 1], sib.values[sib.valcnt - 1]
                                    child = sib.chooseChild(sib_val + 1)
                            # middle
                            elif sib == parent.getLink(2):
                                parent_val, sib_val = parent.values[parent.valcnt - 1], sib.values[0]
                                child = sib.chooseChild(sib_val - 1)
                            elif sib == parent.getLink(0):
                                parent_val, sib_val = parent.values[0], sib.values[sib.valcnt - 1]
                                child = sib.chooseChild(sib_val + 1)

                        node.insertValue(parent_val)
                        parent.removeValue(parent_val)
                        parent.insertValue(sib_val)
                        sib.removeValue(sib_val)                      

                        if not node.isLeafNode():
                            # if this is not a leaf node, redistribute the links also
                            node.addLink(child)
                            sib.removeLink(child)

                        next_node = sib

                    else:
                        # case 2: merge
                        if parent.valcnt == 1:
                            parent_val = parent.values[0]
                        else:                            
                            if sib == parent.getLink(0):
                                parent_val = parent.values[0]
                            elif sib == parent.getLink(1):
                                if sib == rS:
                                    parent_val = parent.values[0]
                                if sib == lS:
                                    parent_val = parent.values[parent.valcnt - 1]

                        child = node.getLink(0)

                        sib.insertValue(parent_val)
                        parent.removeValue(parent_val)
                        parent.removeLink(node)

                        if not node.isLeafNode():
                            sib.addLink(child)

                        next_node = parent

                self.__fixNodeRemove(next_node, next_node.parent) 
            
            else:
                # root node
                self.root = self.root.getLink(0)
                     
    def __fixNodeInsert(self, node):
        if not node.isConsistent():
            # conflict detected, try to resolve it
            if node.isLeafNode() and node is not self.root:
                # case for leaf node
                node.parent.insertValue(node.values[1])
                node.parent.removeLink(node)
                # split the node
                node.parent.addLink(Node(node.values[0], node.parent))
                node.parent.addLink(Node(node.values[node.valcnt - 1], node.parent))
                self.__fixNodeInsert(node.parent)
            else:
                # case for internal node or root node 
                if node is not self.root:
                    node.parent.insertValue(node.values[1])
                    node.parent.removeLink(node)
                    parent = node.parent
                else:
                    self.root = Node(node.values[1])
                    parent = self.root

                # split the node
                leftNode, rightNode = Node(node.values[0], parent), Node(node.values[node.valcnt - 1], parent)
                parent.addLink(leftNode).addLink(rightNode)
                leftNode.addLink(node.getLink(0)).addLink(node.getLink(1))
                rightNode.addLink(node.getLink(2)).addLink(node.getLink(3))

                if node is not self.root:
                    self.__fixNodeInsert(parent)

    # interface methods

    def contains(self, a):
        """ See if we have a given value in our tree """ 
        node = self.findNode(a)
        return node if node.contains(a) else None

    def findNode(self, a):
        """ Find the node which contains the given value """        
        self.lastSearchDepth = 0
        return self.__find(self.root, a)

    def findInorderSucc(self, node, a):
        """ Returns inorder successor of any node """        
        self.lastSearchDepth = 0
        if node.isLeafNode():
            return node
        new_node = node.chooseChild(a + 1)
        return self.__nextSucc(new_node)

    def insertValue(self, a):
        """ Inserts a new value to tree and keeps it balanced """
        if self.root is None:
            self.root = Node(a)
        elif a is not None:
            node = self.findNode(a)
            res = node.contains(a)
            if res:  return res
            # try to insert a new value into existing node
            node.insertValue(a)
            self.__fixNodeInsert(node)
        return self

    def insertList(self, xs):
        """ Insert a list of values into a tree """
        if xs is not None and type(xs) is list:
            for item in xs: self.insertValue(item)

    def removeValue(self, a):
        """ Removes a value from the tree and keeps it balanced """
        node = self.findNode(a)
        if not node or not node.contains(a):
            return None
        # swap the value we want to delete with its inorder successor (always leaf)
        succ = self.findInorderSucc(node, a)
        self.__swapValues(node, a, succ, succ.values[0])
        # delete leaf node value
        succ.removeValue(a)
        # fix tree if needed
        self.__fixNodeRemove(succ)
        return self

    def removeList(self, xs):
       """ Deletes a list of values from a tree """ 
       if xs is not None and type(xs) is list:
            for item in xs: self.removeValue(item)

History