diff options
Diffstat (limited to 'groupthink/aatree.py')
-rw-r--r-- | groupthink/aatree.py | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/groupthink/aatree.py b/groupthink/aatree.py new file mode 100644 index 0000000..6676277 --- /dev/null +++ b/groupthink/aatree.py @@ -0,0 +1,598 @@ +class Node: + """Conventions: a nonexistent child or parent is None.""" + + parent = None + leftchild = None + rightchild = None + annotation = None + value = None + +class AANode(Node): + level = 1 + +class Walker: + """descend must return 0 if the node in question is the one desired, + -1 if the left child would be better, or 1 if the right child would be + better""" + def descend(self, node): + raise + def prepare_descend(self, *args): #optional method to prepare for descent + raise + """ascend should return True iff it should run again on the parent. + It should set the state such that a subsequent + descent would retrace these steps.""" + def ascend(self, node): + raise + def prepare_ascend(self, *args): #optional method to prepare for ascent + raise + +class SearchWalker(Walker): + """Convention: leftchild.annotation < annotation < rightchild.annotation""" + val = 0 + compare = cmp + def prepare_descend(self, val, comparator=cmp): + self.val = val + self.compare = comparator + def descend(self, node): + x = self.compare(node.annotation, self.val) + return x + def ascend(self, node): + self.val = node.annotation + return False + +class RandomWalker(Walker): + def prepare_descend(self): + from random import choice as choice + self.choice = choice + def descend(self, node): + return self.choice((-1,1)) + #ascend not implemented; it doesn't make sense because there is no + #state and no reproducibility + +def descend(node, walker): #move down from a root node + x = walker.descend(node) + while x != 0: + if x == 1: + if node.rightchild is None: + return (node, 1) + else: + node = node.rightchild + else: #x == -1 + if node.leftchild is None: + return (node, -1) + else: + node = node.leftchild + x = walker.descend(node) + return (node, 0) + +def ascend(node, walker): #Move up from a leaf node + while node is not None and walker.ascend(node): + node = node.parent + +def search(root, val): + """Searches a correctly sorted binary tree, starting with the root Node, for + val. Returns a node that contains val if val is present, otherwise a node + for which val would be an acceptable child value.""" + w = SearchWalker + w.prepared_descend(val) + return descend(root, w) + +def findmin(root): + while root.leftchild is not None: + root = root.leftchild + return root + +def findmax(root): + while root.rightchild is not None: + root = root.rightchild + return root + +class MonoidTree: + makenode = Node + """A Monoid Annotation Tree is a binary tree whose nodes are each annotated + by values from some monoid. The annotation of an internal node is computed + by applying the operation to the annotations of its children. The annotation of a leaf + node is specified by the user. Every node must either have two children or + be a leaf node. + + Each leaf node may also be associated with an arbitrary opaque value of the user's + choosing. This node and value will remain associated.""" + def __init__(self, operation, rootnode): + """The rootnode must have a valid annotation, and its parent must be None""" + self.op = operation + self.root = rootnode + def _update(self, node, sentinel=None): + """node must be an internal node""" + while node is not sentinel: + #oldval = node.annotation + node.annotation = self.op(node.leftchild.annotation, node.rightchild.annotation) + #if oldval == node.annotation: + # #this node has not changed, so nodes above it will also not have changed + # break + #else: + node = node.parent + _update_add = _update + _update_del = _update + def _split_link(self, node): + """Introduce and return a new node (newparent) between node and its parent""" + newparent = self.makenode() + newparent.parent = node.parent + if node.parent is not None: + if node.parent.leftchild is node: + node.parent.leftchild = newparent + else: + assert node.parent.rightchild is node + node.parent.rightchild = newparent + else: + self.root = newparent + node.parent = newparent + return newparent + def addleft(self, new, old): + """Add a new leaf node to the left of an old leaf node""" + newparent = self._split_link(old) + newparent.rightchild = old + newparent.leftchild = new + new.parent = newparent + self._update_add(newparent) + def addright(self, new, old): + """Add a new leaf node to the right of an old leaf node""" + newparent = self._split_link(old) + newparent.rightchild = new + newparent.leftchild = old + new.parent = newparent + self._update_add(newparent) + def add(self, new, walker): + leaf, position = descend(self.root, walker) + assert leaf.leftchild is None + assert leaf.rightchild is None + if position == 1: + self.addright(new, leaf) + else: #Makes left the default for duplicate values + self.addleft(new, leaf) + def remove(self, leaf): + p = leaf.parent + if p.leftchild is leaf: + sibling = p.rightchild + else: + assert p.rightchild is leaf + sibling = p.leftchild + gp = p.parent + if gp.leftchild is p: + gp.leftchild = sibling + elif gp.rightchild is p: + gp.rightchild = sibling + sibling.parent = gp + # The only remaining reference to p is now in leaf itself, and the only + # remaining reference to leaf is in the user's hands + self._update_del(gp) + def change_annotation(self, leaf, newann): + assert leaf.leftchild is None + assert leaf.rightchild is None + leaf.annotation = newann + self._update(leaf.parent) + def getnext(self, leaf, skip=None): + assert leaf.leftchild is None + assert leaf.rightchild is None + node = leaf + while ((node.parent is not None) and + ((node.parent.rightchild is node) or + ((skip is not None) and skip(node.parent.rightchild)))): + # Move up until you can move right + node = node.parent + if (node.parent is not None) and (node.parent.leftchild is node): + node = node.parent.rightchild + while node.leftchild is not None: + # Move down, staying as far left as possible. + assert node.rightchild is not None + if (skip is not None) and skip(node.leftchild): + node = node.rightchild + else: + node = node.leftchild + return node + else: + raise StopIteration("No next node") + + def _build_subtree(self, nodes): + #FIXME: This cannot be helpful because insertion of a subtree requires + #rebalancing the main tree by more than one level, which is not possible + #with a single invocation of skew and split + L = len(nodes) + if L == 1: + return nodes[0] + else: + next = [] + sentinel = 'g' #must not be None, since None is the root sentinel + if L % 2: + n2 = nodes.pop() + n1 = nodes.pop() + newnode = self.makenode() + newnode.parent=sentinel #totally arbitrary constant + newnode.leftchild = n1 + n1.parent = newnode + newnode.rightchild = n2 + n2.parent = newnode + self._update_add(newnode, sentinel) + nodes.append(newnode) + for i in xrange(0,L,2): + n1,n2 = nodes[i:(i+2)] + newnode = self.makenode() + newnode.parent=sentinel #totally arbitrary constant + newnode.leftchild = n1 + n1.parent = newnode + newnode.rightchild = n2 + n2.parent = newnode + self._update_add(newnode, sentinel) + + + +class SumWalker(Walker): + """SumWalker is designed to walk over full trees where each leaf has annotation 1 + and the monoid is +. Target is the zero-indexed position of the target node. + + There is one exception: the last node in every tree has annotation 0.""" + target = None + offset = None + def prepare_descend(self, target): + self.target = target + self.offset = 0 + def descend(self, node): + if node.annotation == 0: #empty leaf at the last position + assert self.target == self.offset + return -1 + elif node.leftchild is None: #leaf node case + assert node.rightchild is None + assert self.target == self.offset + return 0 + else: #internal node case + p = self.offset + node.leftchild.annotation + if p <= self.target: + self.offset = p + return 1 + else: + return -1 + def prepare_ascend(self): + self.target = 0 + def ascend(self, node): + if node.parent is not None: + if node.parent.rightchild is node: + self.target += node.parent.leftchild.annotation + else: + assert node.parent.leftchild is node + return True + else: + return False + +class TreeList: + """Implements a list-like interface, backed by a MonoidTree""" + _treetype = MonoidTree + def __init__(self): + self._makenode = self._treetype.makenode + r = self._makenode() + r.annotation = 0 + from operator import add + self._tree = self._treetype(add, r) + self._walker = SumWalker() + # We regard the fields of this walker as public API, and manipulate + # them directly + self._index = {} + def __len__(self): + return self._tree.root.annotation + def _getnode(self, i): + self._walker.prepare_descend(i) + node, pos = descend(self._tree.root, self._walker) + assert pos == 0 + return node + def __getitem__(self, s): + if isinstance(s, int): + node = self._getnode(s) + return node.value + else: + raise UnimplementedError + def __setitem__(self, s, v): + if isinstance(s, int): + if s < len(self): + node = self._getnode(s) + oldv = node.value + self._index[oldv].remove(node) + if not self._index[oldv]: + del self._index[oldv] + node.value = v + if v not in self._index: + self._index[v] = set() + self._index[v].add(node) + else: + self.insert(s, v) + else: + raise UnimplementedError + def __delitem__(self, s): + if isinstance(s, int): + if s < len(self): + node = self._getnode(s) + oldv = node.value + self._index[oldv].remove(node) + if not self._index[oldv]: + del self._index[oldv] + self._tree.remove(node) + else: + raise UnimplementedError + def insert(self, p, v): + if p > len(self): + raise IndexError("Index out of range") + self._walker.prepare_descend(p) + newnode = self._makenode() + newnode.annotation = 1 + newnode.value = v + self._tree.add(newnode, self._walker) + if v not in self._index: + self._index[v] = set() + self._index[v].add(newnode) + def index(self, v): + """index returns some index such that self[i] == v. No promises about ordering.""" + self._walker.prepare_ascend() + for node in self._index[v]: #Pull one arbitrary node out of the set + assert node.value == v + ascend(node, self._walker) + break + return self._walker.target + +class TreeHideList: + """Implements the EagerHideList interface, backed by a MonoidTree""" + _treetype = MonoidTree + class MultiSumWalker(Walker): + index = 0 + target = 0 + offset = 0 + def prepare_descend(self, target, index): + self.index = index + self.target = target + self.offset = 0 + def descend(self, node): + if node.annotation == (0,0): #empty leaf at the last position + assert self.target == self.offset + return -1 + elif node.leftchild is None: #leaf node case + assert node.rightchild is None + assert self.target == self.offset + return 0 + else: #internal node case + p = self.offset + node.leftchild.annotation[self.index] + if p <= self.target: + self.offset = p + return 1 + else: + return -1 + def prepare_ascend(self, index): + self.target = 0 + self.index = index + def ascend(self, node): + if node.parent is not None: + if node.parent.rightchild is node: + self.target += node.parent.leftchild.annotation[self.index] + else: + assert node.parent.leftchild is node + return True + else: + return False + + @staticmethod + def op(a,b): + # Convention: a[0] is visible elements. a[1] is all elements. + return (a[0] + b[0], a[1] + b[1]) + + @staticmethod + def skip(node): + return node.annotation[0] == 0 + + def __init__(self): + self._makenode = self._treetype.makenode + r = self._makenode() + r.annotation = (0, 0) + self._tree = self._treetype(self.op, r) + self._walker = self.MultiSumWalker() + # We regard the fields of this walker as public API, and manipulate + # them directly + self._index = {} + unique = True + if unique: + self._index_lookup = self._index.__getitem__ + self._index_assign = self._index.__setitem__ + else: + self._index_lookup = self._index_lookup_set + self._index_assign = self._index_assign_set + def _index_lookup_set(self, item): + for v in self._index[item]: + return v + def _index_assign_set(self, key, value): + if key not in self._index: + self._index[key] = set() + self._index[key].add(value) + def __len__(self): + return self._tree.root.annotation[0] + def _getnode(self, i, a): + self._walker.prepare_descend(i, a) + node, pos = descend(self._tree.root, self._walker) + assert (pos == 0) or ((pos == -1) and (i == len(self))) + return node + def __getitem__(self, s): + if isinstance(s, int): + if s < len(self): #FIXME: negative indices + node = self._getnode(s, 0) + return node.value + else: + raise IndexError("Index out of range") + else: + start, stop, stride = s.indices(len(self)) + if start == stop: + return [] + elif stride == 1: + # runs in k + log(N) (amortized) + nodes = [self._getnode(start,0)] + k = stop - start + while len(nodes) < k: + nodes.append(self._tree.getnext(nodes[-1],self.skip)) + return [n.value for n in nodes] + else: + #FIXME: runs in k*log(N), could be reduced to k*log(step) + log(N) + return [self[i] for i in xrange(start,stop,stride)] + def index(self, v, visible=True): + """index returns some index such that self[i] == v. No promises about ordering.""" + self._walker.prepare_ascend(0 if visible else 1) + node = self._index_lookup(v) #Pull one arbitrary node out of the set + assert node.value == v + ascend(node, self._walker) + return self._walker.target + def hide(self, position, length): + #self.__getitem__ is eager, so we acquire the list of nodes before + #acting on them + node = self._getnode(position,0) + for i in xrange(position+1,position+length): + self._tree.change_annotation(node,(0,1)) + node = self._tree.getnext(node, self.skip) + self._tree.change_annotation(node,(0,1)) + #FIXME: runs in length*log(N). Could be reduced using a priority queue, + #possibly to length + log(N) + def getitem_all(self, s): + if isinstance(s, int): + node = self._getnode(s, 1) + return node.value + else: + #FIXME: runs in k*log(N), could be reduced to k + log(N) by linked list + return [self.getitem_all(i) for i in xrange(*s.indices())] + def index_all(self, item): + return self.index(item, False) + def is_visible(self, i): + node = self._getnode(i, 1) + return node.annotation[0] == 1 + def is_visible_item(self, item): + node = self._index_lookup(item) + return node.annotation[0] == 1 + def insert_sequence_all(self, position, sequence, visibility): + node = self._getnode(position,1) + self._insert_sequence_leftofnode(node, sequence, visibility) + def insert_sequence_leftof(self, target, sequence, visibility): + node = self._index_lookup(target) + self._insert_sequence_leftofnode(node, sequence, visibility) + def _insert_sequence_leftofnode(self, node, sequence, visibility): + for i in xrange(len(sequence)): + v = sequence[i] + viz = visibility[i] + newnode = self._makenode() + newnode.annotation = (1 if viz else 0, 1) + newnode.value = v + self._tree.addleft(newnode, node) + self._index_assign(v, newnode) + +# Skew, split, and decrease_level are the AA balancing functions, as described +# at http://en.wikipedia.org/wiki/AA_tree . They have been modified +# substantially here to (1) maintain bidirectional linking and (2) maintain +# monoid annotations. +def skew(node, op=None): + L = node.leftchild + if (L is not None) and node.level == L.level: + node.leftchild = L.rightchild + if node.leftchild is not None: + node.leftchild.parent = node + L.rightchild = node + L.parent = node.parent + node.parent = L + if L.parent is not None: + if L.parent.leftchild is node: + L.parent.leftchild = L + else: + assert L.parent.rightchild is node + L.parent.rightchild = L + if op is not None: + L.annotation = node.annotation + node.annotation = op(node.leftchild.annotation, node.rightchild.annotation) + assert L.annotation == op(L.leftchild.annotation, L.rightchild.annotation) + # This assertion is the condition of associativity, guaranteed for any + # valid monoid operation. + return L + else: + return node + +def split(node, op=None): + R = node.rightchild + if ((R is not None) and + (R.rightchild is not None) and + (node.level == R.rightchild.level)): + node.rightchild = R.leftchild + node.rightchild.parent = node + + R.leftchild = node + R.parent = node.parent + node.parent = R + + R.level += 1 + + if R.parent is not None: + if R.parent.leftchild is node: + R.parent.leftchild = R + else: + assert R.parent.rightchild is node + R.parent.rightchild = R + + if op is not None: + R.annotation = node.annotation + node.annotation = op(node.leftchild.annotation, node.rightchild.annotation) + assert R.annotation == op(R.leftchild.annotation, R.rightchild.annotation) + # This assertion is the condition of associativity, guaranteed for any + # valid monoid operation. + + return R + else: + return node + +def decrease_level(node): + # Decrease the level of node if necessary. Returns true if a modification + # was made. + target = min(node.leftchild.level, node.rightchild.level) + 1 + if target < node.level: + node.level = target + if target < node.rightchild.level: + node.rightchild.level = target + return True + return False + +class AAMonoidTree(MonoidTree): + makenode = AANode + def _update_add(self, node, sentinel=None): + """node must be an internal node one level above the leaves, with + two leaves itself.""" + node.level = 2 + while node is not sentinel: + #oldval = node.annotation + node.annotation = self.op(node.leftchild.annotation, node.rightchild.annotation) + node = skew(node, self.op) + node = split(node, self.op) + if node.parent is None: + self.root = node + node = node.parent + def _update_del(self, node, sentinel=None): + while node is not sentinel: + #oldval = node.annotation + #oldlevel = node.level + node.annotation = self.op(node.leftchild.annotation, node.rightchild.annotation) + + decrease_level(node) + + node = skew(node, self.op) + node.rightchild = skew(node.rightchild, self.op) + if node.rightchild.rightchild is not None: + node.rightchild.rightchild = skew(node.rightchild.rightchild, self.op) + node = split(node, self.op) + node.rightchild = split(node.rightchild, self.op) + + #if (oldval == node.annotation) and (oldlevel == node.level): + # #Nodes above this point will not have changed + # break + + if node.parent is None: + self.root = node + node = node.parent + +class AATreeList(TreeList): + _treetype = AAMonoidTree + +class AATreeHideList(TreeHideList): + _treetype = AAMonoidTree |