├── .gitignore ├── funktown ├── __init__.py ├── list.py ├── vector.py ├── dictionary.py └── lookuptree.py ├── setup.py ├── benchmarks └── vectortest.py ├── LICENSE ├── README.md └── unittest.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | *.egg-info 4 | build 5 | dist 6 | -------------------------------------------------------------------------------- /funktown/__init__.py: -------------------------------------------------------------------------------- 1 | from .dictionary import ImmutableDict 2 | from .vector import ImmutableVector 3 | from .list import ImmutableList 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name="funktown", 4 | version="0.4.6", 5 | description="Functional Data Structures for Python", 6 | author="Zhehao Mao", 7 | author_email="zhehao.mao@gmail.com", 8 | url="http://github.com/zhemao/funktown", 9 | packages=["funktown"] 10 | ) 11 | -------------------------------------------------------------------------------- /benchmarks/vectortest.py: -------------------------------------------------------------------------------- 1 | import moka 2 | import funktown 3 | import cProfile as profile 4 | 5 | def mokatest(fname): 6 | mlist = moka.List() 7 | with open(fname) as f: 8 | for line in f: 9 | mlist = mlist.append(line) 10 | 11 | def funktowntest(fname): 12 | ftvec = funktown.ImmutableVector() 13 | with open(fname) as f: 14 | for line in f: 15 | ftvec = ftvec.conj(line) 16 | 17 | def stdlibtest(fname): 18 | lst = [] 19 | with open(fname) as f: 20 | for line in f: 21 | lst.append(line) 22 | 23 | if __name__ == "__main__": 24 | fname = "studyinscarlet.txt" 25 | profile.run('mokatest(fname)') 26 | profile.run('funktowntest(fname)') 27 | profile.run('stdlibtest(fname)') 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Zhehao Mao 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FunkTown: Functional Data Structures for Python 2 | 3 | This library implements an efficient ImmutableDict and ImmutableVector class 4 | inspired by the implementation of Vectors and Maps in the Clojure programming 5 | language. 6 | 7 | ## Installation 8 | 9 | If you are installing from the cloned repository, just use 10 | 11 | python setup.py install 12 | 13 | Package is also available from PyPI. In that case, use 14 | 15 | pip install funktown 16 | 17 | ArchLinux users can install from the AUR 18 | 19 | yaourt -S python2-funktown 20 | yaourt -S python-funktown # python 3 version 21 | 22 | ## API 23 | 24 | ### ImmutableVector 25 | 26 | The constructor takes a list of initial values. It supports standard list 27 | operations such as item access, slicing, and concatenation. 28 | 29 | #### assoc(index, value) 30 | 31 | Returns a new vector with the value assigned to the given index 32 | 33 | #### conj(value) 34 | 35 | Returns a new vector with the value appended to the back. Roughly equivalent 36 | to vec.assoc(len(vec), value) 37 | 38 | #### pop() 39 | 40 | Returns a new vector with the final item removed. 41 | 42 | ### ImmutableDict 43 | 44 | The constructor can take a dict and/or keyword arguments. Item access follows 45 | the same behavior as builtin python dicts. 46 | 47 | #### assoc(key, value) 48 | 49 | Returns a new immutable dict with the value associated with the given key. 50 | 51 | #### get(key) 52 | 53 | Like in Python's builtin dict, this will act like item access, except 54 | returning None instead of raising a KeyError. 55 | 56 | #### remove(key) 57 | 58 | Return a new ImmutableDict with the item at that key removed 59 | 60 | #### update(otherdict) 61 | 62 | Return a new immutable dict updated with the records in otherdict. 63 | 64 | ## Compatiblity 65 | 66 | Funktown has been tested with Python 2.7.2 and 3.2.2 on a Linux system. 67 | It should be compatible with all python versions greater than 2.6. 68 | -------------------------------------------------------------------------------- /funktown/list.py: -------------------------------------------------------------------------------- 1 | class ImmutableList(object): 2 | 3 | def __init__(self, *args): 4 | if len(args) == 0: 5 | self._empty = True 6 | self._head = None 7 | self._tail = None 8 | 9 | elif len(args) == 1 and isinstance(args[0], (list, tuple)): 10 | if len(args[0]) == 0: 11 | self._empty = True 12 | self._head = None 13 | self._tail = None 14 | else: 15 | self._head = args[0][0] 16 | if len(args[0]) > 1: 17 | self._tail = ImmutableList(args[0][1:]) 18 | else: self._tail = ImmutableList() 19 | self._empty = False 20 | 21 | elif len(args) == 2 and isinstance(args[1], ImmutableList): 22 | self._head = args[0] 23 | self._tail = args[1] 24 | self._empty = False 25 | 26 | def conj(self, itm): 27 | return ImmutableList(itm, self) 28 | 29 | def first(self): 30 | return self._head 31 | 32 | def rest(self): 33 | if self._empty: 34 | return self 35 | return self._tail 36 | 37 | def second(self): 38 | if self._empty: 39 | return None 40 | return self._tail._head 41 | 42 | def empty(self): 43 | return self._empty 44 | 45 | def __contains__(self, itm): 46 | if self._empty: 47 | return False 48 | if self._head == itm: 49 | return True 50 | return itm in self._tail 51 | 52 | def __iter__(self): 53 | node = self 54 | while not node._empty: 55 | yield node._head 56 | node = node._tail 57 | 58 | def __eq__(self, other): 59 | if other is None: 60 | return False 61 | 62 | if not hasattr(other, '__iter__'): 63 | return False 64 | 65 | node = self 66 | 67 | for itm in other: 68 | if node._empty: 69 | return False 70 | if node._head != itm: 71 | return False 72 | node = node._tail 73 | 74 | return True 75 | 76 | def __ne__(self, other): 77 | return not self.__eq__(other) 78 | 79 | def __len__(self): 80 | if self._empty: 81 | return 0 82 | else: 83 | return 1 + len(self._tail) 84 | 85 | def __str__(self): 86 | return '[' + ', '.join([str(x) for x in self]) + ']' 87 | 88 | def __repr__(self): 89 | return 'ImmutableList(' + str(self) + ')' 90 | 91 | -------------------------------------------------------------------------------- /funktown/vector.py: -------------------------------------------------------------------------------- 1 | from .lookuptree import LookupTree 2 | from itertools import islice 3 | 4 | class ImmutableVector(object): 5 | '''An immutable vector class. Access, appending, and removal are 6 | guaranteed to have O(log(n)) performance. The constructor takes the same 7 | arguments as the builtin list class.''' 8 | 9 | def __init__(self, initvalues=None): 10 | if initvalues is None: initvalues = [] 11 | self.tree = LookupTree(initvalues) 12 | self._length = len(initvalues) 13 | 14 | def assoc(self, index, value): 15 | '''Return a new vector with value associated at index. The implicit 16 | parameter is not modified.''' 17 | newvec = ImmutableVector() 18 | newvec.tree = self.tree.assoc(index, value) 19 | if index >= self._length: 20 | newvec._length = index+1 21 | else: 22 | newvec._length = self._length 23 | return newvec 24 | 25 | def concat(self, tailvec): 26 | '''Returns the result of concatenating tailvec to the implicit 27 | parameter''' 28 | newvec = ImmutableVector() 29 | vallist = [(i + self._length, tailvec[i]) \ 30 | for i in range(0, tailvec._length)] 31 | newvec.tree = self.tree.multi_assoc(vallist) 32 | newvec._length = self._length + tailvec._length 33 | return newvec 34 | 35 | def pop(self): 36 | '''Return a new ImmutableVector with the last item removed.''' 37 | if self._length == 0: 38 | raise IndexError() 39 | newvec = ImmutableVector() 40 | newvec.tree = self.tree.remove(self._length-1) 41 | newvec._length = self._length-1 42 | return newvec 43 | 44 | def conj(self, value): 45 | '''Return a new ImmutableVector with value appended to the 46 | end of the vector''' 47 | return self.assoc(self._length, value) 48 | 49 | def _get(self, index): 50 | if index >= self._length: 51 | raise IndexError 52 | return self.tree[index] 53 | 54 | def _slice(self, slc): 55 | lst = [val for val in islice(self, slc.start, slc.stop, slc.step)] 56 | return ImmutableVector(lst) 57 | 58 | def __add__(self, other): 59 | return self.concat(other) 60 | 61 | def __iter__(self): 62 | for i in range(0, self._length): 63 | yield self[i] 64 | 65 | def __len__(self): 66 | return self._length 67 | 68 | def __getitem__(self, index): 69 | if isinstance(index, slice): 70 | return self._slice(index) 71 | return self._get(index) 72 | 73 | def __str__(self): 74 | return str(list(self)) 75 | 76 | def __repr__(self): 77 | return 'ImmutableVector('+str(self)+')' 78 | 79 | def __contains__(self, item): 80 | for thing in self: 81 | if thing == item: 82 | return True 83 | return False 84 | 85 | def __eq__(self, other): 86 | if other is None: 87 | return False 88 | 89 | if not hasattr(other, '__getitem__'): 90 | return False 91 | 92 | if len(self) != len(other): 93 | return False 94 | 95 | for i in range(len(self)): 96 | if self[i] != other[i]: 97 | return False 98 | 99 | return True 100 | 101 | def __ne__(self, other): 102 | return not self.__eq__(other) 103 | 104 | -------------------------------------------------------------------------------- /funktown/dictionary.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | from .lookuptree import LookupTree 4 | 5 | class ImmutableDict(collections.Mapping): 6 | '''An immutable dictionary class. Access, insertion, and removal 7 | are guaranteed to have O(log(n)) performance. Constructor takes same 8 | arguments as builtin dict''' 9 | 10 | def __init__(self, initdict=None, **kwargs): 11 | if initdict is None: 12 | initdict = {} 13 | else: 14 | # Don't want to overwrite what the caller sent 15 | initdict = initdict.copy() 16 | initdict.update(kwargs) 17 | hashlist = [(hash(key), (key, initdict[key])) for key in initdict] 18 | fixed_up = dict(hashlist) 19 | self.tree = LookupTree(fixed_up) 20 | self._length = len(initdict) 21 | 22 | def assoc(self, key, value): 23 | '''Returns a new ImmutableDict instance with value associated with key. 24 | The implicit parameter is not modified.''' 25 | copydict = ImmutableDict() 26 | copydict.tree = self.tree.assoc(hash(key), (key, value)) 27 | copydict._length = self._length + 1 28 | return copydict 29 | 30 | def update(self, other=None, **kwargs): 31 | '''Takes the same arguments as the update method in the builtin dict 32 | class. However, this version returns a new ImmutableDict instead of 33 | modifying in-place.''' 34 | copydict = ImmutableDict() 35 | if other: 36 | vallist = [(hash(key), (key, other[key])) for key in other] 37 | else: vallist = [] 38 | if kwargs: 39 | vallist += [(hash(key), (key, kwargs[key])) for key in kwargs] 40 | copydict.tree = self.tree.multi_assoc(vallist) 41 | copydict._length = iter_length(copydict.tree) 42 | return copydict 43 | 44 | def remove(self, key): 45 | '''Returns a new ImmutableDict with the given key removed.''' 46 | copydict = ImmutableDict() 47 | copydict.tree = self.tree.remove(hash(key)) 48 | copydict._length = self._length - 1 49 | return copydict 50 | 51 | def get(self, key, default=None): 52 | '''Same as get method in builtin dict.''' 53 | try: 54 | return self[key] 55 | except KeyError: return default 56 | 57 | def __len__(self): 58 | return self._length 59 | 60 | def __getitem__(self, key): 61 | try: 62 | return self.tree[hash(key)][1] 63 | except KeyError: raise KeyError(key) 64 | 65 | def __iter__(self): 66 | for key,val in self.tree: 67 | yield key 68 | 69 | def keys(self): 70 | '''Same as keys method in dict builtin.''' 71 | return [key for (key,val) in self.tree] 72 | 73 | def values(self): 74 | '''Same as values method in dict builtin.''' 75 | return [val for (key,val) in self.tree] 76 | 77 | def items(self): 78 | '''Same as items method in dict builtin.''' 79 | return [item for item in self.tree] 80 | 81 | def __str__(self): 82 | return str(dict(self)) 83 | 84 | def __repr__(self): 85 | return 'ImmutableDict('+str(self)+')' 86 | 87 | def __contains__(self, key): 88 | try: 89 | self.tree[hash(key)] 90 | return True 91 | except KeyError: return False 92 | 93 | def __eq__(self, other): 94 | if other is None: 95 | return False 96 | 97 | if not isinstance(other, collections.Mapping): 98 | return False 99 | 100 | if len(self) != len(other): 101 | return False 102 | 103 | for key in self: 104 | if self[key] != other[key]: 105 | return False 106 | 107 | return True 108 | 109 | def __ne__(self, other): 110 | return not self.__eq__(other) 111 | 112 | def iter_length(iterable): 113 | try: 114 | return len(iterable) 115 | except: 116 | i = 0 117 | for x in iterable: 118 | i+=1 119 | return i 120 | -------------------------------------------------------------------------------- /unittest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import uuid 4 | 5 | import funktown 6 | 7 | from funktown.lookuptree import LookupTree 8 | from funktown import ImmutableDict, ImmutableVector, ImmutableList 9 | 10 | import collections 11 | 12 | def treetest(): 13 | t1 = LookupTree({0:0, 32:32, 4:4}) 14 | assert t1.get(0) == 0 15 | t2 = t1.assoc(36, 36) 16 | assert t1.get(36) is None 17 | assert t2.get(36) == 36 18 | t3 = t2.assoc(36, 35) 19 | assert t3.get(36) == 35 20 | t4 = t2.multi_assoc([(15,15), (14,14)]) 21 | assert t4.get(15) == 15 22 | assert t4.get(14) == 14 23 | 24 | def vectortest(): 25 | v1 = ImmutableVector([0,1,2]) 26 | v2 = v1.conj(3) 27 | v3 = v1.pop() 28 | assert len(v1) == 3 29 | assert len(v2) == 4 30 | assert len(v3) == 2 31 | assert v2[3] == 3 32 | assert v2 == [0, 1, 2, 3] 33 | v4 = v1 + v2 34 | assert v4 == [0,1,2,0,1,2,3] 35 | assert v4[0:4] == [0,1,2,0] 36 | assert 2 in v4 37 | assert ImmutableVector() == [] 38 | 39 | def dicttest(): 40 | d1 = ImmutableDict(hello="world") 41 | d2 = d1.assoc("goodbye", "moon") 42 | d3 = d2.remove("hello") 43 | assert d1["hello"] == "world" 44 | assert d2["goodbye"] == "moon" 45 | assert d1.get("goodbye") is None 46 | assert d3.get("hello") is None 47 | assert d2 == {"hello":"world", "goodbye":"moon"} 48 | d4 = d2.update(ImmutableDict({"a":"b", "c":"d"})) 49 | assert len(d4) == 4 50 | assert d4['a'] == 'b' 51 | assert d4['c'] == 'd' 52 | d5 = d1.update(hola="mundo") 53 | assert d5['hola'] == 'mundo' 54 | assert 'hola' in d5 55 | assert ImmutableDict() == {} 56 | assert ImmutableDict().get(1, 2) == 2 57 | 58 | def dictismappingtest(): 59 | start = {'a': 1, 'b': 2, 'c': 3} 60 | i_d = ImmutableDict(start) 61 | assert isinstance(i_d, collections.Mapping) 62 | 63 | def dict_creation_test(): 64 | d1 = {"a": 1, "b": 2, "c": 3, 4: 'd'} 65 | initial_length = len(d1) 66 | i_d = ImmutableDict(d1, d=4) 67 | assert len(d1) == initial_length 68 | assert len(i_d) == initial_length + 1 69 | 70 | def dict_int_test(): 71 | d = {n:n for n in range(16)} 72 | d1 = ImmutableDict(d) 73 | assert (len(d1.keys()) == 16) 74 | 75 | def ugly_tree_creation_test(): 76 | tree = LookupTree() 77 | error_values = [] 78 | for i in range(10000): 79 | tree.insert(hash(i), (i, i)) 80 | n = 0 81 | try: 82 | for k, v in tree: 83 | n += 1 84 | if n != i+1: 85 | error_values.append(i) 86 | except TypeError: 87 | # this is failing the first time through the loop, because 88 | # integers aren't iterable. 89 | # I'm torn about what to think about this fact. 90 | # Probably just means I don't understand the tree as well as I thought 91 | print "Quit being able to iterate over tree on insert # %d" % i 92 | raise 93 | assert not error_values 94 | 95 | def brutal_creation_test(): 96 | """ Note that this is incredibly slow and painful. 97 | You probably won't want to run it often """ 98 | errors = [] 99 | for i in range(10000): 100 | print '@', 101 | d = {n:n for n in range(i)} 102 | i_d = ImmutableDict(d) 103 | for j in range(i): 104 | if j not in i_d: 105 | msg = '@ length {}, index {} got lost' 106 | errors.append(msg.format(i, j)) 107 | if not i % 80: 108 | print '!' 109 | assert not errors, str(errors) 110 | 111 | def simpler_dict_collision_test(): 112 | d = {n:n for n in range(10000)} 113 | i_d = ImmutableDict(d) 114 | failures = [] 115 | result_keys = i_d.keys() 116 | for n in range(10000): 117 | if n not in result_keys: 118 | failures.append(n) 119 | if failures: 120 | print "Lost %d keys: %s" % (len(failures), failures) 121 | assert False, str(failures) 122 | 123 | def dict_collision_test(): 124 | l = [str(uuid.uuid4()) for _ in range(10000)] 125 | d = {l[n]: n for n in range(10000)} 126 | i_d = ImmutableDict(d) 127 | assert len(i_d.keys()) == len(d.keys()) 128 | failures = [] 129 | for k in l: 130 | if str(k) not in i_d: 131 | failures.append({k: d[k]}) 132 | if failures: 133 | msg = 'UUID entries lost:' 134 | for uid in failures: 135 | msg += '{}\n'.format(uid) 136 | msg += '\n(There are {} of them)'.format(len(failures)) 137 | assert False, msg 138 | 139 | def listtest(): 140 | l1 = ImmutableList([2, 3]) 141 | assert l1.conj(1) == [1, 2, 3] 142 | assert len(l1) == 2 143 | assert l1.conj(1) == ImmutableList(1, l1) 144 | l3 = ImmutableList() 145 | assert len(l3) == 0 146 | assert l3 == ImmutableList([]) 147 | 148 | def typetest(): 149 | l = ImmutableList() 150 | v = ImmutableVector() 151 | d = ImmutableDict() 152 | 153 | assert l is not None 154 | assert v != 3 155 | assert d != 'a' 156 | 157 | assert l == v 158 | assert d != v 159 | assert d != l 160 | 161 | if __name__ == "__main__": 162 | dict_collision_test() 163 | simpler_dict_collision_test() 164 | # This is simply too slow to run regularly/often 165 | # (if at all) 166 | # brutal_creation_test() 167 | dict_int_test() 168 | # TODO: Get this passing 169 | # ugly_tree_creation_test() 170 | dict_creation_test() 171 | treetest() 172 | vectortest() 173 | dicttest() 174 | dictismappingtest() 175 | listtest() 176 | typetest() 177 | print("All tests passed") 178 | -------------------------------------------------------------------------------- /funktown/lookuptree.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | # This isn't named well. It's really a flag that 4 | # tells us we're talking about a branch node rather 5 | # than a leaf. 6 | # Q: Is this really more efficient than just setting 7 | # a boolean? 8 | _root_index = uuid.uuid4() 9 | 10 | 11 | class LookupTreeNode(object): 12 | def __init__(self, index=_root_index, value=None): 13 | self.children = [None] * 32 14 | self.index = index 15 | self.value = value 16 | 17 | def __iter__(self): 18 | if self.index == _root_index: 19 | for child in self.children: 20 | if child is None: 21 | continue 22 | if child.index == _root_index: 23 | for value in child: 24 | yield value 25 | else: yield child.value 26 | 27 | def __repr__(self): 28 | result = '{}: {}'.format(self.index, self.value) 29 | return result 30 | 31 | 32 | class LookupTree(object): 33 | '''A lookup tree with a branching factor of 32. The constructor 34 | takes an argument initvalues, which can either be a list of values 35 | or a dictionary mapping indices to values. 36 | 37 | This really needs to construct a balanced tree (the canonical example 38 | is red-black). 39 | Q: Is it doing so? 40 | ''' 41 | def __init__(self, initvalues=None): 42 | self.root = LookupTreeNode() 43 | if initvalues is None: 44 | pass 45 | elif isinstance(initvalues, dict): 46 | for key,val in initvalues.items(): 47 | self.insert(key, val) 48 | else: 49 | for i,val in enumerate(initvalues): 50 | self.insert(i, val) 51 | 52 | def get(self, index): 53 | '''Like tree[index], but returns None if index not found''' 54 | try: 55 | return self[index] 56 | except KeyError: 57 | return None 58 | 59 | def __getitem__(self, index): 60 | '''Find the value of the node with the given index''' 61 | node = self.root 62 | level = 0 63 | while node and node.index == _root_index: 64 | i = _getbits(index, level) 65 | node = node.children[i] 66 | level += 1 67 | 68 | if node is not None and node.index == index: 69 | return node.value 70 | else: 71 | raise KeyError(index) 72 | 73 | def assoc(self, index, value): 74 | '''Return a new tree with value associated at index.''' 75 | newnode = LookupTreeNode(index, value) 76 | newtree = LookupTree() 77 | newtree.root = _assoc_down(self.root, newnode, 0) 78 | return newtree 79 | 80 | def multi_assoc(self, values): 81 | '''Return a new tree with multiple values associated. The parameter 82 | values can either be a dictionary mapping indices to values, or a list 83 | of (index,value) tuples''' 84 | if isinstance(values, dict): 85 | nndict = dict([(i, LookupTreeNode(i, values[i])) for i in values]) 86 | else: 87 | nndict = dict([(i, LookupTreeNode(i, val)) for (i,val) in values]) 88 | newtree = LookupTree() 89 | newtree.root = _multi_assoc_down(self.root, nndict, 0) 90 | return newtree 91 | 92 | def remove(self, index): 93 | '''Return new tree with index removed.''' 94 | newtree = LookupTree() 95 | newtree.root = _remove_down(self.root, index, 0) 96 | return newtree 97 | 98 | def insert(self, index, value): 99 | '''Insert a node in-place. It is highly suggested that you do not 100 | use this method. Use assoc instead''' 101 | newnode = LookupTreeNode(index, value) 102 | level = 0 103 | node = self.root 104 | while True: 105 | ind = _getbits(newnode.index, level) 106 | level += 1 107 | child = node.children[ind] 108 | if child is None or child.index == newnode.index: 109 | if child: 110 | assert child.value == newnode.value 111 | node.children[ind] = newnode 112 | break 113 | elif child.index == _root_index: 114 | # This is a branch 115 | node = child 116 | else: 117 | branch = LookupTreeNode() 118 | nind = _getbits(newnode.index, level) 119 | cind = _getbits(child.index, level) 120 | node.children[ind] = branch 121 | 122 | # Life gets tricky when... 123 | if nind == cind: 124 | branch.children[cind] = child 125 | # recurse 126 | node = branch 127 | else: 128 | branch.children[nind] = newnode 129 | branch.children[cind] = child 130 | break 131 | 132 | def __iter__(self): 133 | return iter(self.root) 134 | 135 | 136 | def _assoc_down(node, newnode, level): 137 | ind = _getbits(newnode.index, level) 138 | copynode = LookupTreeNode() 139 | for i,child in enumerate(node.children): 140 | if i != ind: 141 | copynode.children[i] = child 142 | child = node.children[ind] 143 | if child is None or child.index == newnode.index: 144 | copynode.children[ind] = newnode 145 | elif child.index == _root_index: 146 | copynode.children[ind] = _assoc_down(child, newnode, level+1) 147 | else: 148 | branch = LookupTreeNode() 149 | copynode.children[ind] = branch 150 | level+=1 151 | cind = _getbits(child.index, level) 152 | nind = _getbits(newnode.index, level) 153 | branch.children[cind] = child 154 | branch.children[nind] = newnode 155 | return copynode 156 | 157 | def _multi_assoc_down(node, nndict, level): 158 | indices = set([_getbits(index, level) for index in nndict]) 159 | copynode = LookupTreeNode() 160 | for i,child in enumerate(node.children): 161 | if i not in indices: 162 | copynode.children[i] = child 163 | 164 | for ind in indices: 165 | subnndict = dict([(i,nndict[i]) for i in nndict \ 166 | if ind == (i >> level * 5) & 31]) 167 | child = node.children[ind] 168 | if child is None or child.index in subnndict: 169 | if len(subnndict) == 1: 170 | values = [val for val in subnndict.values()] 171 | copynode.children[ind] = values[0] 172 | else: 173 | branch = LookupTreeNode() 174 | copynode.children[ind] = \ 175 | _multi_assoc_down(branch, subnndict, level+1) 176 | elif child.index == _root_index: 177 | copynode.children[ind] = \ 178 | _multi_assoc_down(node, subnndict, level+1) 179 | else: 180 | branch = LookupTreeNode() 181 | copynode.children[ind] = \ 182 | _multi_assoc_down(branch, subnndict, level+1) 183 | 184 | return copynode 185 | 186 | def _remove_down(node, index, level): 187 | ind = _getbits(index, level) 188 | 189 | if node.children[ind] is None: 190 | return node 191 | 192 | copynode = LookupTreeNode() 193 | 194 | for i,child in enumerate(node.children): 195 | if i != ind: 196 | copynode.children[i] = child 197 | 198 | child = node.children[ind] 199 | 200 | if child.index == index: 201 | copynode.children[ind] = None 202 | elif child.index == _root_index: 203 | copynode.children[ind] = _remove_down(child, index, level+1) 204 | else: 205 | return node 206 | 207 | return copynode 208 | 209 | def _getbits(num, level): 210 | return (num >> 5 * level) & 31 211 | --------------------------------------------------------------------------------