├── .github └── workflows │ ├── deploy.yml │ └── testing.yml ├── .gitignore ├── LICENSE.txt ├── MANIFEST.in ├── NEWS.rst ├── README.rst ├── bintrees ├── __init__.py ├── abctree.py ├── avltree.py ├── bintree.py ├── ctrees.c ├── ctrees.h ├── ctrees.pxd ├── cython_trees.pyx ├── rbtree.py └── treeslice.py ├── issues ├── 003_fastrbtree_crash.py ├── 004_rbtree_copy.py ├── 005_btreeslow.py ├── 006_large_data_crash.py └── 007_FastRBTree_error2.py ├── makefile ├── profiling ├── profile_avltree.py ├── profile_big_rbtree.py ├── profile_bintree.py ├── profile_itemslice.py ├── profile_min_max.py ├── profile_prev_succ.py ├── profile_pytrees.py ├── profile_rbtree.py └── testkeys.txt ├── setup.cfg ├── setup.py ├── testresults.ods └── tests ├── __init__.py ├── test_all_trees.py ├── test_cython_avltree.py ├── test_cython_bintree.py ├── test_cython_rbtree.py ├── test_treeslice.py └── testkey.txt /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: PyPI deployer 2 | on: 3 | push: 4 | tags: 5 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 6 | jobs: 7 | # Deploy source distribution 8 | Source-dist: 9 | runs-on: windows-latest 10 | env: 11 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 12 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Setup Python 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: 3.8 19 | - name: Create source distribution 20 | run: python setup.py sdist --formats=zip 21 | - name: Upload source distribution 22 | run: | 23 | pip install twine 24 | twine upload dist/* 25 | continue-on-error: true 26 | 27 | # Build and deploy wheels for MacOS and Windows 28 | Matrix-build: 29 | runs-on: ${{ matrix.os }} 30 | env: 31 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 32 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 33 | strategy: 34 | matrix: 35 | # ignore linux, requires a many-linux build 36 | os: [windows-latest, macos-latest] 37 | python-version: [3.6, 3.7, 3.8, 3.9] 38 | steps: 39 | - uses: actions/checkout@v2 40 | - name: Setup Python 41 | uses: actions/setup-python@v2 42 | with: 43 | python-version: ${{ matrix.python-version }} 44 | - name: Build binary wheels 45 | run: | 46 | pip install setuptools wheel cython 47 | python setup.py bdist_wheel 48 | - name: Upload binary wheels 49 | run: | 50 | pip install twine 51 | twine upload dist/* 52 | continue-on-error: true 53 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: Test with C-extensions 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | python-version: [3.6, 3.7, 3.8, 3.9] 15 | os: [windows-latest, ubuntu-latest, macos-latest] 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | python -m pip install cython 26 | - name: Install with C-extensions 27 | run: | 28 | python setup.py build_ext -i 29 | - name: Test with C-extensions 30 | run: | 31 | python -m unittest discover 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .tox 3 | build/ 4 | bintrees.egg-info/ 5 | dist/ 6 | /bintrees/cython_trees.c 7 | /gc_check.py 8 | tox.ini 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Manfred Moitzi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | Deutsche Übersetzung: 23 | 24 | Copyright (c) 2012, Manfred Moitzi 25 | 26 | Hiermit wird unentgeltlich, jeder Person, die eine Kopie der Software 27 | und der zugehörigen Dokumentationen (die "Software") erhält, die 28 | Erlaubnis erteilt, uneingeschränkt zu benutzen, inklusive und ohne 29 | Ausnahme, dem Recht, sie zu verwenden, kopieren, ändern, fusionieren, 30 | verlegen, verbreiten, unterlizenzieren und/oder zu verkaufen, und 31 | Personen, die diese Software erhalten, diese Rechte zu geben, unter den 32 | folgenden Bedingungen: 33 | 34 | Der obige Urheberrechtsvermerk und dieser Erlaubnisvermerk sind in allen 35 | Kopien oder Teilkopien der Software beizulegen. 36 | 37 | DIE SOFTWARE WIRD OHNE JEDE AUSDRÜCKLICHE ODER IMPLIZIERTE GARANTIE 38 | BEREITGESTELLT, EINSCHLIESSLICH DER GARANTIE ZUR BENUTZUNG FÜR DEN 39 | VORGESEHENEN ODER EINEM BESTIMMTEN ZWECK SOWIE JEGLICHER 40 | RECHTSVERLETZUNG, JEDOCH NICHT DARAUF BESCHRÄNKT. IN KEINEM FALL SIND 41 | DIE AUTOREN ODER COPYRIGHTINHABER FÜR JEGLICHEN SCHADEN ODER SONSTIGE 42 | ANSPRÜCHE HAFTBAR ZU MACHEN, OB INFOLGE DER ERFÜLLUNG EINES VERTRAGES, 43 | EINES DELIKTES ODER ANDERS IM ZUSAMMENHANG MIT DER SOFTWARE ODER 44 | SONSTIGER VERWENDUNG DER SOFTWARE ENTSTANDEN. 45 | 46 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include NEWS.rst README.rst LICENSE.txt 2 | include tests/testkey.txt 3 | recursive-include tests *.py 4 | recursive-include bintrees *.pyx *.pxd *.c *.h -------------------------------------------------------------------------------- /NEWS.rst: -------------------------------------------------------------------------------- 1 | 2 | NEWS 3 | ==== 4 | 5 | Version 2.1.0 - 2020-01-02 6 | 7 | * Use ``sortedcontainers`` instead: https://pypi.python.org/pypi/sortedcontainers 8 | * Project Status: Inactive 9 | * removed official Python 2 support 10 | 11 | Version 2.0.7 - 2017-04-28 12 | 13 | * BUGFIX: foreach (pure Python implementation) works with empty trees 14 | * acquire GIL for PyMem_Malloc() and PyMem_Free() calls 15 | 16 | Version 2.0.6 - 2017-02-04 17 | 18 | * BUGFIX: correct deepcopy() for tree in tree 19 | 20 | Version 2.0.5 - 2017-02-04 21 | 22 | * support for copy.deepcopy() 23 | * changed status back to `Mature`, there will be: bugfixes, compatibility checks and simple additions like this deep 24 | copy support, because I got feedback, that there are some special cases in which `bintrees` can be useful. 25 | * switched development to 64bit only & MS compilers - on Windows 7 everything works fine now with CPython 2.7/3.5/3.6 26 | 27 | Repository moved to GitHub: https://github.com/mozman/bintrees.git 28 | 29 | Version 2.0.4 - 2016-01-09 30 | 31 | * removed logging statements on import 32 | * added helper function bintrees.has_fast_tree_support() 33 | * HINT: pypy runs faster than CPython with Cython extension 34 | 35 | Version 2.0.3 - 2016-01-06 36 | 37 | * replaced print function by logging.warning for import warning messages 38 | * KNOWN ISSUE: unable to build Cython extension with MingW32 and CPython 3.5 & CPython 2.7.10 39 | 40 | Version 2.0.2 - 2015-02-12 41 | 42 | * fixed foreach cython-function by Sam Yaple 43 | 44 | Version 2.0.1 - 2013-10-01 45 | 46 | * removed __del__() method to avoid problems with garbage collection 47 | 48 | Version 2.0.0 - 2013-06-01 49 | 50 | * API change: consistent method naming with synonyms for dict/set compatibility 51 | * code base refactoring 52 | * removed tree walkers 53 | * removed low level node stack implementation -> caused crashes 54 | * optimizations for pypy: iter_items(), succ_item(), prev_item() 55 | * tested with CPython2.7, CPython3.3, pypy-2.0 on Win7 and Linux Mint 15 x64 (pypy-1.9) 56 | 57 | Version 1.0.3 - 2013-05-01 58 | 59 | * extended iter_items(startkey=None, endkey=None, reverse=reverse) -> better performance for slicing 60 | * Cython implementation of iter_items() for Fast_X_Trees() 61 | * added key parameter *reverse* to itemslice(), keyslice(), valueslice() 62 | * tested with CPython2.7, CPython3.3, pypy-2.0 63 | 64 | Version 1.0.2 - 2013-04-01 65 | 66 | * bug fix: FastRBTree data corruption on inserting existing keys 67 | * bug fix: union & symmetric_difference - copy all values to result tree 68 | 69 | Version 1.0.1 - 2013-02-01 70 | 71 | * bug fixes 72 | * refactorings by graingert 73 | * skip useless tests for pypy 74 | * new license: MIT License 75 | * tested with CPython2.7, CPython3.2, CPython3.3, pypy-1.9, pypy-2.0-beta1 76 | * unified line endings to LF 77 | * PEP8 refactorings 78 | * added floor_item/key, ceiling_item/key methods, thanks to Dai Mikurube 79 | 80 | Version 1.0.0 - 2011-12-29 81 | 82 | * bug fixes 83 | * status: 5 - Production/Stable 84 | * removed useless TreeIterator() class and T.treeiter() method. 85 | * patch from Max Motovilov to use Visual Studio 2008 for building C-extensions 86 | 87 | Version 0.4.0 - 2011-04-14 88 | 89 | * API change!!! 90 | * full Python 3 support, also for Cython implementations 91 | * removed user defined compare() function - keys have to be comparable! 92 | * removed T.has_key(), use 'key in T' 93 | * keys(), items(), values() generating 'views' 94 | * removed iterkeys(), itervalues(), iteritems() methods 95 | * replaced index slicing by key slicing 96 | * removed index() and item_at() 97 | * repr() produces a correct representation 98 | * installs on systems without cython (tested with pypy) 99 | * new license: GNU Library or Lesser General Public License (LGPL) 100 | 101 | Version 0.3.2 - 2011-04-09 102 | 103 | * added itemslice(startkey, endkey), keyslice(startkey, endkey), 104 | valueslice(startkey, endkey) - slicing by keys 105 | * tested with pypy 1.4.1, damn fast 106 | * Pure Python trees are working with Python 3 107 | * No Cython implementation for Python 3 108 | 109 | Version 0.3.1 - 2010-09-10 110 | 111 | * runs with Python 2.7 112 | 113 | Version 0.3.0 - 2010-05-11 114 | 115 | * low level functions written as c-module only interface to python is a cython 116 | module 117 | * support for the pickle protocol 118 | 119 | Version 0.2.1 - 2010-05-06 120 | 121 | * added delslice del T[0:3] -> remove treenodes 0, 1, 2 122 | * added discard -> remove key without KeyError if not found 123 | * added heap methods: min, max, nlarges, nsmallest ... 124 | * added Set methods -> intersection, differnce, union, ... 125 | * added slicing: T[5:10] get items with position (not key!) 5, 6, 7, 8, 9 126 | T[5] get item with key! 5 127 | * added index: T.index(key) -> get position of item 128 | * added item_at: T.item_at(0) -> get item at position (not key!) 0 129 | T.item_at(0) O(n)! <==> T.min_item() O(log(n)) 130 | 131 | Version 0.2.0 - 2010-05-03 132 | 133 | * TreeMixin Class as base for Python-Trees and as Mixin for Cython-Trees 134 | 135 | Version 0.1.0 - 2010-04-27 136 | 137 | * Alpha status 138 | * Initial release 139 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Binary Tree Package 2 | =================== 3 | 4 | Bintrees Development Stopped 5 | ---------------------------- 6 | 7 | Use `sortedcontainers` instead: https://pypi.python.org/pypi/sortedcontainers 8 | 9 | see also PyCon 2016 presentation: https://www.youtube.com/watch?v=7z2Ki44Vs4E 10 | 11 | Advantages: 12 | 13 | - pure Python no Cython/C dependencies 14 | - faster 15 | - active development 16 | - more & better testing/profiling 17 | 18 | Abstract 19 | ======== 20 | 21 | This package provides Binary- RedBlack- and AVL-Trees written in Python and Cython/C. 22 | 23 | This Classes are much slower than the built-in *dict* class, but all 24 | iterators/generators yielding data in sorted key order. Trees can be 25 | uses as drop in replacement for *dicts* in most cases. 26 | 27 | Source of Algorithms 28 | -------------------- 29 | 30 | AVL- and RBTree algorithms taken from Julienne Walker: http://eternallyconfuzzled.com/jsw_home.aspx 31 | 32 | Trees written in Python 33 | ----------------------- 34 | 35 | - *BinaryTree* -- unbalanced binary tree 36 | - *AVLTree* -- balanced AVL-Tree 37 | - *RBTree* -- balanced Red-Black-Tree 38 | 39 | Trees written with C-Functions and Cython as wrapper 40 | ---------------------------------------------------- 41 | 42 | - *FastBinaryTree* -- unbalanced binary tree 43 | - *FastAVLTree* -- balanced AVL-Tree 44 | - *FastRBTree* -- balanced Red-Black-Tree 45 | 46 | All trees provides the same API, the pickle protocol is supported. 47 | 48 | Cython-Trees have C-structs as tree-nodes and C-functions for low level operations: 49 | 50 | - insert 51 | - remove 52 | - get_value 53 | - min_item 54 | - max_item 55 | - prev_item 56 | - succ_item 57 | - floor_item 58 | - ceiling_item 59 | 60 | Constructor 61 | ~~~~~~~~~~~ 62 | 63 | * Tree() -> new empty tree; 64 | * Tree(mapping) -> new tree initialized from a mapping (requires only an items() method) 65 | * Tree(seq) -> new tree initialized from seq [(k1, v1), (k2, v2), ... (kn, vn)] 66 | 67 | Methods 68 | ~~~~~~~ 69 | 70 | * __contains__(k) -> True if T has a key k, else False, O(log(n)) 71 | * __delitem__(y) <==> del T[y], del[s:e], O(log(n)) 72 | * __getitem__(y) <==> T[y], T[s:e], O(log(n)) 73 | * __iter__() <==> iter(T) 74 | * __len__() <==> len(T), O(1) 75 | * __max__() <==> max(T), get max item (k,v) of T, O(log(n)) 76 | * __min__() <==> min(T), get min item (k,v) of T, O(log(n)) 77 | * __and__(other) <==> T & other, intersection 78 | * __or__(other) <==> T | other, union 79 | * __sub__(other) <==> T - other, difference 80 | * __xor__(other) <==> T ^ other, symmetric_difference 81 | * __repr__() <==> repr(T) 82 | * __setitem__(k, v) <==> T[k] = v, O(log(n)) 83 | * __copy__() -> shallow copy support, copy.copy(T) 84 | * __deepcopy__() -> deep copy support, copy.deepcopy(T) 85 | * clear() -> None, remove all items from T, O(n) 86 | * copy() -> a shallow copy of T, O(n*log(n)) 87 | * discard(k) -> None, remove k from T, if k is present, O(log(n)) 88 | * get(k[,d]) -> T[k] if k in T, else d, O(log(n)) 89 | * is_empty() -> True if len(T) == 0, O(1) 90 | * items([reverse]) -> generator for (k, v) items of T, O(n) 91 | * keys([reverse]) -> generator for keys of T, O(n) 92 | * values([reverse]) -> generator for values of T, O(n) 93 | * pop(k[,d]) -> v, remove specified key and return the corresponding value, O(log(n)) 94 | * pop_item() -> (k, v), remove and return some (key, value) pair as a 2-tuple, O(log(n)) (synonym popitem() exist) 95 | * set_default(k[,d]) -> value, T.get(k, d), also set T[k]=d if k not in T, O(log(n)) (synonym setdefault() exist) 96 | * update(E) -> None. Update T from dict/iterable E, O(E*log(n)) 97 | * foreach(f, [order]) -> visit all nodes of tree (0 = 'inorder', -1 = 'preorder' or +1 = 'postorder') and call f(k, v) for each node, O(n) 98 | * iter_items(s, e[, reverse]) -> generator for (k, v) items of T for s <= key < e, O(n) 99 | * remove_items(keys) -> None, remove items by keys, O(n) 100 | 101 | slicing by keys 102 | ~~~~~~~~~~~~~~~ 103 | 104 | * item_slice(s, e[, reverse]) -> generator for (k, v) items of T for s <= key < e, O(n), synonym for iter_items(...) 105 | * key_slice(s, e[, reverse]) -> generator for keys of T for s <= key < e, O(n) 106 | * value_slice(s, e[, reverse]) -> generator for values of T for s <= key < e, O(n) 107 | * T[s:e] -> TreeSlice object, with keys in range s <= key < e, O(n) 108 | * del T[s:e] -> remove items by key slicing, for s <= key < e, O(n) 109 | 110 | start/end parameter: 111 | 112 | * if 's' is None or T[:e] TreeSlice/iterator starts with value of min_key(); 113 | * if 'e' is None or T[s:] TreeSlice/iterator ends with value of max_key(); 114 | * T[:] is a TreeSlice which represents the whole tree; 115 | 116 | The step argument of the regular slicing syntax T[s:e:step] will silently ignored. 117 | 118 | TreeSlice is a tree wrapper with range check and contains no references 119 | to objects, deleting objects in the associated tree also deletes the object 120 | in the TreeSlice. 121 | 122 | * TreeSlice[k] -> get value for key k, raises KeyError if k not exists in range s:e 123 | * TreeSlice[s1:e1] -> TreeSlice object, with keys in range s1 <= key < e1 124 | - new lower bound is max(s, s1) 125 | - new upper bound is min(e, e1) 126 | 127 | TreeSlice methods: 128 | 129 | * items() -> generator for (k, v) items of T, O(n) 130 | * keys() -> generator for keys of T, O(n) 131 | * values() -> generator for values of T, O(n) 132 | * __iter__ <==> keys() 133 | * __repr__ <==> repr(T) 134 | * __contains__(key)-> True if TreeSlice has a key k, else False, O(log(n)) 135 | 136 | prev/succ operations 137 | ~~~~~~~~~~~~~~~~~~~~ 138 | 139 | * prev_item(key) -> get (k, v) pair, where k is predecessor to key, O(log(n)) 140 | * prev_key(key) -> k, get the predecessor of key, O(log(n)) 141 | * succ_item(key) -> get (k,v) pair as a 2-tuple, where k is successor to key, O(log(n)) 142 | * succ_key(key) -> k, get the successor of key, O(log(n)) 143 | * floor_item(key) -> get (k, v) pair, where k is the greatest key less than or equal to key, O(log(n)) 144 | * floor_key(key) -> k, get the greatest key less than or equal to key, O(log(n)) 145 | * ceiling_item(key) -> get (k, v) pair, where k is the smallest key greater than or equal to key, O(log(n)) 146 | * ceiling_key(key) -> k, get the smallest key greater than or equal to key, O(log(n)) 147 | 148 | Heap methods 149 | ~~~~~~~~~~~~ 150 | 151 | * max_item() -> get largest (key, value) pair of T, O(log(n)) 152 | * max_key() -> get largest key of T, O(log(n)) 153 | * min_item() -> get smallest (key, value) pair of T, O(log(n)) 154 | * min_key() -> get smallest key of T, O(log(n)) 155 | * pop_min() -> (k, v), remove item with minimum key, O(log(n)) 156 | * pop_max() -> (k, v), remove item with maximum key, O(log(n)) 157 | * nlargest(i[,pop]) -> get list of i largest items (k, v), O(i*log(n)) 158 | * nsmallest(i[,pop]) -> get list of i smallest items (k, v), O(i*log(n)) 159 | 160 | Set methods (using frozenset) 161 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 162 | 163 | * intersection(t1, t2, ...) -> Tree with keys *common* to all trees 164 | * union(t1, t2, ...) -> Tree with keys from *either* trees 165 | * difference(t1, t2, ...) -> Tree with keys in T but not any of t1, t2, ... 166 | * symmetric_difference(t1) -> Tree with keys in either T and t1 but not both 167 | * is_subset(S) -> True if every element in T is in S (synonym issubset() exist) 168 | * is_superset(S) -> True if every element in S is in T (synonym issuperset() exist) 169 | * is_disjoint(S) -> True if T has a null intersection with S (synonym isdisjoint() exist) 170 | 171 | Classmethods 172 | ~~~~~~~~~~~~ 173 | 174 | * from_keys(S[,v]) -> New tree with keys from S and values equal to v. (synonym fromkeys() exist) 175 | 176 | Helper functions 177 | ~~~~~~~~~~~~~~~~ 178 | 179 | * bintrees.has_fast_tree_support() -> True if Cython extension is working else False (False = using pure Python implementation) 180 | 181 | Installation 182 | ============ 183 | 184 | from source:: 185 | 186 | python setup.py install 187 | 188 | or from PyPI:: 189 | 190 | pip install bintrees 191 | 192 | Compiling the fast Trees requires Cython and on Windows is a C-Compiler necessary. 193 | 194 | Download Binaries for Windows 195 | ============================= 196 | 197 | https://github.com/mozman/bintrees/releases 198 | 199 | Documentation 200 | ============= 201 | 202 | this README.rst 203 | 204 | bintrees can be found on GitHub.com at: 205 | 206 | https://github.com/mozman/bintrees.git 207 | -------------------------------------------------------------------------------- /bintrees/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Purpose: binary trees package 5 | # Created: 03.05.2010 6 | # Copyright (c) 2010-2013 by Manfred Moitzi 7 | # License: MIT License 8 | 9 | from __future__ import absolute_import 10 | 11 | __doc__ = """ 12 | Binary Tree Package 13 | =================== 14 | 15 | Python Trees 16 | ------------ 17 | 18 | Balanced and unbalanced binary trees written in pure Python with a dict-like API. 19 | 20 | Classes 21 | ~~~~~~~ 22 | * BinaryTree -- unbalanced binary tree 23 | * AVLTree -- balanced AVL-Tree 24 | * RBTree -- balanced Red-Black-Tree 25 | 26 | Cython Trees 27 | ------------ 28 | 29 | Basic tree functions written in Cython/C, merged with _ABCTree() to provide the 30 | full API of the Python Trees. 31 | 32 | Classes 33 | ~~~~~~~ 34 | 35 | * FastBinaryTree -- unbalanced binary tree 36 | * FastAVLTree -- balanced AVLTree 37 | * FastRBTree -- balanced Red-Black-Tree 38 | 39 | Overview of API for all Classes 40 | =============================== 41 | 42 | * TreeClass ([compare]) -> new empty tree. 43 | * TreeClass(mapping, [compare]) -> new tree initialized from a mapping 44 | * TreeClass(seq, [compare]) -> new tree initialized from seq [(k1, v1), (k2, v2), ... (kn, vn)] 45 | 46 | Methods 47 | ------- 48 | 49 | * __contains__(k) -> True if T has a key k, else False, O(log(n)) 50 | * __delitem__(y) <==> del T[y], O(log(n)) 51 | * __getitem__(y) <==> T[y], O(log(n)) 52 | * __iter__() <==> iter(T) 53 | * __len__() <==> len(T), O(1) 54 | * __max__() <==> max(T), get max item (k,v) of T, O(log(n)) 55 | * __min__() <==> min(T), get min item (k,v) of T, O(log(n)) 56 | * __and__(other) <==> T & other, intersection 57 | * __or__(other) <==> T | other, union 58 | * __sub__(other) <==> T - other, difference 59 | * __xor__(other) <==> T ^ other, symmetric_difference 60 | * __repr__() <==> repr(T) 61 | * __setitem__(k, v) <==> T[k] = v, O(log(n)) 62 | * clear() -> None, Remove all items from T, , O(n) 63 | * copy() -> a shallow copy of T, O(n*log(n)) 64 | * discard(k) -> None, remove k from T, if k is present, O(log(n)) 65 | * get(k[,d]) -> T[k] if k in T, else d, O(log(n)) 66 | * is_empty() -> True if len(T) == 0, O(1) 67 | * items([reverse]) -> list of T's (k, v) pairs, as 2-tuple, O(n) 68 | * keys([reverse]) -> list of T's keys, O(n) 69 | * values([reverse]) -> list of T's values, O(n) 70 | * pop(k[,d]) -> v, remove specified key and return the corresponding value, O(log(n)) 71 | * pop_item() -> (k, v), remove and return some (key, value) pair as a 2-tuple, O(log(n)) 72 | * set_default(k[,d]) -> T.get(k, d), also set T[k]=d if k not in T, O(log(n)) 73 | * update(E) -> None. Update T from dict/iterable E, O(E*log(n)) 74 | * iter_items(s, e, reverse) -> generator for (k, v) items of T for s <= key < e, O(n) 75 | 76 | walk forward/backward, O(log(n)) 77 | 78 | * prev_item(key) -> get (k, v) pair, where k is predecessor to key, O(log(n)) 79 | * prev_key(key) -> k, get the predecessor of key, O(log(n)) 80 | * succ_item(key) -> get (k,v) pair as a 2-tuple, where k is successor to key, O(log(n)) 81 | * succ_key(key) -> k, get the successor of key, O(log(n)) 82 | 83 | slicing by keys 84 | 85 | * item_slice(s, e, reverse) -> generator for (k, v) items of T for s <= key < e, O(n), synonym for iter_items(...) 86 | * key_slice(s, e, reverse) -> generator for keys of T for s <= key < e, O(n) 87 | * value_slice(s, e, reverse) -> generator for values of T for s <= key < e, O(n) 88 | * T[s:e] -> TreeSlice object, with keys in range s <= key < e, O(n) 89 | * del T[s:e] -> remove items by key slicing, for s <= key < e, O(n) 90 | 91 | if 's' is None or T[:e] TreeSlice/iterator starts with value of min_key() 92 | if 'e' is None or T[s:] TreeSlice/iterator ends with value of max_key() 93 | T[:] is a TreeSlice which represents the whole tree. 94 | 95 | The step argument of the regular slicing syntax T[s:e:step] will silently ignored. 96 | 97 | TreeSlice is a tree wrapper with range check, and contains no references 98 | to objects, deleting objects in the associated tree also deletes the object 99 | in the TreeSlice. 100 | 101 | * TreeSlice[k] -> get value for key k, raises KeyError if k not exists in range s:e 102 | * TreeSlice[s1:e1] -> TreeSlice object, with keys in range s1 <= key < e1 103 | 104 | * new lower bound is max(s, s1) 105 | * new upper bound is min(e, e1) 106 | 107 | TreeSlice methods: 108 | 109 | * items() -> generator for (k, v) items of T, O(n) 110 | * keys() -> generator for keys of T, O(n) 111 | * values() -> generator for values of T, O(n) 112 | * __iter__ <==> keys() 113 | * __repr__ <==> repr(T) 114 | * __contains__(key)-> True if TreeSlice has a key k, else False, O(log(n)) 115 | 116 | Heap methods 117 | 118 | * max_item() -> get biggest (key, value) pair of T, O(log(n)) 119 | * max_key() -> get biggest key of T, O(log(n)) 120 | * min_item() -> get smallest (key, value) pair of T, O(log(n)) 121 | * min_key() -> get smallest key of T, O(log(n)) 122 | * pop_min() -> (k, v), remove item with minimum key, O(log(n)) 123 | * pop_max() -> (k, v), remove item with maximum key, O(log(n)) 124 | * nlargest(i[,pop]) -> get list of i largest items (k, v), O(i*log(n)) 125 | * nsmallest(i[,pop]) -> get list of i smallest items (k, v), O(i*log(n)) 126 | 127 | Set methods (using frozenset) 128 | 129 | * intersection(t1, t2, ...) -> Tree with keys *common* to all trees 130 | * union(t1, t2, ...) -> Tree with keys from *either* trees 131 | * difference(t1, t2, ...) -> Tree with keys in T but not any of t1, t2, ... 132 | * symmetric_difference(t1) -> Tree with keys in either T and t1 but not both 133 | * is_subset(S) -> True if every element in T is in S 134 | * is_superset(S) -> True if every element in S is in T 135 | * is_disjoint(S) -> True if T has a null intersection with S 136 | 137 | Classmethods 138 | 139 | * from_keys(S[,v]) -> New tree with keys from S and values equal to v. 140 | 141 | Helper functions 142 | 143 | * bintrees.has_fast_tree_support() -> True if Cython extension is working else False (False = using pure Python implementation) 144 | 145 | """ 146 | 147 | from .bintree import BinaryTree 148 | from .avltree import AVLTree 149 | from .rbtree import RBTree 150 | 151 | 152 | def has_fast_tree_support(): 153 | return FastBinaryTree is not BinaryTree 154 | 155 | 156 | try: 157 | from .cython_trees import FastBinaryTree 158 | except ImportError: # fall back to pure Python version 159 | FastBinaryTree = BinaryTree 160 | 161 | try: 162 | from .cython_trees import FastAVLTree 163 | except ImportError: # fall back to pure Python version 164 | FastAVLTree = AVLTree 165 | 166 | try: 167 | from .cython_trees import FastRBTree 168 | except ImportError: # fall back to pure Python version 169 | FastRBTree = RBTree 170 | -------------------------------------------------------------------------------- /bintrees/abctree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding:utf-8 3 | # Author: Mozman 4 | # Purpose: abstract base class for all binary trees 5 | # Created: 03.05.2010 6 | # Copyright (c) 2010-2013 by Manfred Moitzi 7 | # License: MIT License 8 | from __future__ import absolute_import 9 | import sys 10 | from .treeslice import TreeSlice 11 | from operator import attrgetter 12 | from copy import deepcopy 13 | from abc import abstractmethod, abstractproperty 14 | 15 | PYPY = hasattr(sys, 'pypy_version_info') 16 | 17 | 18 | class _ABCTree(object): 19 | """ 20 | Abstract-Base-Class for ABCTree and Cython trees. 21 | 22 | The _ABCTree Class 23 | ================== 24 | 25 | T has to implement following properties 26 | --------------------------------------- 27 | 28 | count -- get node count 29 | 30 | T has to implement following methods 31 | ------------------------------------ 32 | 33 | get_value(...) 34 | get_value(key) -> returns value for key 35 | 36 | insert(...) 37 | insert(key, value) <==> T[key] = value, insert key into T 38 | 39 | remove(...) 40 | remove(key) <==> del T[key], remove key from T 41 | 42 | clear(...) 43 | T.clear() -> None. Remove all items from T. 44 | 45 | iter_items(...) 46 | iter_items(s, e, [reverse]) -> iterate over all items with keys in range s <= key < e, yielding (k, v) tuple 47 | 48 | foreach(...) 49 | foreach(f, [order]) -> visit all nodes of tree and call f(k, v) for each node 50 | 51 | pop_item(...) 52 | T.pop_item() -> (k, v), remove and return some (key, value) 53 | 54 | min_item(...) 55 | min_item() -> get smallest (key, value) pair of T 56 | 57 | max_item(...) 58 | max_item() -> get largest (key, value) pair of T 59 | 60 | prev_item(...) 61 | prev_item(key) -> get (k, v) pair, where k is predecessor to key 62 | 63 | succ_item(...) 64 | succ_item(key) -> get (k,v) pair as a 2-tuple, where k is successor to key 65 | 66 | floor_item(...) 67 | floor_item(key) -> get (k, v) pair, where k is the greatest key less than or equal to key 68 | 69 | ceiling_item(...) 70 | ceiling_item(key) -> get (k, v) pair, where k is the smallest key greater than or equal to key 71 | 72 | Methods defined here 73 | -------------------- 74 | 75 | * __contains__(k) -> True if T has a key k, else False, O(log(n)) 76 | * __delitem__(y) <==> del T[y], del T[s:e], O(log(n)) 77 | * __getitem__(y) <==> T[y], T[s:e], O(log(n)) 78 | * __iter__() <==> iter(T) 79 | * __len__() <==> len(T), O(1) 80 | * __max__() <==> max(T), get max item (k,v) of T, O(log(n)) 81 | * __min__() <==> min(T), get min item (k,v) of T, O(log(n)) 82 | * __and__(other) <==> T & other, intersection 83 | * __or__(other) <==> T | other, union 84 | * __sub__(other) <==> T - other, difference 85 | * __xor__(other) <==> T ^ other, symmetric_difference 86 | * __repr__() <==> repr(T) 87 | * __setitem__(k, v) <==> T[k] = v, O(log(n)) 88 | * __copy__() -> shallow copy support, copy.copy(T) 89 | * __deepcopy__() -> deep copy support, copy.deepcopy(T) 90 | * clear() -> None, remove all items from T, , O(n) 91 | * remove_items(keys) -> None, remove items by keys 92 | * copy() -> a shallow copy of T, O(n*log(n)) 93 | * discard(k) -> None, remove k from T, if k is present, O(log(n)) 94 | * get(k[,d]) -> T[k] if k in T, else d, O(log(n)) 95 | * is_empty() -> True if len(T) == 0, O(1) 96 | * keys([reverse]) -> generator for keys of T, O(n) 97 | * values([reverse]) -> generator for values of T, O(n) 98 | * pop(k[,d]) -> v, remove specified key and return the corresponding value, O(log(n)) 99 | * set_default(k[,d]) -> value, T.get(k, d), also set T[k]=d if k not in T, O(log(n)) 100 | * update(E) -> None. Update T from dict/iterable E, O(E*log(n)) 101 | 102 | slicing by keys 103 | 104 | * key_slice(s, e[, reverse]) -> generator for keys of T for s <= key < e, O(n) 105 | * value_slice(s, e[, reverse]) -> generator for values of T for s <= key < e, O(n) 106 | * item_slice(s, e[, reverse]) -> generator for items of T for s <= key < e, O(n) 107 | * T[s:e] -> TreeSlice object, with keys in range s <= key < e, O(n) 108 | * del T[s:e] -> remove items by key slicing, for s <= key < e, O(n) 109 | 110 | if 's' is None or T[:e] TreeSlice/iterator starts with value of min_key() 111 | if 'e' is None or T[s:] TreeSlice/iterator ends with value of max_key() 112 | T[:] is a TreeSlice which represents the whole tree. 113 | 114 | TreeSlice is a tree wrapper with range check and contains no references 115 | to objects, deleting objects in the associated tree also deletes the object 116 | in the TreeSlice. 117 | 118 | * TreeSlice[k] -> get value for key k, raises KeyError if k not exists in range s:e 119 | * TreeSlice[s1:e1] -> TreeSlice object, with keys in range s1 <= key < e1 120 | 121 | * new lower bound is max(s, s1) 122 | * new upper bound is min(e, e1) 123 | 124 | TreeSlice methods: 125 | 126 | * items() -> generator for (k, v) items of T, O(n) 127 | * keys() -> generator for keys of T, O(n) 128 | * values() -> generator for values of T, O(n) 129 | * __iter__ <==> keys() 130 | * __repr__ <==> repr(T) 131 | * __contains__(key)-> True if TreeSlice has a key k, else False, O(log(n)) 132 | 133 | prev/succ operations 134 | 135 | * prev_key(key) -> k, get the predecessor of key, O(log(n)) 136 | * succ_key(key) -> k, get the successor of key, O(log(n)) 137 | * floor_key(key) -> k, get the greatest key less than or equal to key, O(log(n)) 138 | * ceiling_key(key) -> k, get the smallest key greater than or equal to key, O(log(n)) 139 | 140 | Heap methods 141 | 142 | * max_key() -> get largest key of T, O(log(n)) 143 | * min_key() -> get smallest key of T, O(log(n)) 144 | * pop_min() -> (k, v), remove item with minimum key, O(log(n)) 145 | * pop_max() -> (k, v), remove item with maximum key, O(log(n)) 146 | * nlargest(i[,pop]) -> get list of i largest items (k, v), O(i*log(n)) 147 | * nsmallest(i[,pop]) -> get list of i smallest items (k, v), O(i*log(n)) 148 | 149 | Set methods (using frozenset) 150 | 151 | * intersection(t1, t2, ...) -> Tree with keys *common* to all trees 152 | * union(t1, t2, ...) -> Tree with keys from *either* trees 153 | * difference(t1, t2, ...) -> Tree with keys in T but not any of t1, t2, ... 154 | * symmetric_difference(t1) -> Tree with keys in either T and t1 but not both 155 | * is_subset(S) -> True if every element in T is in S 156 | * is_superset(S) -> True if every element in S is in T 157 | * is_disjoint(S) -> True if T has a null intersection with S 158 | 159 | Classmethods 160 | 161 | * from_keys(S[,v]) -> New tree with keys from S and values equal to v. 162 | 163 | """ 164 | 165 | @abstractmethod 166 | def insert(self, key, value): 167 | pass 168 | 169 | @abstractmethod 170 | def remove(self, key): 171 | pass 172 | 173 | @abstractmethod 174 | def get_value(self, key): 175 | pass 176 | 177 | @abstractmethod 178 | def min_item(self): 179 | pass 180 | 181 | @abstractmethod 182 | def max_item(self): 183 | pass 184 | 185 | @abstractmethod 186 | def prev_item(self): 187 | pass 188 | 189 | @abstractmethod 190 | def succ_item(self): 191 | pass 192 | 193 | @abstractmethod 194 | def floor_item(self): 195 | pass 196 | 197 | @abstractmethod 198 | def ceiling_item(self): 199 | pass 200 | 201 | @abstractmethod 202 | def iter_items(self, start_key=None, end_key=None, reverse=False): 203 | return [] 204 | 205 | @abstractmethod 206 | def foreach(self, func, order=0): 207 | pass 208 | 209 | @property 210 | def count(self): 211 | return 0 212 | 213 | def __repr__(self): 214 | """T.__repr__(...) <==> repr(x)""" 215 | tpl = "%s({%s})" % (self.__class__.__name__, '%s') 216 | return tpl % ", ".join(("%r: %r" % item for item in self.items())) 217 | 218 | def copy(self): 219 | """T.copy() -> get a shallow copy of T.""" 220 | tree = self.__class__() 221 | self.foreach(tree.insert, order=-1) 222 | return tree 223 | 224 | __copy__ = copy 225 | 226 | def __deepcopy__(self, memo): 227 | """copy.deepcopy(T) -> get a deep copy of T.""" 228 | 229 | def _deepcopy(key, value): 230 | value_copy = deepcopy(value, memo) 231 | tree.insert(key, value_copy) 232 | 233 | tree = type(self)() 234 | memo[id(self)] = tree 235 | self.foreach(_deepcopy, order=-1) 236 | return tree 237 | 238 | def __contains__(self, key): 239 | """k in T -> True if T has a key k, else False""" 240 | try: 241 | self.get_value(key) 242 | return True 243 | except KeyError: 244 | return False 245 | 246 | def __len__(self): 247 | """T.__len__() <==> len(x)""" 248 | return self.count 249 | 250 | def __min__(self): 251 | """T.__min__() <==> min(x)""" 252 | return self.min_item() 253 | 254 | def __max__(self): 255 | """T.__max__() <==> max(x)""" 256 | return self.max_item() 257 | 258 | def __and__(self, other): 259 | """T.__and__(other) <==> self & other""" 260 | return self.intersection(other) 261 | 262 | def __or__(self, other): 263 | """T.__or__(other) <==> self | other""" 264 | return self.union(other) 265 | 266 | def __sub__(self, other): 267 | """T.__sub__(other) <==> self - other""" 268 | return self.difference(other) 269 | 270 | def __xor__(self, other): 271 | """T.__xor__(other) <==> self ^ other""" 272 | return self.symmetric_difference(other) 273 | 274 | def discard(self, key): 275 | """T.discard(k) -> None, remove k from T, if k is present""" 276 | try: 277 | self.remove(key) 278 | except KeyError: 279 | pass 280 | 281 | def is_empty(self): 282 | """T.is_empty() -> False if T contains any items else True""" 283 | return self.count == 0 284 | 285 | def keys(self, reverse=False): 286 | """T.keys([reverse]) -> an iterator over the keys of T, in ascending 287 | order if reverse is True, iterate in descending order, reverse defaults 288 | to False 289 | """ 290 | return (item[0] for item in self.iter_items(reverse=reverse)) 291 | 292 | __iter__ = keys 293 | 294 | def __reversed__(self): 295 | return self.keys(reverse=True) 296 | 297 | def values(self, reverse=False): 298 | """T.values([reverse]) -> an iterator over the values of T, in ascending order 299 | if reverse is True, iterate in descending order, reverse defaults to False 300 | """ 301 | return (item[1] for item in self.iter_items(reverse=reverse)) 302 | 303 | def items(self, reverse=False): 304 | """T.items([reverse]) -> an iterator over the (key, value) items of T, 305 | in ascending order if reverse is True, iterate in descending order, 306 | reverse defaults to False 307 | """ 308 | return self.iter_items(reverse=reverse) 309 | 310 | def __getitem__(self, key): 311 | """T.__getitem__(y) <==> x[y]""" 312 | if isinstance(key, slice): 313 | return TreeSlice(self, key.start, key.stop) 314 | else: 315 | return self.get_value(key) 316 | 317 | def __setitem__(self, key, value): 318 | """T.__setitem__(i, y) <==> x[i]=y""" 319 | if isinstance(key, slice): 320 | raise ValueError('setslice is not supported') 321 | self.insert(key, value) 322 | 323 | def __delitem__(self, key): 324 | """T.__delitem__(y) <==> del x[y]""" 325 | if isinstance(key, slice): 326 | self.remove_items(self.key_slice(key.start, key.stop)) 327 | else: 328 | self.remove(key) 329 | 330 | def remove_items(self, keys): 331 | """T.remove_items(keys) -> None, remove items by keys""" 332 | # convert generator to a tuple, because the content of the 333 | # tree will be modified! 334 | for key in tuple(keys): 335 | self.remove(key) 336 | 337 | def key_slice(self, start_key, end_key, reverse=False): 338 | """T.key_slice(start_key, end_key) -> key iterator: 339 | start_key <= key < end_key. 340 | 341 | Yields keys in ascending order if reverse is False else in descending order. 342 | """ 343 | return (k for k, v in self.iter_items(start_key, end_key, reverse=reverse)) 344 | 345 | def value_slice(self, start_key, end_key, reverse=False): 346 | """T.value_slice(start_key, end_key) -> value iterator: 347 | start_key <= key < end_key. 348 | 349 | Yields values in ascending key order if reverse is False else in descending key order. 350 | """ 351 | return (v for k, v in self.iter_items(start_key, end_key, reverse=reverse)) 352 | 353 | def item_slice(self, start_key, end_key, reverse=False): 354 | """T.item_slice(start_key, end_key) -> item iterator: 355 | start_key <= key < end_key. 356 | 357 | Yields items in ascending key order if reverse is False else in descending key order. 358 | """ 359 | return self.iter_items(start_key, end_key, reverse) 360 | 361 | def __getstate__(self): 362 | return dict(self.items()) 363 | 364 | def __setstate__(self, state): 365 | # note for myself: this is called like __init__, so don't use clear() 366 | # to remove existing data! 367 | self._root = None 368 | self._count = 0 369 | self.update(state) 370 | 371 | def set_default(self, key, default=None): 372 | """T.set_default(k[,d]) -> T.get(k,d), also set T[k]=d if k not in T""" 373 | try: 374 | return self.get_value(key) 375 | except KeyError: 376 | self.insert(key, default) 377 | return default 378 | 379 | setdefault = set_default # for compatibility to dict() 380 | 381 | def update(self, *args): 382 | """T.update(E) -> None. Update T from E : for (k, v) in E: T[k] = v""" 383 | for items in args: 384 | try: 385 | generator = items.items() 386 | except AttributeError: 387 | generator = iter(items) 388 | 389 | for key, value in generator: 390 | self.insert(key, value) 391 | 392 | @classmethod 393 | def from_keys(cls, iterable, value=None): 394 | """T.from_keys(S[,v]) -> New tree with keys from S and values equal to v.""" 395 | tree = cls() 396 | for key in iterable: 397 | tree.insert(key, value) 398 | return tree 399 | 400 | fromkeys = from_keys # for compatibility to dict() 401 | 402 | def get(self, key, default=None): 403 | """T.get(k[,d]) -> T[k] if k in T, else d. d defaults to None.""" 404 | try: 405 | return self.get_value(key) 406 | except KeyError: 407 | return default 408 | 409 | def pop(self, key, *args): 410 | """T.pop(k[,d]) -> v, remove specified key and return the corresponding value. 411 | If key is not found, d is returned if given, otherwise KeyError is raised 412 | """ 413 | if len(args) > 1: 414 | raise TypeError("pop expected at most 2 arguments, got %d" % (1 + len(args))) 415 | try: 416 | value = self.get_value(key) 417 | self.remove(key) 418 | return value 419 | except KeyError: 420 | if len(args) == 0: 421 | raise 422 | else: 423 | return args[0] 424 | 425 | def prev_key(self, key): 426 | """Get predecessor to key, raises KeyError if key is min key 427 | or key does not exist. 428 | """ 429 | return self.prev_item(key)[0] 430 | 431 | def succ_key(self, key): 432 | """Get successor to key, raises KeyError if key is max key 433 | or key does not exist. 434 | """ 435 | return self.succ_item(key)[0] 436 | 437 | def floor_key(self, key): 438 | """Get the greatest key less than or equal to the given key, raises 439 | KeyError if there is no such key. 440 | """ 441 | return self.floor_item(key)[0] 442 | 443 | def ceiling_key(self, key): 444 | """Get the smallest key greater than or equal to the given key, raises 445 | KeyError if there is no such key. 446 | """ 447 | return self.ceiling_item(key)[0] 448 | 449 | def pop_min(self): 450 | """T.pop_min() -> (k, v), remove item with minimum key, raise ValueError 451 | if T is empty. 452 | """ 453 | item = self.min_item() 454 | self.remove(item[0]) 455 | return item 456 | 457 | def pop_max(self): 458 | """T.pop_max() -> (k, v), remove item with maximum key, raise ValueError 459 | if T is empty. 460 | """ 461 | item = self.max_item() 462 | self.remove(item[0]) 463 | return item 464 | 465 | def min_key(self): 466 | """Get min key of tree, raises ValueError if tree is empty. """ 467 | return self.min_item()[0] 468 | 469 | def max_key(self): 470 | """Get max key of tree, raises ValueError if tree is empty. """ 471 | return self.max_item()[0] 472 | 473 | def nsmallest(self, n, pop=False): 474 | """T.nsmallest(n) -> get list of n smallest items (k, v). 475 | If pop is True, remove items from T. 476 | """ 477 | if pop: 478 | return [self.pop_min() for _ in range(min(len(self), n))] 479 | else: 480 | items = self.items() 481 | return [next(items) for _ in range(min(len(self), n))] 482 | 483 | def nlargest(self, n, pop=False): 484 | """T.nlargest(n) -> get list of n largest items (k, v). 485 | If pop is True remove items from T. 486 | """ 487 | if pop: 488 | return [self.pop_max() for _ in range(min(len(self), n))] 489 | else: 490 | items = self.items(reverse=True) 491 | return [next(items) for _ in range(min(len(self), n))] 492 | 493 | def intersection(self, *trees): 494 | """T.intersection(t1, t2, ...) -> Tree, with keys *common* to all trees 495 | """ 496 | thiskeys = frozenset(self.keys()) 497 | sets = _build_sets(trees) 498 | rkeys = thiskeys.intersection(*sets) 499 | return self.__class__(((key, self.get(key)) for key in rkeys)) 500 | 501 | def union(self, *trees): 502 | """T.union(t1, t2, ...) -> Tree with keys from *either* trees 503 | """ 504 | thiskeys = frozenset(self.keys()) 505 | rkeys = thiskeys.union(*_build_sets(trees)) 506 | all_trees = [self] 507 | all_trees.extend(trees) 508 | return self.__class__(((key, _multi_tree_get(all_trees, key)) for key in rkeys)) 509 | 510 | def difference(self, *trees): 511 | """T.difference(t1, t2, ...) -> Tree with keys in T but not any of t1, 512 | t2, ... 513 | """ 514 | thiskeys = frozenset(self.keys()) 515 | rkeys = thiskeys.difference(*_build_sets(trees)) 516 | return self.__class__(((key, self.get(key)) for key in rkeys)) 517 | 518 | def symmetric_difference(self, tree): 519 | """T.symmetric_difference(t1) -> Tree with keys in either T and t1 but 520 | not both 521 | """ 522 | thiskeys = frozenset(self.keys()) 523 | rkeys = thiskeys.symmetric_difference(frozenset(tree.keys())) 524 | all_trees = [self, tree] 525 | return self.__class__(((key, _multi_tree_get(all_trees, key)) for key in rkeys)) 526 | 527 | def is_subset(self, tree): 528 | """T.issubset(tree) -> True if every element in x is in tree """ 529 | thiskeys = frozenset(self.keys()) 530 | return thiskeys.issubset(frozenset(tree.keys())) 531 | 532 | issubset = is_subset # for compatibility to set() 533 | 534 | def is_superset(self, tree): 535 | """T.issubset(tree) -> True if every element in tree is in x """ 536 | thiskeys = frozenset(self.keys()) 537 | return thiskeys.issuperset(frozenset(tree.keys())) 538 | 539 | issuperset = is_superset # for compatibility to set() 540 | 541 | def is_disjoint(self, tree): 542 | """T.isdisjoint(S) -> True if x has a null intersection with tree """ 543 | thiskeys = frozenset(self.keys()) 544 | return thiskeys.isdisjoint(frozenset(tree.keys())) 545 | 546 | isdisjoint = is_disjoint # for compatibility to set() 547 | 548 | 549 | def _build_sets(trees): 550 | return [frozenset(tree.keys()) for tree in trees] 551 | 552 | 553 | def _multi_tree_get(trees, key): 554 | for tree in trees: 555 | try: 556 | return tree[key] 557 | except KeyError: 558 | pass 559 | raise KeyError(key) 560 | 561 | 562 | class CPYTHON_ABCTree(_ABCTree): 563 | """ Base class for the Python implementation of trees. 564 | 565 | T has to implement following methods 566 | ------------------------------------ 567 | 568 | insert(...) 569 | insert(key, value) <==> T[key] = value, insert key into T 570 | 571 | remove(...) 572 | remove(key) <==> del T[key], remove key from T 573 | 574 | Properties defined here 575 | -------------------- 576 | 577 | * count -> get item count of tree 578 | 579 | Methods defined here 580 | -------------------- 581 | * __init__() Tree initializer 582 | * get_value(key) -> returns value for key 583 | * clear() -> None. Remove all items from tree. 584 | * iter_items(start_key, end_key, [reverse]) -> iterate over all items, yielding (k, v) tuple 585 | * foreach(f, [order]) -> visit all nodes of tree and call f(k, v) for each node, O(n) 586 | * pop_item() -> (k, v), remove and return some (key, value) 587 | * min_item() -> get smallest (key, value) pair of T, O(log(n)) 588 | * max_item() -> get largest (key, value) pair of T, O(log(n)) 589 | * prev_item(key) -> get (k, v) pair, where k is predecessor to key, O(log(n)) 590 | * succ_item(key) -> get (k,v) pair as a 2-tuple, where k is successor to key, O(log(n)) 591 | * floor_item(key) -> get (k, v) pair, where k is the greatest key less than or equal to key, O(log(n)) 592 | * ceiling_item(key) -> get (k, v) pair, where k is the smallest key greater than or equal to key, O(log(n)) 593 | """ 594 | 595 | def __init__(self, items=None): 596 | """T.__init__(...) initializes T; see T.__class__.__doc__ for signature""" 597 | self._root = None 598 | self._count = 0 599 | if items is not None: 600 | self.update(items) 601 | 602 | def clear(self): 603 | """T.clear() -> None. Remove all items from T.""" 604 | 605 | def _clear(node): 606 | if node is not None: 607 | _clear(node.left) 608 | _clear(node.right) 609 | node.free() 610 | 611 | _clear(self._root) 612 | self._count = 0 613 | self._root = None 614 | 615 | @property 616 | def count(self): 617 | """Get items count.""" 618 | return self._count 619 | 620 | def get_value(self, key): 621 | node = self._root 622 | while node is not None: 623 | if key == node.key: 624 | return node.value 625 | elif key < node.key: 626 | node = node.left 627 | else: 628 | node = node.right 629 | raise KeyError(str(key)) 630 | 631 | def pop_item(self): 632 | """T.pop_item() -> (k, v), remove and return some (key, value) pair as a 633 | 2-tuple; but raise KeyError if T is empty. 634 | """ 635 | if self.is_empty(): 636 | raise KeyError("pop_item(): tree is empty") 637 | node = self._root 638 | while True: 639 | if node.left is not None: 640 | node = node.left 641 | elif node.right is not None: 642 | node = node.right 643 | else: 644 | break 645 | key = node.key 646 | value = node.value 647 | self.remove(key) 648 | return key, value 649 | 650 | popitem = pop_item # for compatibility to dict() 651 | 652 | def foreach(self, func, order=0): 653 | """Visit all tree nodes and process key, value. 654 | 655 | parm func: function(key, value) 656 | param int order: inorder = 0, preorder = -1, postorder = +1 657 | """ 658 | if self.count == 0: 659 | return 660 | 661 | def _traverse(node): 662 | if order == -1: 663 | func(node.key, node.value) 664 | if node.left is not None: 665 | _traverse(node.left) 666 | if order == 0: 667 | func(node.key, node.value) 668 | if node.right is not None: 669 | _traverse(node.right) 670 | if order == +1: 671 | func(node.key, node.value) 672 | 673 | _traverse(self._root) 674 | 675 | def min_item(self): 676 | """Get item with min key of tree, raises ValueError if tree is empty.""" 677 | if self.is_empty(): 678 | raise ValueError("Tree is empty") 679 | node = self._root 680 | while node.left is not None: 681 | node = node.left 682 | return node.key, node.value 683 | 684 | def max_item(self): 685 | """Get item with max key of tree, raises ValueError if tree is empty.""" 686 | if self.is_empty(): 687 | raise ValueError("Tree is empty") 688 | node = self._root 689 | while node.right is not None: 690 | node = node.right 691 | return node.key, node.value 692 | 693 | def succ_item(self, key): 694 | """Get successor (k,v) pair of key, raises KeyError if key is max key 695 | or key does not exist. optimized for pypy. 696 | """ 697 | # removed graingets version, because it was little slower on CPython and much slower on pypy 698 | # this version runs about 4x faster with pypy than the Cython version 699 | # Note: Code sharing of succ_item() and ceiling_item() is possible, but has always a speed penalty. 700 | node = self._root 701 | succ_node = None 702 | while node is not None: 703 | if key == node.key: 704 | break 705 | elif key < node.key: 706 | if (succ_node is None) or (node.key < succ_node.key): 707 | succ_node = node 708 | node = node.left 709 | else: 710 | node = node.right 711 | 712 | if node is None: # stay at dead end 713 | raise KeyError(str(key)) 714 | # found node of key 715 | if node.right is not None: 716 | # find smallest node of right subtree 717 | node = node.right 718 | while node.left is not None: 719 | node = node.left 720 | if succ_node is None: 721 | succ_node = node 722 | elif node.key < succ_node.key: 723 | succ_node = node 724 | elif succ_node is None: # given key is biggest in tree 725 | raise KeyError(str(key)) 726 | return succ_node.key, succ_node.value 727 | 728 | def prev_item(self, key): 729 | """Get predecessor (k,v) pair of key, raises KeyError if key is min key 730 | or key does not exist. optimized for pypy. 731 | """ 732 | # removed graingets version, because it was little slower on CPython and much slower on pypy 733 | # this version runs about 4x faster with pypy than the Cython version 734 | # Note: Code sharing of prev_item() and floor_item() is possible, but has always a speed penalty. 735 | node = self._root 736 | prev_node = None 737 | 738 | while node is not None: 739 | if key == node.key: 740 | break 741 | elif key < node.key: 742 | node = node.left 743 | else: 744 | if (prev_node is None) or (node.key > prev_node.key): 745 | prev_node = node 746 | node = node.right 747 | 748 | if node is None: # stay at dead end (None) 749 | raise KeyError(str(key)) 750 | # found node of key 751 | if node.left is not None: 752 | # find biggest node of left subtree 753 | node = node.left 754 | while node.right is not None: 755 | node = node.right 756 | if prev_node is None: 757 | prev_node = node 758 | elif node.key > prev_node.key: 759 | prev_node = node 760 | elif prev_node is None: # given key is smallest in tree 761 | raise KeyError(str(key)) 762 | return prev_node.key, prev_node.value 763 | 764 | def floor_item(self, key): 765 | """Get the element (k,v) pair associated with the greatest key less 766 | than or equal to the given key, raises KeyError if there is no such key. 767 | """ 768 | # Note: Code sharing of prev_item() and floor_item() is possible, but has always a speed penalty. 769 | node = self._root 770 | prev_node = None 771 | while node is not None: 772 | if key == node.key: 773 | return node.key, node.value 774 | elif key < node.key: 775 | node = node.left 776 | else: 777 | if (prev_node is None) or (node.key > prev_node.key): 778 | prev_node = node 779 | node = node.right 780 | # node must be None here 781 | if prev_node: 782 | return prev_node.key, prev_node.value 783 | raise KeyError(str(key)) 784 | 785 | def ceiling_item(self, key): 786 | """Get the element (k,v) pair associated with the smallest key greater 787 | than or equal to the given key, raises KeyError if there is no such key. 788 | """ 789 | # Note: Code sharing of succ_item() and ceiling_item() is possible, but has always a speed penalty. 790 | node = self._root 791 | succ_node = None 792 | while node is not None: 793 | if key == node.key: 794 | return node.key, node.value 795 | elif key > node.key: 796 | node = node.right 797 | else: 798 | if (succ_node is None) or (node.key < succ_node.key): 799 | succ_node = node 800 | node = node.left 801 | # node must be None here 802 | if succ_node: 803 | return succ_node.key, succ_node.value 804 | raise KeyError(str(key)) 805 | 806 | def iter_items(self, start_key=None, end_key=None, reverse=False): 807 | """Iterates over the (key, value) items of the associated tree, 808 | in ascending order if reverse is True, iterate in descending order, 809 | reverse defaults to False""" 810 | # optimized iterator (reduced method calls) - faster on CPython but slower on pypy 811 | 812 | if self.is_empty(): 813 | return [] 814 | if reverse: 815 | return self._iter_items_backward(start_key, end_key) 816 | else: 817 | return self._iter_items_forward(start_key, end_key) 818 | 819 | def _iter_items_forward(self, start_key=None, end_key=None): 820 | for item in self._iter_items(left=attrgetter("left"), right=attrgetter("right"), 821 | start_key=start_key, end_key=end_key): 822 | yield item 823 | 824 | def _iter_items_backward(self, start_key=None, end_key=None): 825 | for item in self._iter_items(left=attrgetter("right"), right=attrgetter("left"), 826 | start_key=start_key, end_key=end_key): 827 | yield item 828 | 829 | def _iter_items(self, left=attrgetter("left"), right=attrgetter("right"), start_key=None, end_key=None): 830 | node = self._root 831 | stack = [] 832 | go_left = True 833 | in_range = self._get_in_range_func(start_key, end_key) 834 | 835 | while True: 836 | if left(node) is not None and go_left: 837 | stack.append(node) 838 | node = left(node) 839 | else: 840 | if in_range(node.key): 841 | yield node.key, node.value 842 | if right(node) is not None: 843 | node = right(node) 844 | go_left = True 845 | else: 846 | if not len(stack): 847 | return # all done 848 | node = stack.pop() 849 | go_left = False 850 | 851 | def _get_in_range_func(self, start_key, end_key): 852 | if start_key is None and end_key is None: 853 | return lambda x: True 854 | else: 855 | if start_key is None: 856 | start_key = self.min_key() 857 | if end_key is None: 858 | return lambda x: x >= start_key 859 | else: 860 | return lambda x: start_key <= x < end_key 861 | 862 | 863 | class PYPY_ABCTree(CPYTHON_ABCTree): 864 | def iter_items(self, start_key=None, end_key=None, reverse=False): 865 | """Iterates over the (key, value) items of the associated tree, 866 | in ascending order if reverse is True, iterate in descending order, 867 | reverse defaults to False""" 868 | # optimized for pypy, but slower on CPython 869 | if self.is_empty(): 870 | return 871 | direction = 1 if reverse else 0 872 | other = 1 - direction 873 | go_down = True 874 | stack = [] 875 | node = self._root 876 | in_range = self._get_in_range_func(start_key, end_key) 877 | 878 | while True: 879 | if node[direction] is not None and go_down: 880 | stack.append(node) 881 | node = node[direction] 882 | else: 883 | if in_range(node.key): 884 | yield node.key, node.value 885 | if node[other] is not None: 886 | node = node[other] 887 | go_down = True 888 | else: 889 | if not len(stack): 890 | return # all done 891 | node = stack.pop() 892 | go_down = False 893 | 894 | 895 | if PYPY: 896 | ABCTree = PYPY_ABCTree 897 | else: 898 | ABCTree = CPYTHON_ABCTree 899 | -------------------------------------------------------------------------------- /bintrees/avltree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding:utf-8 3 | # Author: mozman (python version) 4 | # Purpose: avl tree module (Julienne Walker's unbounded none recursive algorithm) 5 | # source: http://eternallyconfuzzled.com/tuts/datastructures/jsw_tut_avl.aspx 6 | # Created: 01.05.2010 7 | # Copyright (c) 2010-2013 by Manfred Moitzi 8 | # License: MIT License 9 | 10 | # Conclusion of Julienne Walker 11 | 12 | # AVL trees are about as close to optimal as balanced binary search trees can 13 | # get without eating up resources. You can rest assured that the O(log N) 14 | # performance of binary search trees is guaranteed with AVL trees, but the extra 15 | # bookkeeping required to maintain an AVL tree can be prohibitive, especially 16 | # if deletions are common. Insertion into an AVL tree only requires one single 17 | # or double rotation, but deletion could perform up to O(log N) rotations, as 18 | # in the example of a worst case AVL (ie. Fibonacci) tree. However, those cases 19 | # are rare, and still very fast. 20 | 21 | # AVL trees are best used when degenerate sequences are common, and there is 22 | # little or no locality of reference in nodes. That basically means that 23 | # searches are fairly random. If degenerate sequences are not common, but still 24 | # possible, and searches are random then a less rigid balanced tree such as red 25 | # black trees or Andersson trees are a better solution. If there is a significant 26 | # amount of locality to searches, such as a small cluster of commonly searched 27 | # items, a splay tree is theoretically better than all of the balanced trees 28 | # because of its move-to-front design. 29 | 30 | from __future__ import absolute_import 31 | 32 | from .abctree import ABCTree 33 | from array import array 34 | 35 | __all__ = ['AVLTree'] 36 | 37 | MAXSTACK = 32 38 | 39 | 40 | class Node(object): 41 | """Internal object, represents a tree node.""" 42 | __slots__ = ['left', 'right', 'balance', 'key', 'value'] 43 | 44 | def __init__(self, key=None, value=None): 45 | self.left = None 46 | self.right = None 47 | self.key = key 48 | self.value = value 49 | self.balance = 0 50 | 51 | def __getitem__(self, key): 52 | """N.__getitem__(key) <==> x[key], where key is 0 (left) or 1 (right).""" 53 | return self.left if key == 0 else self.right 54 | 55 | def __setitem__(self, key, value): 56 | """N.__setitem__(key, value) <==> x[key]=value, where key is 0 (left) or 1 (right).""" 57 | if key == 0: 58 | self.left = value 59 | else: 60 | self.right = value 61 | 62 | def free(self): 63 | """Remove all references.""" 64 | self.left = None 65 | self.right = None 66 | self.key = None 67 | self.value = None 68 | 69 | 70 | def height(node): 71 | return node.balance if node is not None else -1 72 | 73 | 74 | def jsw_single(root, direction): 75 | other_side = 1 - direction 76 | save = root[other_side] 77 | root[other_side] = save[direction] 78 | save[direction] = root 79 | rlh = height(root.left) 80 | rrh = height(root.right) 81 | slh = height(save[other_side]) 82 | root.balance = max(rlh, rrh) + 1 83 | save.balance = max(slh, root.balance) + 1 84 | return save 85 | 86 | 87 | def jsw_double(root, direction): 88 | other_side = 1 - direction 89 | root[other_side] = jsw_single(root[other_side], other_side) 90 | return jsw_single(root, direction) 91 | 92 | 93 | class AVLTree(ABCTree): 94 | """ 95 | AVLTree implements a balanced binary tree with a dict-like interface. 96 | 97 | see: http://en.wikipedia.org/wiki/AVL_tree 98 | 99 | In computer science, an AVL tree is a self-balancing binary search tree, and 100 | it is the first such data structure to be invented. In an AVL tree, the 101 | heights of the two child subtrees of any node differ by at most one; 102 | therefore, it is also said to be height-balanced. Lookup, insertion, and 103 | deletion all take O(log n) time in both the average and worst cases, where n 104 | is the number of nodes in the tree prior to the operation. Insertions and 105 | deletions may require the tree to be rebalanced by one or more tree rotations. 106 | 107 | The AVL tree is named after its two inventors, G.M. Adelson-Velskii and E.M. 108 | Landis, who published it in their 1962 paper "An algorithm for the 109 | organization of information." 110 | 111 | AVLTree() -> new empty tree. 112 | AVLTree(mapping) -> new tree initialized from a mapping 113 | AVLTree(seq) -> new tree initialized from seq [(k1, v1), (k2, v2), ... (kn, vn)] 114 | 115 | see also abctree.ABCTree() class. 116 | """ 117 | 118 | def _new_node(self, key, value): 119 | """Create a new tree node.""" 120 | self._count += 1 121 | return Node(key, value) 122 | 123 | def insert(self, key, value): 124 | """T.insert(key, value) <==> T[key] = value, insert key, value into tree.""" 125 | if self._root is None: 126 | self._root = self._new_node(key, value) 127 | else: 128 | node_stack = [] # node stack 129 | dir_stack = array('I') # direction stack 130 | done = False 131 | top = 0 132 | node = self._root 133 | # search for an empty link, save path 134 | while True: 135 | if key == node.key: # update existing item 136 | node.value = value 137 | return 138 | direction = 1 if key > node.key else 0 139 | dir_stack.append(direction) 140 | node_stack.append(node) 141 | if node[direction] is None: 142 | break 143 | node = node[direction] 144 | 145 | # Insert a new node at the bottom of the tree 146 | node[direction] = self._new_node(key, value) 147 | 148 | # Walk back up the search path 149 | top = len(node_stack) - 1 150 | while (top >= 0) and not done: 151 | direction = dir_stack[top] 152 | other_side = 1 - direction 153 | top_node = node_stack[top] 154 | left_height = height(top_node[direction]) 155 | right_height = height(top_node[other_side]) 156 | 157 | # Terminate or rebalance as necessary */ 158 | if left_height - right_height == 0: 159 | done = True 160 | if left_height - right_height >= 2: 161 | a = top_node[direction][direction] 162 | b = top_node[direction][other_side] 163 | 164 | if height(a) >= height(b): 165 | node_stack[top] = jsw_single(top_node, other_side) 166 | else: 167 | node_stack[top] = jsw_double(top_node, other_side) 168 | 169 | # Fix parent 170 | if top != 0: 171 | node_stack[top - 1][dir_stack[top - 1]] = node_stack[top] 172 | else: 173 | self._root = node_stack[0] 174 | done = True 175 | 176 | # Update balance factors 177 | top_node = node_stack[top] 178 | left_height = height(top_node[direction]) 179 | right_height = height(top_node[other_side]) 180 | 181 | top_node.balance = max(left_height, right_height) + 1 182 | top -= 1 183 | 184 | def remove(self, key): 185 | """T.remove(key) <==> del T[key], remove item from tree.""" 186 | if self._root is None: 187 | raise KeyError(str(key)) 188 | else: 189 | node_stack = [None] * MAXSTACK # node stack 190 | dir_stack = array('I', [0] * MAXSTACK) # direction stack 191 | top = 0 192 | node = self._root 193 | 194 | while True: 195 | # Terminate if not found 196 | if node is None: 197 | raise KeyError(str(key)) 198 | elif node.key == key: 199 | break 200 | 201 | # Push direction and node onto stack 202 | direction = 1 if key > node.key else 0 203 | dir_stack[top] = direction 204 | 205 | node_stack[top] = node 206 | node = node[direction] 207 | top += 1 208 | 209 | # Remove the node 210 | if (node.left is None) or (node.right is None): 211 | # Which child is not null? 212 | direction = 1 if node.left is None else 0 213 | 214 | # Fix parent 215 | if top != 0: 216 | node_stack[top - 1][dir_stack[top - 1]] = node[direction] 217 | else: 218 | self._root = node[direction] 219 | node.free() 220 | self._count -= 1 221 | else: 222 | # Find the inorder successor 223 | heir = node.right 224 | 225 | # Save the path 226 | dir_stack[top] = 1 227 | node_stack[top] = node 228 | top += 1 229 | 230 | while heir.left is not None: 231 | dir_stack[top] = 0 232 | node_stack[top] = heir 233 | top += 1 234 | heir = heir.left 235 | 236 | # Swap data 237 | node.key = heir.key 238 | node.value = heir.value 239 | 240 | # Unlink successor and fix parent 241 | xdir = 1 if node_stack[top - 1].key == node.key else 0 242 | node_stack[top - 1][xdir] = heir.right 243 | heir.free() 244 | self._count -= 1 245 | 246 | # Walk back up the search path 247 | top -= 1 248 | while top >= 0: 249 | direction = dir_stack[top] 250 | other_side = 1 - direction 251 | top_node = node_stack[top] 252 | left_height = height(top_node[direction]) 253 | right_height = height(top_node[other_side]) 254 | b_max = max(left_height, right_height) 255 | 256 | # Update balance factors 257 | top_node.balance = b_max + 1 258 | 259 | # Terminate or rebalance as necessary 260 | if (left_height - right_height) == -1: 261 | break 262 | if (left_height - right_height) <= -2: 263 | a = top_node[other_side][direction] 264 | b = top_node[other_side][other_side] 265 | if height(a) <= height(b): 266 | node_stack[top] = jsw_single(top_node, direction) 267 | else: 268 | node_stack[top] = jsw_double(top_node, direction) 269 | # Fix parent 270 | if top != 0: 271 | node_stack[top - 1][dir_stack[top - 1]] = node_stack[top] 272 | else: 273 | self._root = node_stack[0] 274 | top -= 1 275 | -------------------------------------------------------------------------------- /bintrees/bintree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Purpose: binary tree module 5 | # Created: 28.04.2010 6 | # Copyright (c) 2010-2013 by Manfred Moitzi 7 | # License: MIT License 8 | 9 | from __future__ import absolute_import 10 | 11 | from .abctree import ABCTree 12 | 13 | __all__ = ['BinaryTree'] 14 | 15 | 16 | class Node(object): 17 | """Internal object, represents a tree node.""" 18 | __slots__ = ['key', 'value', 'left', 'right'] 19 | 20 | def __init__(self, key, value): 21 | self.key = key 22 | self.value = value 23 | self.left = None 24 | self.right = None 25 | 26 | def __getitem__(self, key): 27 | """N.__getitem__(key) <==> x[key], where key is 0 (left) or 1 (right).""" 28 | return self.left if key == 0 else self.right 29 | 30 | def __setitem__(self, key, value): 31 | """N.__setitem__(key, value) <==> x[key]=value, where key is 0 (left) or 1 (right).""" 32 | if key == 0: 33 | self.left = value 34 | else: 35 | self.right = value 36 | 37 | def free(self): 38 | """Set references to None.""" 39 | self.left = None 40 | self.right = None 41 | self.value = None 42 | self.key = None 43 | 44 | 45 | class BinaryTree(ABCTree): 46 | """ 47 | BinaryTree implements an unbalanced binary tree with a dict-like interface. 48 | 49 | see: http://en.wikipedia.org/wiki/Binary_tree 50 | 51 | A binary tree is a tree data structure in which each node has at most two 52 | children. 53 | 54 | BinaryTree() -> new empty tree. 55 | BinaryTree(mapping,) -> new tree initialized from a mapping 56 | BinaryTree(seq) -> new tree initialized from seq [(k1, v1), (k2, v2), ... (kn, vn)] 57 | 58 | see also abctree.ABCTree() class. 59 | """ 60 | def _new_node(self, key, value): 61 | """Create a new tree node.""" 62 | self._count += 1 63 | return Node(key, value) 64 | 65 | def insert(self, key, value): 66 | """T.insert(key, value) <==> T[key] = value, insert key, value into tree.""" 67 | if self._root is None: 68 | self._root = self._new_node(key, value) 69 | else: 70 | parent = None 71 | direction = 0 72 | node = self._root 73 | while True: 74 | if node is None: 75 | parent[direction] = self._new_node(key, value) 76 | break 77 | if key == node.key: 78 | node.value = value # replace value 79 | break 80 | else: 81 | parent = node 82 | direction = 0 if key <= node.key else 1 83 | node = node[direction] 84 | 85 | def remove(self, key): 86 | """T.remove(key) <==> del T[key], remove item from tree.""" 87 | node = self._root 88 | if node is None: 89 | raise KeyError(str(key)) 90 | else: 91 | parent = None 92 | direction = 0 93 | while True: 94 | if key == node.key: 95 | # remove node 96 | if (node.left is not None) and (node.right is not None): 97 | # find replacment node: smallest key in right-subtree 98 | parent = node 99 | direction = 1 100 | replacement = node.right 101 | while replacement.left is not None: 102 | parent = replacement 103 | direction = 0 104 | replacement = replacement.left 105 | parent[direction] = replacement.right 106 | #swap places 107 | node.key = replacement.key 108 | node.value = replacement.value 109 | node = replacement # delete replacement! 110 | else: 111 | down_dir = 1 if node.left is None else 0 112 | if parent is None: # root 113 | self._root = node[down_dir] 114 | else: 115 | parent[direction] = node[down_dir] 116 | node.free() 117 | self._count -= 1 118 | break 119 | else: 120 | direction = 0 if key < node.key else 1 121 | parent = node 122 | node = node[direction] 123 | if node is None: 124 | raise KeyError(str(key)) 125 | 126 | -------------------------------------------------------------------------------- /bintrees/ctrees.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ctrees.c 3 | * 4 | * Author: mozman 5 | * Copyright (c) 2010-2013 by Manfred Moitzi 6 | * License: MIT-License 7 | */ 8 | 9 | #include "ctrees.h" 10 | #include 11 | 12 | #define LEFT 0 13 | #define RIGHT 1 14 | #define KEY(node) (node->key) 15 | #define VALUE(node) (node->value) 16 | #define LEFT_NODE(node) (node->link[LEFT]) 17 | #define RIGHT_NODE(node) (node->link[RIGHT]) 18 | #define LINK(node, dir) (node->link[dir]) 19 | #define XDATA(node) (node->xdata) 20 | #define RED(node) (node->xdata) 21 | #define BALANCE(node) (node->xdata) 22 | 23 | static node_t * 24 | ct_new_node(PyObject *key, PyObject *value, int xdata) 25 | { 26 | PyGILState_STATE state = PyGILState_Ensure(); 27 | node_t *new_node = PyMem_Malloc(sizeof(node_t)); 28 | PyGILState_Release(state); 29 | if (new_node != NULL) { 30 | KEY(new_node) = key; 31 | Py_INCREF(key); 32 | VALUE(new_node) = value; 33 | Py_INCREF(value); 34 | LEFT_NODE(new_node) = NULL; 35 | RIGHT_NODE(new_node) = NULL; 36 | XDATA(new_node) = xdata; 37 | } 38 | return new_node; 39 | } 40 | 41 | static void 42 | ct_delete_node(node_t *node) 43 | { 44 | PyGILState_STATE state; 45 | if (node != NULL) { 46 | Py_XDECREF(KEY(node)); 47 | Py_XDECREF(VALUE(node)); 48 | LEFT_NODE(node) = NULL; 49 | RIGHT_NODE(node) = NULL; 50 | state = PyGILState_Ensure(); 51 | PyMem_Free(node); 52 | PyGILState_Release(state); 53 | } 54 | } 55 | 56 | extern void 57 | ct_delete_tree(node_t *root) 58 | { 59 | if (root == NULL) 60 | return; 61 | if (LEFT_NODE(root) != NULL) { 62 | ct_delete_tree(LEFT_NODE(root)); 63 | } 64 | if (RIGHT_NODE(root) != NULL) { 65 | ct_delete_tree(RIGHT_NODE(root)); 66 | } 67 | ct_delete_node(root); 68 | } 69 | 70 | static void 71 | ct_swap_data(node_t *node1, node_t *node2) 72 | { 73 | PyObject *tmp; 74 | tmp = KEY(node1); 75 | KEY(node1) = KEY(node2); 76 | KEY(node2) = tmp; 77 | tmp = VALUE(node1); 78 | VALUE(node1) = VALUE(node2); 79 | VALUE(node2) = tmp; 80 | } 81 | 82 | int 83 | ct_compare(PyObject *key1, PyObject *key2) 84 | { 85 | int res; 86 | 87 | res = PyObject_RichCompareBool(key1, key2, Py_LT); 88 | if (res > 0) 89 | return -1; 90 | else if (res < 0) { 91 | PyErr_SetString(PyExc_TypeError, "invalid type for key"); 92 | return 0; 93 | } 94 | /* second compare: 95 | +1 if key1 > key2 96 | 0 if not -> equal 97 | -1 means error, if error, it should happen at the first compare 98 | */ 99 | return PyObject_RichCompareBool(key1, key2, Py_GT); 100 | } 101 | 102 | extern node_t * 103 | ct_find_node(node_t *root, PyObject *key) 104 | { 105 | int res; 106 | while (root != NULL) { 107 | res = ct_compare(key, KEY(root)); 108 | if (res == 0) /* key found */ 109 | return root; 110 | else { 111 | root = LINK(root, (res > 0)); 112 | } 113 | } 114 | return NULL; /* key not found */ 115 | } 116 | 117 | extern node_t* 118 | ct_get_leaf_node(node_t *node) 119 | { 120 | if (node == NULL) 121 | return NULL; 122 | for(;;) { 123 | if (LEFT_NODE(node) != NULL) 124 | node = LEFT_NODE(node); 125 | else if (RIGHT_NODE(node) != NULL) 126 | node = RIGHT_NODE(node); 127 | else return node; 128 | } 129 | } 130 | 131 | extern PyObject * 132 | ct_get_item(node_t *root, PyObject *key) 133 | { 134 | node_t *node; 135 | PyObject *tuple; 136 | 137 | node = ct_find_node(root, key); 138 | if (node != NULL) { 139 | tuple = PyTuple_New(2); 140 | PyTuple_SET_ITEM(tuple, 0, KEY(node)); 141 | PyTuple_SET_ITEM(tuple, 1, VALUE(node)); 142 | return tuple; 143 | } 144 | Py_RETURN_NONE; 145 | } 146 | 147 | extern node_t * 148 | ct_max_node(node_t *root) 149 | /* get node with largest key */ 150 | { 151 | if (root == NULL) 152 | return NULL; 153 | while (RIGHT_NODE(root) != NULL) 154 | root = RIGHT_NODE(root); 155 | return root; 156 | } 157 | 158 | extern node_t * 159 | ct_min_node(node_t *root) 160 | /* get node with smallest key */ 161 | { 162 | if (root == NULL) 163 | return NULL; 164 | while (LEFT_NODE(root) != NULL) 165 | root = LEFT_NODE(root); 166 | return root; 167 | } 168 | 169 | extern int 170 | ct_bintree_remove(node_t **rootaddr, PyObject *key) 171 | /* attention: rootaddr is the address of the root pointer */ 172 | { 173 | node_t *node, *parent, *replacement; 174 | int direction, cmp_res, down_dir; 175 | 176 | node = *rootaddr; 177 | 178 | if (node == NULL) 179 | return 0; /* root is NULL */ 180 | parent = NULL; 181 | direction = 0; 182 | 183 | while (1) { 184 | cmp_res = ct_compare(key, KEY(node)); 185 | if (cmp_res == 0) /* key found, remove node */ 186 | { 187 | if ((LEFT_NODE(node) != NULL) && (RIGHT_NODE(node) != NULL)) { 188 | /* find replacement node: smallest key in right-subtree */ 189 | parent = node; 190 | direction = RIGHT; 191 | replacement = RIGHT_NODE(node); 192 | while (LEFT_NODE(replacement) != NULL) { 193 | parent = replacement; 194 | direction = LEFT; 195 | replacement = LEFT_NODE(replacement); 196 | } 197 | LINK(parent, direction) = RIGHT_NODE(replacement); 198 | /* swap places */ 199 | ct_swap_data(node, replacement); 200 | node = replacement; /* delete replacement node */ 201 | } 202 | else { 203 | down_dir = (LEFT_NODE(node) == NULL) ? RIGHT : LEFT; 204 | if (parent == NULL) /* root */ 205 | { 206 | *rootaddr = LINK(node, down_dir); 207 | } 208 | else { 209 | LINK(parent, direction) = LINK(node, down_dir); 210 | } 211 | } 212 | ct_delete_node(node); 213 | return 1; /* remove was success full */ 214 | } 215 | else { 216 | direction = (cmp_res < 0) ? LEFT : RIGHT; 217 | parent = node; 218 | node = LINK(node, direction); 219 | if (node == NULL) 220 | return 0; /* error key not found */ 221 | } 222 | } 223 | } 224 | 225 | extern int 226 | ct_bintree_insert(node_t **rootaddr, PyObject *key, PyObject *value) 227 | /* attention: rootaddr is the address of the root pointer */ 228 | { 229 | node_t *parent, *node; 230 | int direction, cval; 231 | node = *rootaddr; 232 | if (node == NULL) { 233 | node = ct_new_node(key, value, 0); /* new node is also the root */ 234 | if (node == NULL) 235 | return -1; /* got no memory */ 236 | *rootaddr = node; 237 | } 238 | else { 239 | direction = LEFT; 240 | parent = NULL; 241 | while (1) { 242 | if (node == NULL) { 243 | node = ct_new_node(key, value, 0); 244 | if (node == NULL) 245 | return -1; /* get no memory */ 246 | LINK(parent, direction) = node; 247 | return 1; 248 | } 249 | cval = ct_compare(key, KEY(node)); 250 | if (cval == 0) { 251 | /* key exists, replace value object */ 252 | Py_XDECREF(VALUE(node)); /* release old value object */ 253 | VALUE(node) = value; /* set new value object */ 254 | Py_INCREF(value); /* take new value object */ 255 | return 0; 256 | } 257 | else { 258 | parent = node; 259 | direction = (cval < 0) ? LEFT : RIGHT; 260 | node = LINK(node, direction); 261 | } 262 | } 263 | } 264 | return 1; 265 | } 266 | 267 | static int 268 | is_red (node_t *node) 269 | { 270 | return (node != NULL) && (RED(node) == 1); 271 | } 272 | 273 | #define rb_new_node(key, value) ct_new_node(key, value, 1) 274 | 275 | static node_t * 276 | rb_single(node_t *root, int dir) 277 | { 278 | node_t *save = root->link[!dir]; 279 | 280 | root->link[!dir] = save->link[dir]; 281 | save->link[dir] = root; 282 | 283 | RED(root) = 1; 284 | RED(save) = 0; 285 | return save; 286 | } 287 | 288 | static node_t * 289 | rb_double(node_t *root, int dir) 290 | { 291 | root->link[!dir] = rb_single(root->link[!dir], !dir); 292 | return rb_single(root, dir); 293 | } 294 | 295 | #define rb_new_node(key, value) ct_new_node(key, value, 1) 296 | 297 | extern int 298 | rb_insert(node_t **rootaddr, PyObject *key, PyObject *value) 299 | { 300 | int new_node = 0; 301 | node_t *root = *rootaddr; 302 | 303 | if (root == NULL) { 304 | /* 305 | We have an empty tree; attach the 306 | new node directly to the root 307 | */ 308 | root = rb_new_node(key, value); 309 | new_node = 1; 310 | if (root == NULL) 311 | return -1; // got no memory 312 | } 313 | else { 314 | node_t head; /* False tree root */ 315 | node_t *g, *t; /* Grandparent & parent */ 316 | node_t *p, *q; /* Iterator & parent */ 317 | int dir = 0; 318 | int last = 0; 319 | 320 | /* Set up our helpers */ 321 | t = &head; 322 | g = NULL; 323 | p = NULL; 324 | RIGHT_NODE(t) = root; 325 | LEFT_NODE(t) = NULL; 326 | q = RIGHT_NODE(t); 327 | 328 | /* Search down the tree for a place to insert */ 329 | for (;;) { 330 | int cmp_res; 331 | if (q == NULL) { 332 | /* Insert a new node at the first null link */ 333 | q = rb_new_node(key, value); 334 | new_node = 1; 335 | p->link[dir] = q; 336 | if (q == NULL) 337 | return -1; /* get no memory */ 338 | } 339 | else if (is_red(q->link[0]) && is_red(q->link[1])) { 340 | /* Simple red violation: color flip */ 341 | RED(q) = 1; 342 | RED(q->link[0]) = 0; 343 | RED(q->link[1]) = 0; 344 | } 345 | 346 | if (is_red(q) && is_red(p)) { 347 | /* Hard red violation: rotations necessary */ 348 | int dir2 = (t->link[1] == g); 349 | 350 | if (q == p->link[last]) 351 | t->link[dir2] = rb_single(g, !last); 352 | else 353 | t->link[dir2] = rb_double(g, !last); 354 | } 355 | 356 | /* Stop working if we inserted a new node. */ 357 | if (new_node) 358 | break; 359 | 360 | cmp_res = ct_compare(KEY(q), key); 361 | if (cmp_res == 0) { /* if key exists */ 362 | Py_XDECREF(VALUE(q)); /* release old value object */ 363 | VALUE(q) = value; /* set new value object */ 364 | Py_INCREF(value); /* take new value object */ 365 | break; 366 | } 367 | last = dir; 368 | dir = (cmp_res < 0); 369 | 370 | /* Move the helpers down */ 371 | if (g != NULL) 372 | t = g; 373 | 374 | g = p; 375 | p = q; 376 | q = q->link[dir]; 377 | } 378 | /* Update the root (it may be different) */ 379 | root = head.link[1]; 380 | } 381 | 382 | /* Make the root black for simplified logic */ 383 | RED(root) = 0; 384 | (*rootaddr) = root; 385 | return new_node; 386 | } 387 | 388 | extern int 389 | rb_remove(node_t **rootaddr, PyObject *key) 390 | { 391 | node_t *root = *rootaddr; 392 | 393 | node_t head = { { NULL } }; /* False tree root */ 394 | node_t *q, *p, *g; /* Helpers */ 395 | node_t *f = NULL; /* Found item */ 396 | int dir = 1; 397 | 398 | if (root == NULL) 399 | return 0; 400 | 401 | /* Set up our helpers */ 402 | q = &head; 403 | g = p = NULL; 404 | RIGHT_NODE(q) = root; 405 | 406 | /* 407 | Search and push a red node down 408 | to fix red violations as we go 409 | */ 410 | while (q->link[dir] != NULL) { 411 | int last = dir; 412 | int cmp_res; 413 | 414 | /* Move the helpers down */ 415 | g = p, p = q; 416 | q = q->link[dir]; 417 | 418 | cmp_res = ct_compare(KEY(q), key); 419 | 420 | dir = cmp_res < 0; 421 | 422 | /* 423 | Save the node with matching data and keep 424 | going; we'll do removal tasks at the end 425 | */ 426 | if (cmp_res == 0) 427 | f = q; 428 | 429 | /* Push the red node down with rotations and color flips */ 430 | if (!is_red(q) && !is_red(q->link[dir])) { 431 | if (is_red(q->link[!dir])) 432 | p = p->link[last] = rb_single(q, dir); 433 | else if (!is_red(q->link[!dir])) { 434 | node_t *s = p->link[!last]; 435 | 436 | if (s != NULL) { 437 | if (!is_red(s->link[!last]) && 438 | !is_red(s->link[last])) { 439 | /* Color flip */ 440 | RED(p) = 0; 441 | RED(s) = 1; 442 | RED(q) = 1; 443 | } 444 | else { 445 | int dir2 = g->link[1] == p; 446 | 447 | if (is_red(s->link[last])) 448 | g->link[dir2] = rb_double(p, last); 449 | else if (is_red(s->link[!last])) 450 | g->link[dir2] = rb_single(p, last); 451 | 452 | /* Ensure correct coloring */ 453 | RED(q) = RED(g->link[dir2]) = 1; 454 | RED(g->link[dir2]->link[0]) = 0; 455 | RED(g->link[dir2]->link[1]) = 0; 456 | } 457 | } 458 | } 459 | } 460 | } 461 | 462 | /* Replace and remove the saved node */ 463 | if (f != NULL) { 464 | ct_swap_data(f, q); 465 | p->link[p->link[1] == q] = q->link[q->link[0] == NULL]; 466 | ct_delete_node(q); 467 | } 468 | 469 | /* Update the root (it may be different) */ 470 | root = head.link[1]; 471 | 472 | /* Make the root black for simplified logic */ 473 | if (root != NULL) 474 | RED(root) = 0; 475 | *rootaddr = root; 476 | return (f != NULL); 477 | } 478 | 479 | #define avl_new_node(key, value) ct_new_node(key, value, 0) 480 | #define height(p) ((p) == NULL ? -1 : (p)->xdata) 481 | #define avl_max(a, b) ((a) > (b) ? (a) : (b)) 482 | 483 | static node_t * 484 | avl_single(node_t *root, int dir) 485 | { 486 | node_t *save = root->link[!dir]; 487 | int rlh, rrh, slh; 488 | 489 | /* Rotate */ 490 | root->link[!dir] = save->link[dir]; 491 | save->link[dir] = root; 492 | 493 | /* Update balance factors */ 494 | rlh = height(root->link[0]); 495 | rrh = height(root->link[1]); 496 | slh = height(save->link[!dir]); 497 | 498 | BALANCE(root) = avl_max(rlh, rrh) + 1; 499 | BALANCE(save) = avl_max(slh, BALANCE(root)) + 1; 500 | 501 | return save; 502 | } 503 | 504 | static node_t * 505 | avl_double(node_t *root, int dir) 506 | { 507 | root->link[!dir] = avl_single(root->link[!dir], !dir); 508 | return avl_single(root, dir); 509 | } 510 | 511 | extern int 512 | avl_insert(node_t **rootaddr, PyObject *key, PyObject *value) 513 | { 514 | node_t *root = *rootaddr; 515 | 516 | if (root == NULL) { 517 | root = avl_new_node(key, value); 518 | if (root == NULL) 519 | return -1; /* got no memory */ 520 | } 521 | else { 522 | node_t *it, *up[32]; 523 | int upd[32], top = 0; 524 | int done = 0; 525 | int cmp_res; 526 | 527 | it = root; 528 | /* Search for an empty link, save the path */ 529 | for (;;) { 530 | /* Push direction and node onto stack */ 531 | cmp_res = ct_compare(KEY(it), key); 532 | if (cmp_res == 0) { 533 | Py_XDECREF(VALUE(it)); /* release old value object */ 534 | VALUE(it) = value; /* set new value object */ 535 | Py_INCREF(value); /* take new value object */ 536 | return 0; 537 | } 538 | /* upd[top] = it->data < data; */ 539 | upd[top] = (cmp_res < 0); 540 | up[top++] = it; 541 | 542 | if (it->link[upd[top - 1]] == NULL) 543 | break; 544 | it = it->link[upd[top - 1]]; 545 | } 546 | 547 | /* Insert a new node at the bottom of the tree */ 548 | it->link[upd[top - 1]] = avl_new_node(key, value); 549 | if (it->link[upd[top - 1]] == NULL) 550 | return -1; // got no memory 551 | 552 | /* Walk back up the search path */ 553 | while (--top >= 0 && !done) { 554 | // int dir = (cmp_res < 0); 555 | int lh, rh, max; 556 | 557 | cmp_res = ct_compare(KEY(up[top]), key); 558 | 559 | lh = height(up[top]->link[upd[top]]); 560 | rh = height(up[top]->link[!upd[top]]); 561 | 562 | /* Terminate or rebalance as necessary */ 563 | if (lh - rh == 0) 564 | done = 1; 565 | if (lh - rh >= 2) { 566 | node_t *a = up[top]->link[upd[top]]->link[upd[top]]; 567 | node_t *b = up[top]->link[upd[top]]->link[!upd[top]]; 568 | 569 | if (height( a ) >= height( b )) 570 | up[top] = avl_single(up[top], !upd[top]); 571 | else 572 | up[top] = avl_double(up[top], !upd[top]); 573 | 574 | /* Fix parent */ 575 | if (top != 0) 576 | up[top - 1]->link[upd[top - 1]] = up[top]; 577 | else 578 | root = up[0]; 579 | done = 1; 580 | } 581 | /* Update balance factors */ 582 | lh = height(up[top]->link[upd[top]]); 583 | rh = height(up[top]->link[!upd[top]]); 584 | max = avl_max(lh, rh); 585 | BALANCE(up[top]) = max + 1; 586 | } 587 | } 588 | (*rootaddr) = root; 589 | return 1; 590 | } 591 | 592 | extern int 593 | avl_remove(node_t **rootaddr, PyObject *key) 594 | { 595 | node_t *root = *rootaddr; 596 | int cmp_res; 597 | 598 | if (root != NULL) { 599 | node_t *it, *up[32]; 600 | int upd[32], top = 0; 601 | 602 | it = root; 603 | for (;;) { 604 | /* Terminate if not found */ 605 | if (it == NULL) 606 | return 0; 607 | cmp_res = ct_compare(KEY(it), key); 608 | if (cmp_res == 0) 609 | break; 610 | 611 | /* Push direction and node onto stack */ 612 | upd[top] = (cmp_res < 0); 613 | up[top++] = it; 614 | it = it->link[upd[top - 1]]; 615 | } 616 | 617 | /* Remove the node */ 618 | if (it->link[0] == NULL || 619 | it->link[1] == NULL) { 620 | /* Which child is not null? */ 621 | int dir = it->link[0] == NULL; 622 | 623 | /* Fix parent */ 624 | if (top != 0) 625 | up[top - 1]->link[upd[top - 1]] = it->link[dir]; 626 | else 627 | root = it->link[dir]; 628 | 629 | ct_delete_node(it); 630 | } 631 | else { 632 | /* Find the inorder successor */ 633 | node_t *heir = it->link[1]; 634 | 635 | /* Save the path */ 636 | upd[top] = 1; 637 | up[top++] = it; 638 | 639 | while ( heir->link[0] != NULL ) { 640 | upd[top] = 0; 641 | up[top++] = heir; 642 | heir = heir->link[0]; 643 | } 644 | /* Swap data */ 645 | ct_swap_data(it, heir); 646 | /* Unlink successor and fix parent */ 647 | up[top - 1]->link[up[top - 1] == it] = heir->link[1]; 648 | ct_delete_node(heir); 649 | } 650 | 651 | /* Walk back up the search path */ 652 | while (--top >= 0) { 653 | int lh = height(up[top]->link[upd[top]]); 654 | int rh = height(up[top]->link[!upd[top]]); 655 | int max = avl_max(lh, rh); 656 | 657 | /* Update balance factors */ 658 | BALANCE(up[top]) = max + 1; 659 | 660 | /* Terminate or rebalance as necessary */ 661 | if (lh - rh == -1) 662 | break; 663 | if (lh - rh <= -2) { 664 | node_t *a = up[top]->link[!upd[top]]->link[upd[top]]; 665 | node_t *b = up[top]->link[!upd[top]]->link[!upd[top]]; 666 | 667 | if (height(a) <= height(b)) 668 | up[top] = avl_single(up[top], upd[top]); 669 | else 670 | up[top] = avl_double(up[top], upd[top]); 671 | 672 | /* Fix parent */ 673 | if (top != 0) 674 | up[top - 1]->link[upd[top - 1]] = up[top]; 675 | else 676 | root = up[0]; 677 | } 678 | } 679 | } 680 | (*rootaddr) = root; 681 | return 1; 682 | } 683 | 684 | extern node_t * 685 | ct_succ_node(node_t *root, PyObject *key) 686 | { 687 | node_t *succ = NULL; 688 | node_t *node = root; 689 | int cval; 690 | 691 | while (node != NULL) { 692 | cval = ct_compare(key, KEY(node)); 693 | if (cval == 0) 694 | break; 695 | else if (cval < 0) { 696 | if ((succ == NULL) || 697 | (ct_compare(KEY(node), KEY(succ)) < 0)) 698 | succ = node; 699 | node = LEFT_NODE(node); 700 | } else 701 | node = RIGHT_NODE(node); 702 | } 703 | if (node == NULL) 704 | return NULL; 705 | /* found node of key */ 706 | if (RIGHT_NODE(node) != NULL) { 707 | /* find smallest node of right subtree */ 708 | node = RIGHT_NODE(node); 709 | while (LEFT_NODE(node) != NULL) 710 | node = LEFT_NODE(node); 711 | if (succ == NULL) 712 | succ = node; 713 | else if (ct_compare(KEY(node), KEY(succ)) < 0) 714 | succ = node; 715 | } 716 | return succ; 717 | } 718 | 719 | extern node_t * 720 | ct_prev_node(node_t *root, PyObject *key) 721 | { 722 | node_t *prev = NULL; 723 | node_t *node = root; 724 | int cval; 725 | 726 | while (node != NULL) { 727 | cval = ct_compare(key, KEY(node)); 728 | if (cval == 0) 729 | break; 730 | else if (cval < 0) 731 | node = LEFT_NODE(node); 732 | else { 733 | if ((prev == NULL) || (ct_compare(KEY(node), KEY(prev)) > 0)) 734 | prev = node; 735 | node = RIGHT_NODE(node); 736 | } 737 | } 738 | if (node == NULL) /* stay at dead end (None) */ 739 | return NULL; 740 | /* found node of key */ 741 | if (LEFT_NODE(node) != NULL) { 742 | /* find biggest node of left subtree */ 743 | node = LEFT_NODE(node); 744 | while (RIGHT_NODE(node) != NULL) 745 | node = RIGHT_NODE(node); 746 | if (prev == NULL) 747 | prev = node; 748 | else if (ct_compare(KEY(node), KEY(prev)) > 0) 749 | prev = node; 750 | } 751 | return prev; 752 | } 753 | 754 | extern node_t * 755 | ct_floor_node(node_t *root, PyObject *key) 756 | { 757 | node_t *prev = NULL; 758 | node_t *node = root; 759 | int cval; 760 | 761 | while (node != NULL) { 762 | cval = ct_compare(key, KEY(node)); 763 | if (cval == 0) 764 | return node; 765 | else if (cval < 0) 766 | node = LEFT_NODE(node); 767 | else { 768 | if ((prev == NULL) || (ct_compare(KEY(node), KEY(prev)) > 0)) 769 | prev = node; 770 | node = RIGHT_NODE(node); 771 | } 772 | } 773 | return prev; 774 | } 775 | 776 | extern node_t * 777 | ct_ceiling_node(node_t *root, PyObject *key) 778 | { 779 | node_t *succ = NULL; 780 | node_t *node = root; 781 | int cval; 782 | 783 | while (node != NULL) { 784 | cval = ct_compare(key, KEY(node)); 785 | if (cval == 0) 786 | return node; 787 | else if (cval < 0) { 788 | if ((succ == NULL) || 789 | (ct_compare(KEY(node), KEY(succ)) < 0)) 790 | succ = node; 791 | node = LEFT_NODE(node); 792 | } else 793 | node = RIGHT_NODE(node); 794 | } 795 | return succ; 796 | } 797 | -------------------------------------------------------------------------------- /bintrees/ctrees.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ctrees.h 3 | * 4 | * Author: mozman 5 | * Copyright (c) 2010-2013 by Manfred Moitzi 6 | * License: MIT-License 7 | */ 8 | 9 | #ifndef __CTREES_H 10 | #define __CTREES_H 11 | 12 | #include 13 | 14 | typedef struct tree_node node_t; 15 | 16 | struct tree_node { 17 | node_t *link[2]; 18 | PyObject *key; 19 | PyObject *value; 20 | int xdata; 21 | }; 22 | 23 | typedef node_t* nodeptr; 24 | 25 | /* common binary tree functions */ 26 | void ct_delete_tree(node_t *root); 27 | int ct_compare(PyObject *key1, PyObject *key2); 28 | PyObject *ct_get_item(node_t *root, PyObject *key); 29 | node_t *ct_find_node(node_t *root, PyObject *key); 30 | node_t *ct_get_leaf_node(node_t *node); 31 | node_t *ct_succ_node(node_t *root, PyObject *key); 32 | node_t *ct_prev_node(node_t *root, PyObject *key); 33 | node_t *ct_max_node(node_t *root); 34 | node_t *ct_min_node(node_t *root); 35 | node_t *ct_floor_node(node_t *root, PyObject *key); 36 | node_t *ct_ceiling_node(node_t *root, PyObject *key); 37 | 38 | /* unbalanced binary tree */ 39 | int ct_bintree_insert(node_t **root, PyObject *key, PyObject *value); 40 | int ct_bintree_remove(node_t **root, PyObject *key); 41 | 42 | /* avl-tree functions */ 43 | int avl_insert(node_t **root, PyObject *key, PyObject *value); 44 | int avl_remove(node_t **root, PyObject *key); 45 | 46 | /* rb-tree functions */ 47 | int rb_insert(node_t **root, PyObject *key, PyObject *value); 48 | int rb_remove(node_t **root, PyObject *key); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /bintrees/ctrees.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Created: 08.05.2010 5 | # Copyright (c) 2010-2013 by Manfred Moitzi 6 | # License: MIT License 7 | 8 | cdef extern from "ctrees.h": 9 | ctypedef struct PyObject: 10 | pass 11 | 12 | ctypedef struct node_t: 13 | node_t *link[2] 14 | PyObject *key 15 | PyObject *value 16 | 17 | int ct_compare(object key1, object key2) 18 | void ct_delete_tree(node_t *root) 19 | node_t *ct_find_node(node_t *root, object key) 20 | node_t *ct_get_leaf_node(node_t *node) 21 | PyObject *ct_get_item(node_t *root, object key) 22 | node_t *ct_max_node(node_t *root) 23 | node_t *ct_min_node(node_t *root) 24 | node_t *ct_succ_node(node_t *root, object key) 25 | node_t *ct_prev_node(node_t *root, object key) 26 | node_t *ct_floor_node(node_t *root, object key) 27 | node_t *ct_ceiling_node(node_t *root, object key) 28 | int ct_index_of(node_t *root, object key) 29 | node_t *ct_node_at(node_t *root, int index) 30 | 31 | # binary-tree functions 32 | int ct_bintree_insert(node_t **root, object key, object value) 33 | int ct_bintree_remove(node_t **root, object key) 34 | # avl-tree functions 35 | int avl_insert(node_t **root, object key, object value) 36 | int avl_remove(node_t **root, object key) 37 | # rb-tree functions 38 | int rb_insert(node_t **root, object key, object value) 39 | int rb_remove(node_t **root, object key) -------------------------------------------------------------------------------- /bintrees/cython_trees.pyx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Purpose: Binary trees implemented in Cython/C 5 | # Created: 28.04.2010 6 | # Copyright (c) 2010-2013 by Manfred Moitzi 7 | # License: MIT License 8 | 9 | from .abctree import _ABCTree 10 | from ctrees cimport * 11 | 12 | DEF MAXSTACK = 64 13 | 14 | cdef class NodeStack: 15 | """Simple stack for tree nodes.""" 16 | cdef node_t* stack[MAXSTACK] 17 | cdef int stackptr 18 | 19 | def __cinit__(self): 20 | self.stackptr = 0 21 | 22 | cdef push(self, node_t* node): 23 | if self.stackptr >= MAXSTACK: 24 | raise RuntimeError("Stack overflow in NodeStack.push().") 25 | self.stack[self.stackptr] = node 26 | self.stackptr += 1 27 | 28 | cdef node_t* pop(self): 29 | if self.stackptr <= 0: 30 | raise RuntimeError("Stack underflow in NodeStack.pop().") 31 | self.stackptr -= 1 32 | return self.stack[self.stackptr] 33 | 34 | cdef bint is_empty(self): 35 | return self.stackptr == 0 36 | 37 | cdef class _BaseTree: 38 | cdef node_t *root # private (hidden) for CPython 39 | cdef readonly int count # public readonly access for CPython 40 | 41 | def __cinit__(self, items=None): 42 | self.root = NULL 43 | self.count = 0 44 | 45 | def __init__(self, items=None): 46 | if items is not None: 47 | self.update(items) 48 | 49 | def __dealloc__(self): 50 | ct_delete_tree(self.root) 51 | 52 | def __getstate__(self): 53 | return dict(self.items()) 54 | 55 | def __setstate__(self, state): 56 | self.update(state) 57 | 58 | def clear(self): 59 | ct_delete_tree(self.root) 60 | self.count = 0 61 | self.root = NULL 62 | 63 | def get_value(self, key): 64 | cdef node_t *result = ct_find_node(self.root, key) 65 | if result == NULL: 66 | raise KeyError(key) 67 | else: 68 | return result.value 69 | 70 | def max_item(self): 71 | """Get item with max key of tree, raises ValueError if tree is empty.""" 72 | cdef node_t *node = ct_max_node(self.root) 73 | if node == NULL: 74 | raise ValueError("Tree is empty") 75 | return node.key, node.value 76 | 77 | def min_item(self): 78 | """Get item with min key of tree, raises ValueError if tree is empty.""" 79 | cdef node_t *node = ct_min_node(self.root) 80 | if node == NULL: 81 | raise ValueError("Tree is empty") 82 | return node.key, node.value 83 | 84 | def succ_item(self, key): 85 | """Get successor (k,v) pair of key, raises KeyError if key is max key 86 | or key does not exist. 87 | """ 88 | cdef node_t *node = ct_succ_node(self.root, key) 89 | if node == NULL: # given key is biggest in tree 90 | raise KeyError(str(key)) 91 | return node.key, node.value 92 | 93 | def prev_item(self, key): 94 | """Get predecessor (k,v) pair of key, raises KeyError if key is min key 95 | or key does not exist. 96 | """ 97 | cdef node_t *node = ct_prev_node(self.root, key) 98 | if node == NULL: # given key is smallest in tree 99 | raise KeyError(str(key)) 100 | return node.key, node.value 101 | 102 | def floor_item(self, key): 103 | """Get (k,v) pair associated with the greatest key less than or equal to 104 | the given key, raises KeyError if there is no such key. 105 | """ 106 | cdef node_t *node = ct_floor_node(self.root, key) 107 | if node == NULL: # given key is smaller than min-key in tree 108 | raise KeyError(str(key)) 109 | return node.key, node.value 110 | 111 | def ceiling_item(self, key): 112 | """Get (k,v) pair associated with the smallest key greater than or equal to 113 | the given key, raises KeyError if there is no such key. 114 | """ 115 | cdef node_t *node = ct_ceiling_node(self.root, key) 116 | if node == NULL: # given key is greater than max-key in tree 117 | raise KeyError(str(key)) 118 | return node.key, node.value 119 | 120 | def iter_items(self, start_key=None, end_key=None, reverse=False): 121 | """Iterate over the (key, value) items in ascending order 122 | if reverse is True iterate in descending order. 123 | """ 124 | if self.count == 0: 125 | return 126 | cdef int direction = 1 if reverse else 0 127 | cdef int other = 1 - direction 128 | cdef bint go_down = True 129 | cdef NodeStack stack = NodeStack() 130 | cdef node_t *node 131 | 132 | node = self.root 133 | while True: 134 | if node.link[direction] != NULL and go_down: 135 | stack.push(node) 136 | node = node.link[direction] 137 | else: 138 | if (start_key is None or ct_compare(start_key, node.key) < 1) and \ 139 | (end_key is None or ct_compare(end_key, node.key) > 0): 140 | yield node.key, node.value 141 | if node.link[other] != NULL: 142 | node = node.link[other] 143 | go_down = True 144 | else: 145 | if stack.is_empty(): 146 | return # all done 147 | node = stack.pop() 148 | go_down = False 149 | 150 | def pop_item(self): 151 | """ T.pop_item() -> (k, v), remove and return some (key, value) pair as a 152 | 2-tuple; but raise KeyError if T is empty. 153 | """ 154 | if self.count == 0: 155 | raise KeyError("pop_item(): tree is empty") 156 | 157 | cdef node_t *node = ct_get_leaf_node(self.root) 158 | key = node.key 159 | value = node.value 160 | self.remove(key) 161 | return key, value 162 | popitem = pop_item # for compatibility to dict() 163 | 164 | def foreach(self, func, int order=0): 165 | """Visit all tree nodes and process tree data by func(key, Value). 166 | 167 | parm func: function(key, value) 168 | param int order: inorder = 0, preorder = -1, postorder = +1 169 | """ 170 | if self.count == 0: 171 | return 172 | cdef NodeStack stack = NodeStack() 173 | cdef NodeStack tempstack = NodeStack() 174 | cdef node_t *node = self.root 175 | 176 | if order == 0: 177 | while not stack.is_empty() or node: 178 | if node: 179 | stack.push(node) 180 | node = node.link[0] 181 | else: 182 | node = stack.pop() 183 | func(node.key, node.value) 184 | node = node.link[1] 185 | elif order == -1: 186 | stack.push(node) 187 | while not stack.is_empty(): 188 | node = stack.pop() 189 | func(node.key, node.value) 190 | if node.link[1]: 191 | stack.push(node.link[1]) 192 | if node.link[0]: 193 | stack.push(node.link[0]) 194 | elif order == +1: 195 | tempstack.push(node) 196 | while not tempstack.is_empty(): 197 | node = tempstack.pop() 198 | stack.push(node) 199 | if node.link[0]: 200 | tempstack.push(node.link[0]) 201 | if node.link[1]: 202 | tempstack.push(node.link[1]) 203 | while not stack.is_empty(): 204 | node = stack.pop() 205 | func(node.key, node.value) 206 | 207 | 208 | cdef class _BinaryTree(_BaseTree): 209 | def insert(self, key, value): 210 | cdef int result = ct_bintree_insert(&self.root, key, value) 211 | if result < 0: 212 | raise MemoryError('Can not allocate memory for node structure.') 213 | self.count += result 214 | 215 | def remove(self, key): 216 | cdef int result 217 | result = ct_bintree_remove(&self.root, key) 218 | if result == 0: 219 | raise KeyError(str(key)) 220 | else: 221 | self.count -= 1 222 | 223 | 224 | class FastBinaryTree(_BinaryTree, _ABCTree): 225 | pass 226 | 227 | 228 | cdef class _AVLTree(_BaseTree): 229 | def insert(self, key, value): 230 | cdef int result = avl_insert(&self.root, key, value) 231 | if result < 0: 232 | raise MemoryError('Can not allocate memory for node structure.') 233 | else: 234 | self.count += result 235 | 236 | def remove(self, key): 237 | cdef int result = avl_remove(&self.root, key) 238 | if result == 0: 239 | raise KeyError(str(key)) 240 | else: 241 | self.count -= 1 242 | 243 | 244 | class FastAVLTree(_AVLTree, _ABCTree): 245 | pass 246 | 247 | 248 | cdef class _RBTree(_BaseTree): 249 | def insert(self, key, value): 250 | cdef int result = rb_insert(&self.root, key, value) 251 | if result < 0: 252 | raise MemoryError('Can not allocate memory for node structure.') 253 | else: 254 | self.count += result 255 | 256 | def remove(self, key): 257 | cdef int result = rb_remove(&self.root, key) 258 | if result == 0: 259 | raise KeyError(str(key)) 260 | else: 261 | self.count -= 1 262 | 263 | 264 | class FastRBTree(_RBTree, _ABCTree): 265 | pass 266 | -------------------------------------------------------------------------------- /bintrees/rbtree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding:utf-8 3 | # Author: mozman (python version) 4 | # Purpose: red-black tree module (Julienne Walker's none recursive algorithm) 5 | # source: http://eternallyconfuzzled.com/tuts/datastructures/jsw_tut_rbtree.aspx 6 | # Created: 01.05.2010 7 | # Copyright (c) 2010-2013 by Manfred Moitzi 8 | # License: MIT License 9 | 10 | # Conclusion of Julian Walker 11 | 12 | # Red black trees are interesting beasts. They're believed to be simpler than 13 | # AVL trees (their direct competitor), and at first glance this seems to be the 14 | # case because insertion is a breeze. However, when one begins to play with the 15 | # deletion algorithm, red black trees become very tricky. However, the 16 | # counterweight to this added complexity is that both insertion and deletion 17 | # can be implemented using a single pass, top-down algorithm. Such is not the 18 | # case with AVL trees, where only the insertion algorithm can be written top-down. 19 | # Deletion from an AVL tree requires a bottom-up algorithm. 20 | 21 | # So when do you use a red black tree? That's really your decision, but I've 22 | # found that red black trees are best suited to largely random data that has 23 | # occasional degenerate runs, and searches have no locality of reference. This 24 | # takes full advantage of the minimal work that red black trees perform to 25 | # maintain balance compared to AVL trees and still allows for speedy searches. 26 | 27 | # Red black trees are popular, as most data structures with a whimsical name. 28 | # For example, in Java and C++, the library map structures are typically 29 | # implemented with a red black tree. Red black trees are also comparable in 30 | # speed to AVL trees. While the balance is not quite as good, the work it takes 31 | # to maintain balance is usually better in a red black tree. There are a few 32 | # misconceptions floating around, but for the most part the hype about red black 33 | # trees is accurate. 34 | 35 | from __future__ import absolute_import 36 | 37 | from .abctree import ABCTree 38 | 39 | __all__ = ['RBTree'] 40 | 41 | 42 | class Node(object): 43 | """Internal object, represents a tree node.""" 44 | __slots__ = ['key', 'value', 'red', 'left', 'right'] 45 | 46 | def __init__(self, key=None, value=None): 47 | self.key = key 48 | self.value = value 49 | self.red = True 50 | self.left = None 51 | self.right = None 52 | 53 | def free(self): 54 | self.left = None 55 | self.right = None 56 | self.key = None 57 | self.value = None 58 | 59 | def __getitem__(self, key): 60 | """N.__getitem__(key) <==> x[key], where key is 0 (left) or 1 (right).""" 61 | return self.left if key == 0 else self.right 62 | 63 | def __setitem__(self, key, value): 64 | """N.__setitem__(key, value) <==> x[key]=value, where key is 0 (left) or 1 (right).""" 65 | if key == 0: 66 | self.left = value 67 | else: 68 | self.right = value 69 | 70 | 71 | def is_red(node): 72 | if (node is not None) and node.red: 73 | return True 74 | else: 75 | return False 76 | 77 | 78 | def jsw_single(root, direction): 79 | other_side = 1 - direction 80 | save = root[other_side] 81 | root[other_side] = save[direction] 82 | save[direction] = root 83 | root.red = True 84 | save.red = False 85 | return save 86 | 87 | 88 | def jsw_double(root, direction): 89 | other_side = 1 - direction 90 | root[other_side] = jsw_single(root[other_side], other_side) 91 | return jsw_single(root, direction) 92 | 93 | 94 | class RBTree(ABCTree): 95 | """ 96 | RBTree implements a balanced binary tree with a dict-like interface. 97 | 98 | see: http://en.wikipedia.org/wiki/Red_black_tree 99 | 100 | A red-black tree is a type of self-balancing binary search tree, a data 101 | structure used in computing science, typically used to implement associative 102 | arrays. The original structure was invented in 1972 by Rudolf Bayer, who 103 | called them "symmetric binary B-trees", but acquired its modern name in a 104 | paper in 1978 by Leonidas J. Guibas and Robert Sedgewick. It is complex, 105 | but has good worst-case running time for its operations and is efficient in 106 | practice: it can search, insert, and delete in O(log n) time, where n is 107 | total number of elements in the tree. Put very simply, a red-black tree is a 108 | binary search tree which inserts and removes intelligently, to ensure the 109 | tree is reasonably balanced. 110 | 111 | RBTree() -> new empty tree. 112 | RBTree(mapping) -> new tree initialized from a mapping 113 | RBTree(seq) -> new tree initialized from seq [(k1, v1), (k2, v2), ... (kn, vn)] 114 | 115 | see also abctree.ABCTree() class. 116 | """ 117 | 118 | def _new_node(self, key, value): 119 | """Create a new tree node.""" 120 | self._count += 1 121 | return Node(key, value) 122 | 123 | def insert(self, key, value): 124 | """T.insert(key, value) <==> T[key] = value, insert key, value into tree.""" 125 | if self._root is None: # Empty tree case 126 | self._root = self._new_node(key, value) 127 | self._root.red = False # make root black 128 | return 129 | 130 | head = Node() # False tree root 131 | grand_parent = None 132 | grand_grand_parent = head 133 | parent = None # parent 134 | direction = 0 135 | last = 0 136 | 137 | # Set up helpers 138 | grand_grand_parent.right = self._root 139 | node = grand_grand_parent.right 140 | # Search down the tree 141 | while True: 142 | if node is None: # Insert new node at the bottom 143 | node = self._new_node(key, value) 144 | parent[direction] = node 145 | elif is_red(node.left) and is_red(node.right): # Color flip 146 | node.red = True 147 | node.left.red = False 148 | node.right.red = False 149 | 150 | # Fix red violation 151 | if is_red(node) and is_red(parent): 152 | direction2 = 1 if grand_grand_parent.right is grand_parent else 0 153 | if node is parent[last]: 154 | grand_grand_parent[direction2] = jsw_single(grand_parent, 1 - last) 155 | else: 156 | grand_grand_parent[direction2] = jsw_double(grand_parent, 1 - last) 157 | 158 | # Stop if found 159 | if key == node.key: 160 | node.value = value # set new value for key 161 | break 162 | 163 | last = direction 164 | direction = 0 if key < node.key else 1 165 | # Update helpers 166 | if grand_parent is not None: 167 | grand_grand_parent = grand_parent 168 | grand_parent = parent 169 | parent = node 170 | node = node[direction] 171 | 172 | self._root = head.right # Update root 173 | self._root.red = False # make root black 174 | 175 | def remove(self, key): 176 | """T.remove(key) <==> del T[key], remove item from tree.""" 177 | if self._root is None: 178 | raise KeyError(str(key)) 179 | head = Node() # False tree root 180 | node = head 181 | node.right = self._root 182 | parent = None 183 | grand_parent = None 184 | found = None # Found item 185 | direction = 1 186 | 187 | # Search and push a red down 188 | while node[direction] is not None: 189 | last = direction 190 | 191 | # Update helpers 192 | grand_parent = parent 193 | parent = node 194 | node = node[direction] 195 | 196 | direction = 1 if key > node.key else 0 197 | 198 | # Save found node 199 | if key == node.key: 200 | found = node 201 | 202 | # Push the red node down 203 | if not is_red(node) and not is_red(node[direction]): 204 | if is_red(node[1 - direction]): 205 | parent[last] = jsw_single(node, direction) 206 | parent = parent[last] 207 | elif not is_red(node[1 - direction]): 208 | sibling = parent[1 - last] 209 | if sibling is not None: 210 | if (not is_red(sibling[1 - last])) and (not is_red(sibling[last])): 211 | # Color flip 212 | parent.red = False 213 | sibling.red = True 214 | node.red = True 215 | else: 216 | direction2 = 1 if grand_parent.right is parent else 0 217 | if is_red(sibling[last]): 218 | grand_parent[direction2] = jsw_double(parent, last) 219 | elif is_red(sibling[1 - last]): 220 | grand_parent[direction2] = jsw_single(parent, last) 221 | # Ensure correct coloring 222 | grand_parent[direction2].red = True 223 | node.red = True 224 | grand_parent[direction2].left.red = False 225 | grand_parent[direction2].right.red = False 226 | 227 | # Replace and remove if found 228 | if found is not None: 229 | found.key = node.key 230 | found.value = node.value 231 | parent[int(parent.right is node)] = node[int(node.left is None)] 232 | node.free() 233 | self._count -= 1 234 | 235 | # Update root and make it black 236 | self._root = head.right 237 | if self._root is not None: 238 | self._root.red = False 239 | if not found: 240 | raise KeyError(str(key)) 241 | -------------------------------------------------------------------------------- /bintrees/treeslice.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding:utf-8 3 | # Author: mozman -- 4 | # Purpose: TreeSlice 5 | # Created: 11.04.2011 6 | # Copyright (c) 2010-2013 by Manfred Moitzi 7 | # License: MIT License 8 | 9 | 10 | class TreeSlice(object): 11 | __slots__ = ['_tree', '_start', '_stop'] 12 | 13 | def __init__(self, tree, start, stop): 14 | self._tree = tree 15 | self._start = start 16 | self._stop = stop 17 | 18 | def __repr__(self): 19 | tpl = "%s({%s})" % (self._tree.__class__.__name__, '%s') 20 | return tpl % ", ".join(("%r: %r" % item for item in self.items())) 21 | 22 | def __contains__(self, key): 23 | if self._is_in_range(key): 24 | return key in self._tree 25 | else: 26 | return False 27 | 28 | def _is_in_range(self, key): 29 | if self._start is not None and key < self._start: 30 | return False 31 | if self._stop is not None and key >= self._stop: 32 | return False 33 | return True 34 | 35 | def __getitem__(self, key): 36 | if isinstance(key, slice): 37 | return self._sub_slice(key.start, key.stop) 38 | if self._is_in_range(key): 39 | return self._tree[key] 40 | else: 41 | raise KeyError(key) 42 | 43 | def _sub_slice(self, start, stop): 44 | def newstart(): 45 | if start is None: 46 | return self._start 47 | elif self._start is None: 48 | return start 49 | else: 50 | return max(start, self._start) 51 | 52 | def newstop(): 53 | if stop is None: 54 | return self._stop 55 | elif self._stop is None: 56 | return stop 57 | else: 58 | return min(stop, self._stop) 59 | 60 | return TreeSlice(self._tree, newstart(), newstop()) 61 | 62 | def keys(self): 63 | return self._tree.key_slice(self._start, self._stop) 64 | 65 | __iter__ = keys 66 | 67 | def values(self): 68 | return self._tree.value_slice(self._start, self._stop) 69 | 70 | def items(self): 71 | return self._tree.iter_items(self._start, self._stop) 72 | -------------------------------------------------------------------------------- /issues/003_fastrbtree_crash.py: -------------------------------------------------------------------------------- 1 | from bintrees import RBTree, FastRBTree 2 | 3 | # Commenting in the first outcommented line in below function 4 | # results in the trees being unequal and a KeyError occurring. They should 5 | # not be unequal (the RBTree is correct) 6 | # Commenting in further lines results in further discrepancies 7 | 8 | def populate(tree): 9 | tree[14] = tree.get(14,0) + 212 10 | tree[15.84] = tree.get(15.84,0) + 623 11 | tree[16] = tree.get(16,0) + 693 12 | tree[16] = 1213 13 | tree[16.3] = tree.get(16.3,0) + 1952 14 | tree[15.8] = tree.get(15.8,0) + 1934 15 | tree[16.48] = tree.get(16.48,0) + 65 16 | tree[14.95] = tree.get(14.95,0) + 325 17 | tree[15.07] = tree.get(15.07,0) + 1293 18 | tree[16.41] = tree.get(16.41,0) + 2000 19 | tree[16.43] = tree.get(16.43,0) + 2000 20 | tree[16.45] = tree.get(16.45,0) + 2000 21 | tree[16.4] = tree.get(16.4,0) + 2000 22 | tree[16.42] = tree.get(16.42,0) + 2000 23 | tree[16.47] = tree.get(16.47,0) + 2000 24 | tree[16.44] = tree.get(16.44,0) + 2000 25 | tree[16.46] = tree.get(16.46,0) + 2000 26 | tree[16.48] = tree.get(16.48,0) + 2065 27 | tree[16.51] = tree.get(16.51,0) + 600 28 | tree[16.5] = tree.get(16.5,0) + 600 29 | tree[16.49] = tree.get(16.49,0) + 600 30 | tree[16.5] = 1400 31 | tree[16.49] = 2600 32 | tree[16.49] = 3159 33 | tree[16.47] = 2694 34 | tree[16.5] = 2079 35 | tree[16.48] = 2599 36 | tree[16.46] = 2564 37 | tree[16.44] = 2709 38 | # tree[16.45] = 2644 39 | # tree[16.43] = 2621 40 | # tree[16.49] = 3959 41 | # tree[16.47] = 3494 42 | # tree[16.48] = 3399 43 | # tree[16.46] = 3364 44 | # tree[16.44] = 3509 45 | # tree[16.45] = 3444 46 | # tree[16.43] = 3421 47 | # tree[16.46] = 3735 48 | # del tree[15.84] 49 | # tree[16.43] = 4921 50 | # tree[16.48] = 4099 51 | # tree[16.5] = 1279 52 | # tree[16.49] = 1959 53 | # tree[16.39] = tree.get(16.39,0) + 2000 54 | 55 | rbt = RBTree() 56 | frbt = FastRBTree() 57 | populate(rbt) 58 | populate(frbt) 59 | 60 | print('RBT len: {0} FRBT len: {1}'.format(len(rbt), len(frbt))) 61 | for key, value in rbt.items(): 62 | print("RBTree[{key}] = {value} <-> FastRBTree[{key}] = {value2}".format(key=key, value=value, value2=frbt[key])) 63 | -------------------------------------------------------------------------------- /issues/004_rbtree_copy.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Apr 11, 2013 3 | 4 | @author: matthijssnel 5 | ''' 6 | 7 | from bintrees import FastRBTree 8 | 9 | 10 | def print_node(key, value): 11 | print("Key: {}; Value:{}".format(key, value)) 12 | 13 | 14 | def populate(tree): 15 | tree[20.5] = tree.get(20.5, 0) + 644 16 | tree[17.35] = tree.get(17.35, 0) + 32 17 | tree[19.5] = tree.get(19.5, 0) + 440 18 | tree[20.0] = tree.get(20.0, 0) + 73 19 | tree[18.5] = tree.get(18.5, 0) + 1500 20 | tree[20.8] = tree.get(20.8, 0) + 330 21 | tree[21.0] = tree.get(21.0, 0) + 450 22 | tree[19.25] = tree.get(19.25, 0) + 137 23 | tree[18.7] = tree.get(18.7, 0) + 740 24 | tree[20.12] = tree.get(20.12, 0) + 500 25 | tree[19.85] = tree.get(19.85, 0) + 300 26 | del tree[17.35] 27 | tree[18.5] = 1662 28 | tree[17.23] = tree.get(17.23, 0) + 4594 29 | tree[16.6] = tree.get(16.6, 0) + 2000 30 | tree[16.62] = tree.get(16.62, 0) + 2000 31 | tree[16.66] = tree.get(16.66, 0) + 2000 32 | tree[16.68] = tree.get(16.68, 0) + 2000 33 | tree[16.61] = tree.get(16.61, 0) + 2000 34 | tree[16.64] = tree.get(16.64, 0) + 2000 35 | tree[16.67] = tree.get(16.67, 0) + 2000 36 | tree[16.57] = tree.get(16.57, 0) + 600 37 | tree[16.58] = tree.get(16.58, 0) + 600 38 | tree[16.59] = tree.get(16.59, 0) + 600 39 | del tree[16.68] 40 | tree[16.59] = 2600 41 | tree[16.59] = 2000 42 | tree[16.56] = tree.get(16.56, 0) + 600 43 | tree[16.59] = 2800 44 | del tree[16.67] 45 | tree[16.58] = 2600 46 | tree[16.56] = 5796 47 | tree[16.56] = 600 48 | tree[16.57] = 2600 49 | tree[16.56] = 1400 50 | tree[16.55] = tree.get(16.55, 0) + 5196 51 | tree[16.53] = tree.get(16.53, 0) + 548 52 | tree[16.55] = 5829 53 | tree[16.54] = tree.get(16.54, 0) + 657 54 | tree[16.56] = 1964 55 | tree[16.58] = 3119 56 | tree[16.6] = 2691 57 | tree[16.57] = 3245 58 | tree[16.59] = 3385 59 | tree[16.58] = 3919 60 | tree[16.6] = 3491 61 | tree[16.57] = 4045 62 | del tree[16.66] 63 | tree[16.56] = 2764 64 | tree[16.55] = 6629 65 | tree[16.58] = 3319 66 | tree[16.55] = 7229 67 | tree[16.55] = 2033 68 | tree[16.55] = 2833 69 | tree[16.54] = 1457 70 | tree[16.54] = 6653 71 | tree[16.54] = 5996 72 | tree[16.62] = 2492 73 | del tree[16.53] 74 | tree[16.61] = 2708 75 | tree[16.54] = 5196 76 | tree[16.55] = 2033 77 | tree[16.62] = 2000 78 | tree[16.54] = 5801 79 | tree[16.62] = 2800 80 | tree[16.61] = 3508 81 | tree[16.58] = 3687 82 | tree[16.61] = 2800 83 | tree[16.53] = tree.get(16.53, 0) + 522 84 | tree[16.55] = 2833 85 | tree[16.54] = 6601 86 | tree[16.54] = 1405 87 | tree[16.53] = 5718 88 | tree[16.6] = 2800 89 | tree[16.52] = tree.get(16.52, 0) + 537 90 | tree[16.58] = 3319 91 | tree[16.56] = 3133 92 | tree.copy() 93 | del tree[16.52] 94 | tree[16.6] = 3471 95 | tree[16.6] = 2800 96 | tree[16.52] = tree.get(16.52, 0) + 655 97 | tree[16.54] = 2905 98 | tree[16.57] = 3445 99 | del tree[16.64] 100 | tree[16.54] = 3705 101 | tree[16.53] = 6518 102 | tree[16.59] = 2800 103 | tree[16.51] = tree.get(16.51, 0) + 523 104 | clone = tree.copy() 105 | print("\nOriginal Tree:") 106 | tree.foreach(print_node) 107 | print("\nClone Tree:") 108 | clone.foreach(print_node) 109 | 110 | 111 | tree = FastRBTree() 112 | populate(tree) 113 | 114 | -------------------------------------------------------------------------------- /issues/005_btreeslow.py: -------------------------------------------------------------------------------- 1 | import bintrees 2 | import time 3 | 4 | t = bintrees.FastRBTree.fromkeys(range(1000000), True) 5 | start = 50000 6 | 7 | key = start 8 | t0 = time.time() 9 | while True: 10 | try: 11 | key, _ = t.succ_item(key) 12 | except KeyError: 13 | break 14 | t1 = time.time() 15 | print("Iterating using succ_item(): %f sec" % (t1-t0)) 16 | 17 | 18 | t0 = time.time() 19 | for key, _ in t.item_slice(start, None): 20 | pass 21 | t1 = time.time() 22 | print("Iterating using item_slice(): %f sec" % (t1-t0)) 23 | -------------------------------------------------------------------------------- /issues/006_large_data_crash.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | import bintrees 3 | 4 | 5 | class HyperGraph: 6 | def __init__(self): 7 | self.alerts = [] 8 | for x in range(0, 3000): 9 | alert = [] 10 | for y in range(0, 6): 11 | alert.append(repr(randint(0, 7))) 12 | self.alerts.append(alert) 13 | self.hyper_dict = {} 14 | self.hcombinations = [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), 15 | (2, 5), (3, 4), (3, 5), (4, 5)] 16 | for alert in self.alerts: 17 | for c in self.hcombinations: 18 | key = alert[:] 19 | for x in range(2): 20 | key[c[x]] = '*' 21 | hyperkey = tuple(key) 22 | if hyperkey in self.hyper_dict: 23 | self.hyper_dict[hyperkey].alerts.get(("test1", "test2")) 24 | else: 25 | self.hyper_dict[hyperkey] = Hyperedge(key) 26 | 27 | for alert in self.alerts: 28 | for c in self.hcombinations: 29 | tmpkey = alert[:] 30 | for x in range(2): 31 | tmpkey[c[x]] = '*' 32 | tmpkey = tuple(tmpkey) 33 | if tmpkey in self.hyper_dict: 34 | del (self.hyper_dict[tmpkey]) 35 | 36 | 37 | class Hyperedge: 38 | def __init__(self, hyperkey): 39 | self.hyperkey = hyperkey 40 | self.alerts = bintrees.FastRBTree() 41 | self.alerts.insert(("test1", "test2"), 1) # replace list by tuple -> no crash 42 | 43 | 44 | for x in range(10): 45 | alertgraph = HyperGraph() 46 | print("finished iteration {} of 10".format(x)) 47 | -------------------------------------------------------------------------------- /issues/007_FastRBTree_error2.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from bintrees import FastRBTree as Tree 3 | 4 | class Hyperedge: 5 | def __init__(self, hyperkey, col, hlabel): 6 | self.hyperkey = hyperkey 7 | self.col = col 8 | self._alerts = Tree() 9 | self.insert_alert(hlabel, 1) 10 | self.nalerts = 1 11 | 12 | def get_alert(self, key): 13 | return self._alerts.get(key) 14 | 15 | def insert_alert(self, alert_key, count): 16 | self._alerts.insert(alert_key, count) 17 | 18 | def foreach_alert(self, func): 19 | self._alerts.foreach(func) 20 | 21 | def pop_alert(self, key): 22 | return self._alerts.pop(key) 23 | 24 | 25 | def treeloop(hlabel, dupcount): 26 | alert = hyperedge.hyperkey[:] 27 | for x in range(2): 28 | alert[hyperedge.col[x]] = hlabel[x] 29 | for c in hcombinations: 30 | if hyperedge.col != c: 31 | label = [] 32 | for x in range(2): 33 | label.append(alert[c[x]]) 34 | alert[c[x]] = '*' 35 | tmpkey = tuple(alert) 36 | for x in range(2): 37 | alert[c[x]] = label[x] 38 | hlabel = tuple(label) 39 | if tmpkey in hyper_dict: 40 | tmpedge = hyper_dict[tmpkey] 41 | hypersize_list.discard((tmpedge.nalerts, tmpedge.hyperkey)) 42 | tmpedge.nalerts -= tmpedge.pop_alert(hlabel) 43 | if tmpedge.nalerts > 0: 44 | hypersize_list.insert((tmpedge.nalerts, tmpedge.hyperkey), tmpedge) 45 | 46 | 47 | for z in range(10): 48 | hyper_dict = {} 49 | hypersize_list = Tree() 50 | hcombinations = [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), 51 | (3, 4), (3, 5), (4, 5)] 52 | for x in range(0, 3000): 53 | alert = [] 54 | for y in range(0, 6): 55 | alert.append(repr(randint(0, 8 - 1)) + "test") 56 | for c in hcombinations: 57 | label = [] 58 | key = alert[:] 59 | for x in range(2): 60 | key[c[x]] = '*' 61 | label.append(alert[c[x]]) 62 | 63 | hyperkey = tuple(key) 64 | hlabel = tuple(label) 65 | if hyperkey in hyper_dict: 66 | hyper_dict[hyperkey].nalerts += 1 67 | result = hyper_dict[hyperkey].get_alert(hlabel) 68 | if result is not None: 69 | hyper_dict[hyperkey].insert_alert(hlabel, result + 1) 70 | else: 71 | hyper_dict[hyperkey].insert_alert(hlabel, 1) 72 | else: 73 | hyper_dict[hyperkey] = Hyperedge(key, c, hlabel) 74 | 75 | for hyperedge in hyper_dict.values(): 76 | hypersize_list.insert((hyperedge.nalerts, hyperedge.hyperkey), hyperedge) 77 | 78 | while not hypersize_list.is_empty(): 79 | (key, hyperedge) = hypersize_list.pop_max() 80 | hyperedge.foreach_alert(treeloop) 81 | print("Completed iteration %d of 10" % z) 82 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # Author: mozman 2 | # License: MIT-License 3 | 4 | BUILD_OPTIONS = setup.py build_ext --inplace --force 5 | TEST_OPTIONS = -m unittest discover 6 | SRC = bintrees 7 | PYTHON3 = py -3.9 8 | PYPY3 = pypy3 9 | 10 | .PHONY: build test testpypy3 packages 11 | 12 | build: 13 | $(PYTHON3) $(BUILD_OPTIONS) 14 | 15 | test: build 16 | $(PYTHON3) $(TEST_OPTIONS) 17 | 18 | testpypy3: 19 | $(PYPY3) $(TEST_OPTIONS) 20 | 21 | clean: 22 | rm -f $(SRC)/cython_trees.c 23 | rm -f $(SRC)/*.pyd 24 | 25 | packages: test 26 | $(PYTHON3) setup.py bdist_wheel 27 | $(PYTHON3) setup.py sdist --formats=zip 28 | -------------------------------------------------------------------------------- /profiling/profile_avltree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Purpose: profile AVLTree, FastAVLTree 5 | # Created: 01.05.2010 6 | # Copyright (c) 2010-2013 by Manfred Moitzi 7 | # License: MIT License 8 | 9 | import sys 10 | from timeit import Timer 11 | from random import shuffle 12 | 13 | from bintrees import AVLTree, has_fast_tree_support 14 | from bintrees import FastAVLTree 15 | 16 | COUNT = 100 17 | 18 | setup_AVLTree = """ 19 | from __main__ import avl_build_delete, avl_build, avl_search 20 | """ 21 | setup_FastAVLTree = """ 22 | from __main__ import cavl_build_delete, cavl_build, cavl_search 23 | """ 24 | 25 | try: 26 | fp = open('testkeys.txt') 27 | keys = eval(fp.read()) 28 | fp.close() 29 | except IOError: 30 | print("create 'testkeys.txt' with profile_bintree.py\n") 31 | sys.exit() 32 | 33 | py_searchtree = AVLTree.from_keys(keys) 34 | cy_searchtree = FastAVLTree.from_keys(keys) 35 | 36 | 37 | def avl_build_delete(): 38 | tree = AVLTree.from_keys(keys) 39 | for key in keys: 40 | del tree[key] 41 | 42 | 43 | def cavl_build_delete(): 44 | tree = FastAVLTree.from_keys(keys) 45 | for key in keys: 46 | del tree[key] 47 | 48 | 49 | def avl_build(): 50 | tree = AVLTree.from_keys(keys) 51 | 52 | 53 | def cavl_build(): 54 | tree = FastAVLTree.from_keys(keys) 55 | 56 | 57 | def avl_search(): 58 | for key in keys: 59 | obj = py_searchtree[key] 60 | 61 | 62 | def cavl_search(): 63 | for key in keys: 64 | obj = cy_searchtree[key] 65 | 66 | 67 | def print_result(time, text): 68 | print("Operation: %s takes %.2f seconds\n" % (text, time)) 69 | 70 | 71 | def main(): 72 | fp = open('testkeys.txt', 'w') 73 | fp.write(repr(keys)) 74 | fp.close() 75 | print ("Nodes: %d" % len(keys)) 76 | 77 | t = Timer("avl_build()", setup_AVLTree) 78 | print_result(t.timeit(COUNT), 'AVLTree build only') 79 | 80 | t = Timer("cavl_build()", setup_FastAVLTree) 81 | print_result(t.timeit(COUNT), 'FastAVLTree build only') 82 | 83 | t = Timer("avl_build_delete()", setup_AVLTree) 84 | print_result(t.timeit(COUNT), 'AVLTree build & delete') 85 | 86 | t = Timer("cavl_build_delete()", setup_FastAVLTree) 87 | print_result(t.timeit(COUNT), 'FastAVLTree build & delete') 88 | 89 | # shuffle search keys 90 | shuffle(keys) 91 | t = Timer("avl_search()", setup_AVLTree) 92 | print_result(t.timeit(COUNT), 'AVLTree search') 93 | 94 | t = Timer("cavl_search()", setup_FastAVLTree) 95 | print_result(t.timeit(COUNT), 'FastAVLTree search') 96 | 97 | if __name__ == '__main__': 98 | if not has_fast_tree_support(): 99 | print("Cython extension for FastAVLTree is NOT working.") 100 | else: 101 | print("Cython extension for FastAVLTree is working.") 102 | main() 103 | -------------------------------------------------------------------------------- /profiling/profile_big_rbtree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Purpose: profile big trees 5 | # Created: 15.02.2015 6 | # Copyright (c) 2015 by Manfred Moitzi 7 | # License: MIT License 8 | 9 | from timeit import Timer 10 | from random import shuffle 11 | 12 | from bintrees import FastRBTree, has_fast_tree_support 13 | 14 | COUNT = 1 15 | ITEMS = 2**22 16 | 17 | setup_dict = """ 18 | from __main__ import dict_build_delete, dict_build, dict_search 19 | """ 20 | setup_FastRBTree = """ 21 | from __main__ import crb_build_delete, crb_build, crb_search 22 | """ 23 | 24 | 25 | keys = [k for k in range(ITEMS)] 26 | shuffle(keys) 27 | 28 | fastrbtree_searchtree = FastRBTree.from_keys(keys) 29 | dict_searchtree = dict.fromkeys(keys) 30 | 31 | 32 | def dict_build_delete(): 33 | d = dict.fromkeys(keys) 34 | for key in keys: 35 | del d[key] 36 | 37 | 38 | def crb_build_delete(): 39 | tree = FastRBTree.from_keys(keys) 40 | for key in keys: 41 | del tree[key] 42 | 43 | 44 | def dict_build(): 45 | d = dict.fromkeys(keys) 46 | 47 | 48 | def crb_build(): 49 | tree = FastRBTree.from_keys(keys) 50 | 51 | 52 | def dict_search(): 53 | for key in keys: 54 | obj = dict_searchtree[key] 55 | 56 | 57 | def crb_search(): 58 | for key in keys: 59 | obj = fastrbtree_searchtree[key] 60 | 61 | 62 | def print_result(time, text): 63 | print("Operation: %s takes %.2f seconds\n" % (text, time)) 64 | 65 | 66 | def main(): 67 | print("Nodes: %d" % len(keys)) 68 | 69 | t = Timer("dict_build()", setup_dict) 70 | print_result(t.timeit(COUNT), 'dict build only') 71 | 72 | t = Timer("crb_build()", setup_FastRBTree) 73 | print_result(t.timeit(COUNT), 'FastRBTree build only') 74 | 75 | t = Timer("dict_build_delete()", setup_dict) 76 | print_result(t.timeit(COUNT), 'dict build & delete') 77 | 78 | t = Timer("crb_build_delete()", setup_FastRBTree) 79 | print_result(t.timeit(COUNT), 'FastRBTree build & delete') 80 | 81 | # shuffle search keys 82 | shuffle(keys) 83 | t = Timer("dict_search()", setup_dict) 84 | print_result(t.timeit(COUNT), 'dict search') 85 | 86 | t = Timer("crb_search()", setup_FastRBTree) 87 | print_result(t.timeit(COUNT), 'FastRBTree search') 88 | 89 | if __name__ == '__main__': 90 | if not has_fast_tree_support(): 91 | print("Cython extension for FastRBTree is NOT working.") 92 | else: 93 | print("Cython extension for FastRBTree is working.") 94 | main() -------------------------------------------------------------------------------- /profiling/profile_bintree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Purpose: profile BinaryTree, FastBinaryTree 5 | # Created: 01.05.2010 6 | # Copyright (c) 2010-2013 by Manfred Moitzi 7 | # License: MIT License 8 | 9 | from timeit import Timer 10 | from bintrees import BinaryTree 11 | from bintrees import FastBinaryTree, has_fast_tree_support 12 | from random import shuffle 13 | 14 | COUNT = 100 15 | KEYS = 5000 16 | KEYRANGE = 1000000 17 | 18 | setup_BinaryTree = """ 19 | from __main__ import bintree_build_delete, bintree_build, bintree_search, iterbintree 20 | """ 21 | setup_FastBinaryTree = """ 22 | from __main__ import cbintree_build_delete, cbintree_build, cbintree_search, itercbintree 23 | """ 24 | 25 | 26 | def random_keys(): 27 | import random 28 | return random.sample(range(KEYRANGE), KEYS) 29 | try: 30 | with open('testkeys.txt') as fp: 31 | keys = eval(fp.read()) 32 | except IOError: 33 | keys = random_keys() 34 | 35 | py_searchtree = BinaryTree.from_keys(keys) 36 | cy_searchtree = FastBinaryTree.from_keys(keys) 37 | 38 | 39 | def bintree_build_delete(): 40 | tree = BinaryTree.from_keys(keys) 41 | for key in keys: 42 | del tree[key] 43 | 44 | 45 | def cbintree_build_delete(): 46 | tree = FastBinaryTree.from_keys(keys) 47 | for key in keys: 48 | del tree[key] 49 | 50 | 51 | def bintree_build(): 52 | tree = BinaryTree.from_keys(keys) 53 | 54 | 55 | def cbintree_build(): 56 | tree = FastBinaryTree.from_keys(keys) 57 | 58 | 59 | def bintree_search(): 60 | for key in keys: 61 | obj = py_searchtree[key] 62 | 63 | 64 | def cbintree_search(): 65 | for key in keys: 66 | obj = cy_searchtree[key] 67 | 68 | 69 | def iterbintree(): 70 | items = list(py_searchtree.items()) 71 | 72 | 73 | def itercbintree(): 74 | items = list(cy_searchtree.items()) 75 | 76 | 77 | def print_result(time, text): 78 | print("Operation: %s takes %.2f seconds\n" % (text, time)) 79 | 80 | 81 | def main(): 82 | fp = open('testkeys.txt', 'w') 83 | fp.write(repr(keys)) 84 | fp.close() 85 | print ("Nodes: %d" % len(keys)) 86 | 87 | t = Timer("bintree_build()", setup_BinaryTree) 88 | print_result(t.timeit(COUNT), 'BinaryTree build only') 89 | 90 | t = Timer("cbintree_build()", setup_FastBinaryTree) 91 | print_result(t.timeit(COUNT), 'FastBinaryTree build only') 92 | 93 | t = Timer("bintree_build_delete()", setup_BinaryTree) 94 | print_result(t.timeit(COUNT), 'BinaryTree build & delete') 95 | 96 | t = Timer("cbintree_build_delete()", setup_FastBinaryTree) 97 | print_result(t.timeit(COUNT), 'FastBinaryTree build & delete') 98 | 99 | # shuffle search keys 100 | shuffle(keys) 101 | t = Timer("bintree_search()", setup_BinaryTree) 102 | print_result(t.timeit(COUNT), 'BinaryTree search') 103 | 104 | t = Timer("cbintree_search()", setup_FastBinaryTree) 105 | print_result(t.timeit(COUNT), 'FastBinaryTree search') 106 | 107 | t = Timer("iterbintree()", setup_BinaryTree) 108 | print_result(t.timeit(COUNT), 'BinaryTree iter all items') 109 | 110 | t = Timer("itercbintree()", setup_FastBinaryTree) 111 | print_result(t.timeit(COUNT), 'FastBinaryTree iter all items') 112 | 113 | if __name__ == '__main__': 114 | if not has_fast_tree_support(): 115 | print("Cython extension for FastBinaryTree is NOT working.") 116 | else: 117 | print("Cython extension for FastBinaryTree is working.") 118 | main() 119 | -------------------------------------------------------------------------------- /profiling/profile_itemslice.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Purpose: profile RBTree, FastRBTree 5 | # Created: 17.05.2013 6 | # Copyright (c) 2010-2013 by Manfred Moitzi 7 | # License: MIT License 8 | 9 | import sys 10 | from timeit import Timer 11 | 12 | from bintrees import RBTree as PTree 13 | from bintrees import FastRBTree as FTree, has_fast_tree_support 14 | 15 | COUNT = 100 16 | 17 | setup_RBTree_ps = """ 18 | from __main__ import rb_iter_by_prev_item, rb_iter_by_succ_item, rb_iter_by_item_slice_prev, rb_iter_by_item_slice_succ 19 | """ 20 | setup_FastRBTree_ps = """ 21 | from __main__ import crb_iter_by_prev_item, crb_iter_by_succ_item, crb_iter_by_item_slice_prev, crb_iter_by_item_slice_succ 22 | """ 23 | 24 | try: 25 | with open('testkeys.txt') as fp: 26 | keys = eval(fp.read()) 27 | except IOError: 28 | print("create 'testkeys.txt' with profile_bintree.py\n") 29 | sys.exit() 30 | 31 | ptree = PTree.from_keys(keys) 32 | ftree = FTree.from_keys(keys) 33 | sorted_keys = list(ftree.keys()) 34 | median_key = sorted_keys[int(len(sorted_keys) / 2)] 35 | 36 | 37 | def rb_iter_by_prev_item(): 38 | key = median_key 39 | while True: 40 | try: 41 | key, value = ptree.prev_item(key) 42 | except KeyError: 43 | break 44 | 45 | 46 | def rb_iter_by_succ_item(): 47 | key = median_key 48 | while True: 49 | try: 50 | key, value = ptree.succ_item(key) 51 | except KeyError: 52 | break 53 | 54 | 55 | def rb_iter_by_item_slice_prev(): 56 | for item in ptree.iter_items(None, median_key): 57 | pass 58 | 59 | 60 | def rb_iter_by_item_slice_succ(): 61 | for item in ptree.iter_items(median_key, None): 62 | pass 63 | 64 | 65 | def crb_iter_by_prev_item(): 66 | key = median_key 67 | while True: 68 | try: 69 | key, value = ftree.prev_item(key) 70 | except KeyError: 71 | break 72 | 73 | 74 | def crb_iter_by_succ_item(): 75 | key = median_key 76 | while True: 77 | try: 78 | key, value = ftree.succ_item(key) 79 | except KeyError: 80 | break 81 | 82 | 83 | def crb_iter_by_item_slice_prev(): 84 | for item in ftree.iter_items(None, median_key): 85 | pass 86 | 87 | 88 | def crb_iter_by_item_slice_succ(): 89 | for item in ftree.iter_items(median_key, None): 90 | pass 91 | 92 | 93 | def print_result(time, text): 94 | print("Operation: %s == [ %.2f s ]\n" % (text, time)) 95 | 96 | 97 | def main(): 98 | print ("Iterating {}x {} keys out of {} Nodes.\n".format(COUNT, len(keys)/2, len(keys))) 99 | 100 | t = Timer("rb_iter_by_prev_item()", setup_RBTree_ps) 101 | print_result(t.timeit(COUNT), 'RBTree iterating by k, v = prev_item(k)') 102 | 103 | t = Timer("rb_iter_by_succ_item()", setup_RBTree_ps) 104 | print_result(t.timeit(COUNT), 'RBTree iterating by k, v = succ_item(k)') 105 | 106 | t = Timer("rb_iter_by_item_slice_prev()", setup_RBTree_ps) 107 | print_result(t.timeit(COUNT), 'RBTree itemslice(None, median_key)') 108 | 109 | t = Timer("rb_iter_by_item_slice_succ()", setup_RBTree_ps) 110 | print_result(t.timeit(COUNT), 'RBTree itemslice(median_key: None)') 111 | 112 | t = Timer("crb_iter_by_prev_item()", setup_FastRBTree_ps) 113 | print_result(t.timeit(COUNT), 'FastRBTree iterating by k, v = prev_item(k)') 114 | 115 | t = Timer("crb_iter_by_succ_item()", setup_FastRBTree_ps) 116 | print_result(t.timeit(COUNT), 'FastRBTree iterating by k, v = succ_item(k)') 117 | 118 | t = Timer("crb_iter_by_item_slice_prev()", setup_FastRBTree_ps) 119 | print_result(t.timeit(COUNT), 'FastRBTree itemslice(None, median_key)') 120 | 121 | t = Timer("crb_iter_by_item_slice_succ()", setup_FastRBTree_ps) 122 | print_result(t.timeit(COUNT), 'FastRBTree itemslice(median_key, None)') 123 | 124 | if __name__ == '__main__': 125 | if not has_fast_tree_support(): 126 | print("Cython extension for FastRBTree is NOT working.") 127 | else: 128 | print("Cython extension for FastRBTree is working.") 129 | main() 130 | -------------------------------------------------------------------------------- /profiling/profile_min_max.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Purpose: profile RBTree, FastRBTree 5 | # Created: 02.05.2010 6 | # Copyright (c) 2010-2013 by Manfred Moitzi 7 | # License: MIT License 8 | 9 | import sys 10 | from timeit import Timer 11 | from random import shuffle 12 | 13 | from bintrees import RBTree 14 | from bintrees import FastRBTree, has_fast_tree_support 15 | 16 | COUNT = 100 17 | 18 | setup_RBTree = """ 19 | from __main__ import rb_pop_min, rb_pop_max 20 | """ 21 | setup_FastRBTree = """ 22 | from __main__ import keys, crb_pop_min, crb_pop_max 23 | """ 24 | 25 | try: 26 | fp = open('testkeys.txt') 27 | keys = eval(fp.read()) 28 | fp.close() 29 | bskeys = zip(keys, keys) 30 | except IOError: 31 | print("create 'testkeys.txt' with profile_bintree.py\n") 32 | sys.exit() 33 | 34 | 35 | def rb_pop_min(): 36 | tree = RBTree.fromkeys(keys) 37 | while tree.count: 38 | tree.pop_min() 39 | 40 | 41 | def rb_pop_max(): 42 | tree = RBTree.fromkeys(keys) 43 | while tree.count: 44 | tree.pop_max() 45 | 46 | 47 | def crb_pop_min(): 48 | tree = FastRBTree.fromkeys(keys) 49 | while tree.count: 50 | tree.pop_min() 51 | 52 | 53 | def crb_pop_max(): 54 | tree = FastRBTree.fromkeys(keys) 55 | while tree.count: 56 | tree.pop_max() 57 | 58 | 59 | def print_result(time, text): 60 | print("Operation: %s takes %.2f seconds\n" % (text, time)) 61 | 62 | 63 | def main(): 64 | fp = open('testkeys.txt', 'w') 65 | fp.write(repr(keys)) 66 | fp.close() 67 | print("Nodes: %d" % len(keys)) 68 | 69 | shuffle(keys) 70 | 71 | t = Timer("rb_pop_min()", setup_RBTree) 72 | print_result(t.timeit(COUNT), 'RBTree pop_min') 73 | 74 | t = Timer("rb_pop_max()", setup_RBTree) 75 | print_result(t.timeit(COUNT), 'RBTree pop_max') 76 | 77 | t = Timer("crb_pop_min()", setup_FastRBTree) 78 | print_result(t.timeit(COUNT), 'FastRBTree pop_min') 79 | 80 | t = Timer("crb_pop_max()", setup_FastRBTree) 81 | print_result(t.timeit(COUNT), 'FastRBTree pop_max') 82 | 83 | if __name__ == '__main__': 84 | if not has_fast_tree_support(): 85 | print("Cython extension for FastRBTree is NOT working.") 86 | else: 87 | print("Cython extension for FastRBTree is working.") 88 | main() 89 | -------------------------------------------------------------------------------- /profiling/profile_prev_succ.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Purpose: profile RBTree, FastRBTree 5 | # Created: 02.05.2010 6 | # Copyright (c) 2010-2013 by Manfred Moitzi 7 | # License: MIT License 8 | 9 | import sys 10 | from timeit import Timer 11 | from random import shuffle 12 | 13 | from bintrees import RBTree as PTree 14 | from bintrees import FastRBTree as FTree, has_fast_tree_support 15 | 16 | COUNT = 100 17 | 18 | setup_RBTree_ps = """ 19 | from __main__ import keys, rb_prev, rb_succ 20 | """ 21 | setup_FastRBTree_ps = """ 22 | from __main__ import keys, crb_prev, crb_succ 23 | """ 24 | 25 | try: 26 | fp = open('testkeys.txt') 27 | keys = eval(fp.read()) 28 | fp.close() 29 | bskeys = zip(keys, keys) 30 | except IOError: 31 | print("create 'testkeys.txt' with profile_bintree.py\n") 32 | sys.exit() 33 | 34 | ptree = PTree.from_keys(keys) 35 | ftree = FTree.from_keys(keys) 36 | 37 | 38 | def rb_prev(): 39 | for key in keys: 40 | try: 41 | item = ptree.prev_item(key) 42 | except KeyError: 43 | pass 44 | 45 | 46 | def rb_succ(): 47 | for key in keys: 48 | try: 49 | item = ptree.succ_item(key) 50 | except KeyError: 51 | pass 52 | 53 | 54 | def crb_prev(): 55 | for key in keys: 56 | try: 57 | item = ftree.prev_item(key) 58 | except KeyError: 59 | pass 60 | 61 | 62 | def crb_succ(): 63 | for key in keys: 64 | try: 65 | item = ftree.succ_item(key) 66 | except KeyError: 67 | pass 68 | 69 | 70 | def print_result(time, text): 71 | print("Operation: %s takes %.2f seconds\n" % (text, time)) 72 | 73 | 74 | def main(): 75 | fp = open('testkeys.txt', 'w') 76 | fp.write(repr(keys)) 77 | fp.close() 78 | print ("Nodes: %d" % len(keys)) 79 | 80 | shuffle(keys) 81 | 82 | t = Timer("rb_prev()", setup_RBTree_ps) 83 | print_result(t.timeit(COUNT), 'PythonTree prev') 84 | 85 | t = Timer("rb_succ()", setup_RBTree_ps) 86 | print_result(t.timeit(COUNT), 'PythonTree succ') 87 | 88 | t = Timer("crb_prev()", setup_FastRBTree_ps) 89 | print_result(t.timeit(COUNT), 'FastXTree prev') 90 | 91 | t = Timer("crb_succ()", setup_FastRBTree_ps) 92 | print_result(t.timeit(COUNT), 'FastXTree succ') 93 | 94 | 95 | if __name__ == '__main__': 96 | if not has_fast_tree_support(): 97 | print("Cython extension for FastRBTree is NOT working.") 98 | else: 99 | print("Cython extension for FastRBTree is working.") 100 | main() 101 | -------------------------------------------------------------------------------- /profiling/profile_pytrees.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Purpose: profile pure python trees, works also with ipy 5 | # Created: 01.05.2010 6 | # Copyright (c) 2010-2013 by Manfred Moitzi 7 | # License: MIT License 8 | 9 | from timeit import Timer 10 | from bintrees import BinaryTree 11 | from bintrees import AVLTree 12 | from bintrees import RBTree 13 | 14 | from random import shuffle 15 | 16 | COUNT = 100 17 | KEYS = 5000 18 | KEYRANGE = 1000000 19 | 20 | setup_BinaryTree_bd = """ 21 | from __main__ import keys, bintree_build_delete, BinaryTree 22 | """ 23 | setup_BinaryTree_b = """ 24 | from __main__ import keys, bintree_build, BinaryTree 25 | """ 26 | setup_BinaryTree_s = """ 27 | from __main__ import keys, bintree_search, py_searchtree 28 | """ 29 | setup_AVLTree_bd = """ 30 | from __main__ import keys, avl_build_delete, AVLTree 31 | """ 32 | setup_AVLTree_b = """ 33 | from __main__ import keys, avl_build, AVLTree 34 | """ 35 | setup_AVLTree_s = """ 36 | from __main__ import keys, avl_search, py_searchtree 37 | """ 38 | setup_RBTree_bd = """ 39 | from __main__ import keys, rb_build_delete, RBTree 40 | """ 41 | setup_RBTree_b = """ 42 | from __main__ import keys, rb_build, RBTree 43 | """ 44 | setup_RBTree_s = """ 45 | from __main__ import keys, rb_search, py_searchtree 46 | """ 47 | 48 | 49 | def random_keys(): 50 | from random import shuffle, randint 51 | keys = set() 52 | while len(keys) < KEYS: 53 | keys.add(randint(0, KEYRANGE)) 54 | keys = list(keys) 55 | shuffle(keys) 56 | return keys 57 | try: 58 | fp = open('testkeys.txt') 59 | keys = eval(fp.read()) 60 | fp.close() 61 | except IOError: 62 | keys = random_keys() 63 | 64 | py_searchtree = BinaryTree.from_keys(keys) 65 | 66 | 67 | def bintree_build_delete(): 68 | tree = BinaryTree.from_keys(keys) 69 | for key in keys: 70 | del tree[key] 71 | 72 | 73 | def bintree_build(): 74 | tree = BinaryTree.from_keys(keys) 75 | 76 | 77 | def bintree_search(): 78 | for key in keys: 79 | obj = py_searchtree[key] 80 | 81 | 82 | def avl_build_delete(): 83 | tree = AVLTree.from_keys(keys) 84 | for key in keys: 85 | del tree[key] 86 | 87 | 88 | def avl_build(): 89 | tree = AVLTree.from_keys(keys) 90 | 91 | 92 | def avl_search(): 93 | for key in keys: 94 | obj = py_searchtree[key] 95 | 96 | 97 | def rb_build_delete(): 98 | tree = RBTree.from_keys(keys) 99 | for key in keys: 100 | del tree[key] 101 | 102 | 103 | def rb_build(): 104 | tree = RBTree.from_keys(keys) 105 | 106 | 107 | def rb_search(): 108 | for key in keys: 109 | obj = py_searchtree[key] 110 | 111 | 112 | def print_result(time, text): 113 | print("Operation: %s takes %.2f seconds\n" % (text, time)) 114 | 115 | 116 | def main(): 117 | fp = open('testkeys.txt', 'w') 118 | fp.write(repr(keys)) 119 | fp.close() 120 | print ("Nodes: %d" % len(keys)) 121 | 122 | t = Timer("bintree_build()", setup_BinaryTree_b) 123 | print_result(t.timeit(COUNT), 'BinaryTree build only') 124 | 125 | t = Timer("avl_build()", setup_AVLTree_b) 126 | print_result(t.timeit(COUNT), 'AVLTree build only') 127 | 128 | t = Timer("rb_build()", setup_RBTree_b) 129 | print_result(t.timeit(COUNT), 'RBTree build only') 130 | 131 | t = Timer("bintree_build_delete()", setup_BinaryTree_bd) 132 | print_result(t.timeit(COUNT), 'BinaryTree build & delete') 133 | 134 | t = Timer("avl_build_delete()", setup_AVLTree_bd) 135 | print_result(t.timeit(COUNT), 'AVLTree build & delete') 136 | 137 | t = Timer("rb_build_delete()", setup_RBTree_bd) 138 | print_result(t.timeit(COUNT), 'RBTree build & delete') 139 | 140 | shuffle(keys) 141 | 142 | t = Timer("bintree_search()", setup_BinaryTree_s) 143 | print_result(t.timeit(COUNT), 'BinaryTree search') 144 | 145 | t = Timer("avl_search()", setup_AVLTree_s) 146 | print_result(t.timeit(COUNT), 'AVLTree search') 147 | 148 | t = Timer("rb_search()", setup_RBTree_s) 149 | print_result(t.timeit(COUNT), 'RBTree search') 150 | 151 | if __name__ == '__main__': 152 | main() -------------------------------------------------------------------------------- /profiling/profile_rbtree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Purpose: profile RBTree, FastRBTree 5 | # Created: 02.05.2010 6 | # Copyright (c) 2010-2013 by Manfred Moitzi 7 | # License: MIT License 8 | 9 | import sys 10 | from timeit import Timer 11 | from random import shuffle 12 | 13 | from bintrees import RBTree 14 | from bintrees import FastRBTree, has_fast_tree_support 15 | 16 | COUNT = 100 17 | 18 | setup_RBTree = """ 19 | from __main__ import rb_build_delete, rb_build, rb_search 20 | """ 21 | setup_FastRBTree = """ 22 | from __main__ import crb_build_delete, crb_build, crb_search 23 | """ 24 | 25 | try: 26 | fp = open('testkeys.txt') 27 | keys = eval(fp.read()) 28 | fp.close() 29 | bskeys = zip(keys, keys) 30 | except IOError: 31 | print("create 'testkeys.txt' with profile_bintree.py\n") 32 | sys.exit() 33 | 34 | py_searchtree = RBTree.from_keys(keys) 35 | cy_searchtree = FastRBTree.from_keys(keys) 36 | 37 | 38 | def rb_build_delete(): 39 | tree = RBTree.from_keys(keys) 40 | for key in keys: 41 | del tree[key] 42 | 43 | 44 | def crb_build_delete(): 45 | tree = FastRBTree.from_keys(keys) 46 | for key in keys: 47 | del tree[key] 48 | 49 | 50 | def rb_build(): 51 | tree = RBTree.from_keys(keys) 52 | 53 | 54 | def crb_build(): 55 | tree = FastRBTree.from_keys(keys) 56 | 57 | 58 | def rb_search(): 59 | for key in keys: 60 | obj = py_searchtree[key] 61 | 62 | 63 | def crb_search(): 64 | for key in keys: 65 | obj = cy_searchtree[key] 66 | 67 | 68 | def print_result(time, text): 69 | print("Operation: %s takes %.2f seconds\n" % (text, time)) 70 | 71 | 72 | def main(): 73 | fp = open('testkeys.txt', 'w') 74 | fp.write(repr(keys)) 75 | fp.close() 76 | print ("Nodes: %d" % len(keys)) 77 | 78 | t = Timer("rb_build()", setup_RBTree) 79 | print_result(t.timeit(COUNT), 'RBTree build only') 80 | 81 | t = Timer("crb_build()", setup_FastRBTree) 82 | print_result(t.timeit(COUNT), 'FastRBTree build only') 83 | 84 | t = Timer("rb_build_delete()", setup_RBTree) 85 | print_result(t.timeit(COUNT), 'RBTree build & delete') 86 | 87 | t = Timer("crb_build_delete()", setup_FastRBTree) 88 | print_result(t.timeit(COUNT), 'FastRBTree build & delete') 89 | 90 | # shuffle search keys 91 | shuffle(keys) 92 | t = Timer("rb_search()", setup_RBTree) 93 | print_result(t.timeit(COUNT), 'RBTree search') 94 | 95 | t = Timer("crb_search()", setup_FastRBTree) 96 | print_result(t.timeit(COUNT), 'FastRBTree search') 97 | 98 | if __name__ == '__main__': 99 | if not has_fast_tree_support(): 100 | print("Cython extension for FastRBTree is NOT working.") 101 | else: 102 | print("Cython extension for FastRBTree is working.") 103 | main() 104 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [build_ext] 2 | 3 | [bdist_wheel] 4 | universal = 0 5 | 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Author: mozman 3 | # Copyright (c) 2010-2020 by Manfred Moitzi 4 | # License: MIT License 5 | 6 | # `python setup.py install` should build the C extension if you have installed Cython 7 | 8 | import os 9 | from setuptools import setup 10 | from setuptools import Extension 11 | 12 | try: 13 | from Cython.Distutils import build_ext 14 | ext_modules = [Extension("bintrees.cython_trees", ["bintrees/ctrees.c", "bintrees/cython_trees.pyx"]), 15 | ] 16 | commands = {'build_ext': build_ext} 17 | except ImportError: 18 | ext_modules = [] 19 | commands = {} 20 | 21 | 22 | def read(fname): 23 | with open(os.path.join(os.path.dirname(__file__), fname)) as f: 24 | return f.read() 25 | 26 | setup( 27 | name='bintrees', 28 | version='2.2.0', 29 | description='Package provides Binary-, RedBlack- and AVL-Trees in Python and Cython.', 30 | author='mozman', 31 | url='https://github.com/mozman/bintrees.git', 32 | download_url='https://github.com/mozman/bintrees/releases', 33 | author_email='me@mozman.at', 34 | python_requires='>=3.6', 35 | cmdclass=commands, 36 | ext_modules=ext_modules, 37 | packages=['bintrees'], 38 | long_description=read('README.rst')+read('NEWS.rst'), 39 | platforms="OS Independent", 40 | license="MIT License", 41 | classifiers=[ 42 | "Development Status :: 7 - Inactive", 43 | "License :: OSI Approved :: MIT License", 44 | "Operating System :: OS Independent", 45 | "Programming Language :: Python :: 3", 46 | "Programming Language :: Python :: 3.6", 47 | "Programming Language :: Python :: 3.7", 48 | "Programming Language :: Python :: 3.8", 49 | "Programming Language :: Python :: 3.9", 50 | "Programming Language :: Cython", 51 | "Programming Language :: Python :: Implementation :: CPython", 52 | "Programming Language :: Python :: Implementation :: PyPy", 53 | "Intended Audience :: Developers", 54 | "Topic :: Software Development :: Libraries :: Python Modules", 55 | ], 56 | ) 57 | -------------------------------------------------------------------------------- /testresults.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozman/bintrees/35c948f95e8b45be2f14f34c6270016dee0def38/testresults.ods -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozman/bintrees/35c948f95e8b45be2f14f34c6270016dee0def38/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_all_trees.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Purpose: test binary trees 5 | # Created: 28.04.2010 6 | # Copyright (c) 2010-2017 by Manfred Moitzi 7 | # License: MIT License 8 | import sys 9 | PYPY = hasattr(sys, 'pypy_version_info') 10 | 11 | import unittest 12 | import pickle 13 | from copy import deepcopy 14 | from random import randint, shuffle 15 | 16 | from bintrees import BinaryTree, AVLTree, RBTree, has_fast_tree_support 17 | from bintrees import FastBinaryTree, FastAVLTree, FastRBTree 18 | 19 | set3 = [34, 67, 89, 123, 3, 7, 9, 2, 0, 999] 20 | 21 | 22 | def check_integrity(keys, remove_key, tree): 23 | for search_key in keys: 24 | if search_key == remove_key: 25 | if search_key in tree: 26 | return False 27 | else: 28 | if search_key not in tree: 29 | return False 30 | return True 31 | 32 | 33 | def randomkeys(num, maxnum=100000): 34 | keys = set([randint(0, maxnum) for _ in range(num)]) 35 | while len(keys) != num: 36 | keys.add(randint(0, maxnum)) 37 | return list(keys) 38 | 39 | 40 | class TestCythonSupport(unittest.TestCase): 41 | def test_cython_support(self): 42 | if PYPY: 43 | self.assertFalse(has_fast_tree_support()) 44 | else: 45 | self.assertTrue(has_fast_tree_support()) 46 | 47 | 48 | class CheckTree(object): 49 | default_values1 = list(zip([12, 34, 45, 16, 35, 57], [12, 34, 45, 16, 35, 57])) 50 | default_values2 = [(2, 12), (4, 34), (8, 45), (1, 16), (9, 35), (3, 57)] 51 | slicetest_data = [(1, 1), (2, 2), (3, 3), (4, 4), (8, 8), (9, 9), (10, 10), (11, 11)] 52 | 53 | def test_001_init(self): 54 | tree = self.TREE_CLASS() 55 | self.assertEqual(len(tree), 0) 56 | tree.update(self.default_values1) 57 | self.assertEqual(len(tree), 6) 58 | 59 | def test_002_init_with_dict(self): 60 | tree = self.TREE_CLASS(dict(self.default_values1)) 61 | self.assertEqual(len(tree), 6) 62 | 63 | def test_000_init_with_seq(self): 64 | tree = self.TREE_CLASS(self.default_values1) 65 | self.assertEqual(len(tree), 6) 66 | 67 | def test_004_init_with_tree(self): 68 | tree1 = self.TREE_CLASS(self.default_values1) 69 | tree2 = self.TREE_CLASS(tree1) 70 | self.assertEqual(len(tree2), 6) 71 | 72 | def test_005_iter_empty_tree(self): 73 | tree = self.TREE_CLASS() 74 | self.assertEqual([], list(tree)) 75 | 76 | def test_006_copy(self): 77 | tree1 = self.TREE_CLASS(self.default_values1) 78 | tree2 = tree1.copy() 79 | self.assertEqual(list(tree1.items()), list(tree2.items())) 80 | 81 | def test_007_to_dict(self): 82 | tree = self.TREE_CLASS(self.default_values2) 83 | d = dict(tree) 84 | self.assertEqual(d, dict(self.default_values2)) 85 | 86 | def test_008a_repr(self): 87 | tree = self.TREE_CLASS(self.default_values2) 88 | clsname = tree.__class__.__name__ 89 | reprstr = repr(tree) 90 | self.assertEqual(reprstr, '%s({1: 16, 2: 12, 3: 57, 4: 34, 8: 45, 9: 35})' % clsname) 91 | 92 | def test_008b_repr_empty_tree(self): 93 | tree = self.TREE_CLASS() 94 | self.assertEqual(repr(tree), tree.__class__.__name__ + '({})') 95 | 96 | def test_009_clear(self): 97 | tree = self.TREE_CLASS(self.default_values2) 98 | tree.clear() 99 | self.assertEqual(len(tree), 0) 100 | 101 | def test_010_contains(self): 102 | tree1 = self.TREE_CLASS(self.default_values2) 103 | tree2 = self.TREE_CLASS(self.default_values1) 104 | for key in tree1.keys(): 105 | self.assertFalse(key in tree2) 106 | for key in tree2.keys(): 107 | self.assertTrue(key in tree2) 108 | 109 | def test_011_is_empty(self): 110 | tree = self.TREE_CLASS() 111 | self.assertTrue(tree.is_empty()) 112 | tree[0] = 1 113 | self.assertFalse(tree.is_empty()) 114 | 115 | def test_012_update_1(self): 116 | tree = self.TREE_CLASS() 117 | tree.update({1: 'one', 2: 'two'}) 118 | tree.update([(3, 'three'), (2, 'zwei')]) 119 | self.assertEqual(list(tree.keys()), [1, 2, 3]) 120 | self.assertEqual(list(tree.values()), ['one', 'zwei', 'three']) 121 | 122 | def test_013_update_2(self): 123 | tree = self.TREE_CLASS() 124 | tree.update({1: 'one', 2: 'two'}, [(3, 'three'), (2, 'zwei')]) 125 | self.assertEqual(list(tree.keys()), [1, 2, 3]) 126 | self.assertEqual(list(tree.values()), ['one', 'zwei', 'three']) 127 | 128 | def test_014_unique_keys(self): 129 | tree = self.TREE_CLASS() 130 | for value in range(5): 131 | tree[0] = value 132 | self.assertEqual(tree[0], 4) 133 | self.assertEqual(len(tree), 1) 134 | 135 | def test_015_getitem(self): 136 | tree = self.TREE_CLASS(self.default_values1) # key == value 137 | for key in [12, 34, 45, 16, 35, 57]: 138 | self.assertEqual(key, tree[key]) 139 | 140 | def test_016_setitem(self): 141 | tree = self.TREE_CLASS() 142 | for key in [12, 34, 45, 16, 35, 57]: 143 | tree[key] = key 144 | for key in [12, 34, 45, 16, 35, 57]: 145 | self.assertEqual(key, tree[key]) 146 | 147 | def test_017_setdefault(self): 148 | tree = self.TREE_CLASS(self.default_values2) 149 | value = tree.setdefault(2, 17) # key <2> exists and == 12 150 | self.assertEqual(value, 12) 151 | value = tree.setdefault(99, 77) 152 | self.assertEqual(value, 77) 153 | 154 | def test_018_keys(self): 155 | tree = self.TREE_CLASS(self.default_values1) 156 | result = list(iter(tree)) 157 | self.assertEqual(result, [12, 16, 34, 35, 45, 57]) 158 | self.assertEqual(result, list(tree.keys())) 159 | 160 | def test_018a_keyslice(self): 161 | tree = self.TREE_CLASS(self.default_values1) 162 | result = list(tree.key_slice(15, 36)) 163 | self.assertEqual(result, [16, 34, 35]) 164 | 165 | def test_018b_keyslice(self): 166 | tree = self.TREE_CLASS(self.default_values1) 167 | result = list(tree.key_slice(15, 35)) 168 | self.assertEqual(result, [16, 34]) 169 | 170 | def test_018c_keyslice_reverse(self): 171 | tree = self.TREE_CLASS(self.default_values1) 172 | result = list(tree.key_slice(15, 36, reverse=True)) 173 | self.assertEqual(list(result), [35, 34, 16]) 174 | 175 | def test_018d_slice_from_start(self): 176 | # values: 1, 2, 3, 4, 8, 9, 10, 11 177 | tree = self.TREE_CLASS(self.slicetest_data) 178 | result = list(tree[:4]) 179 | self.assertEqual(list(result), [1, 2, 3]) 180 | 181 | def test_018e_slice_til_end(self): 182 | # values: 1, 2, 3, 4, 8, 9, 10, 11 183 | tree = self.TREE_CLASS(self.slicetest_data) 184 | result = list(tree[8:]) 185 | self.assertEqual(list(result), [8, 9, 10, 11]) 186 | 187 | def test_018f_slice_from_start_til_end(self): 188 | # values: 1, 2, 3, 4, 8, 9, 10, 11 189 | tree = self.TREE_CLASS(self.slicetest_data) 190 | result = list(tree[:]) 191 | self.assertEqual(list(result), [1, 2, 3, 4, 8, 9, 10, 11]) 192 | 193 | def test_018g_slice_produces_keys(self): 194 | tree = self.TREE_CLASS([(1, 100), (2, 200), (3, 300)]) 195 | result = list(tree[:]) 196 | self.assertEqual(list(result), [1, 2, 3]) 197 | 198 | def test_019_values(self): 199 | tree = self.TREE_CLASS(self.default_values1) 200 | result = list(tree.values()) 201 | self.assertEqual(result, [12, 16, 34, 35, 45, 57]) 202 | self.assertEqual(result, list(tree.values())) 203 | 204 | def test_020_items(self): 205 | tree = self.TREE_CLASS(self.default_values1) 206 | result = list(tree.items()) 207 | self.assertEqual(result, list(sorted(self.default_values1))) 208 | self.assertEqual(result, list(tree.items())) 209 | 210 | def test_020a_items_of_empty_tree(self): 211 | tree = self.TREE_CLASS() 212 | # empty tree also has to return an iterable 213 | result = [item for item in tree.items()] 214 | self.assertEqual(0, len(result)) 215 | 216 | def test_021_keys_reverse(self): 217 | tree = self.TREE_CLASS(self.default_values1) 218 | result = list(tree.keys(reverse=True)) 219 | self.assertEqual(result, list(reversed([12, 16, 34, 35, 45, 57]))) 220 | self.assertEqual(result, list(tree.keys(reverse=True))) 221 | 222 | def test_022_values_reverse(self): 223 | tree = self.TREE_CLASS(self.default_values1) 224 | result = list(tree.values(reverse=True)) 225 | self.assertEqual(result, list(reversed([12, 16, 34, 35, 45, 57]))) 226 | self.assertEqual(result, list(tree.values(reverse=True))) 227 | 228 | def test_023_items_reverse(self): 229 | tree = self.TREE_CLASS(self.default_values1) 230 | result = list(tree.items(reverse=True)) 231 | self.assertEqual(result, list(tree.items(reverse=True))) 232 | 233 | def test_024_get(self): 234 | tree = self.TREE_CLASS(self.default_values1) 235 | self.assertEqual(tree.get(34), 34) 236 | self.assertEqual(tree.get(99), None) 237 | 238 | def test_025_get_default(self): 239 | tree = self.TREE_CLASS(self.default_values1) 240 | self.assertEqual(tree.get(99, -10), -10) # get default value 241 | self.assertEqual(tree.get(34, -10), 34) # key exist 242 | self.assertEqual(tree.get(7, "DEFAULT"), "DEFAULT") 243 | 244 | def test_026_remove_child_1(self): 245 | keys = [50, 25] 246 | tree = self.TREE_CLASS.fromkeys(keys) 247 | remove_key = 25 248 | del tree[remove_key] 249 | self.assertTrue(check_integrity(keys, remove_key, tree)) 250 | 251 | def test_027_remove_child_2(self): 252 | keys = [50, 25, 12] 253 | tree = self.TREE_CLASS.fromkeys(keys) 254 | remove_key = 25 255 | del tree[remove_key] 256 | self.assertTrue(check_integrity(keys, remove_key, tree)) 257 | 258 | def test_028_remove_child_3(self): 259 | keys = [50, 25, 12, 33] 260 | tree = self.TREE_CLASS.fromkeys(keys) 261 | remove_key = 25 262 | del tree[remove_key] 263 | self.assertTrue(check_integrity(keys, remove_key, tree)) 264 | 265 | def test_029_remove_child_4(self): 266 | keys = [50, 25, 12, 33, 40] 267 | tree = self.TREE_CLASS.fromkeys(keys) 268 | remove_key = 25 269 | del tree[remove_key] 270 | self.assertTrue(check_integrity(keys, remove_key, tree)) 271 | 272 | def test_030_remove_child_5(self): 273 | keys = [50, 25, 12, 33, 40, 37, 43] 274 | tree = self.TREE_CLASS.fromkeys(keys) 275 | remove_key = 25 276 | del tree[remove_key] 277 | self.assertTrue(check_integrity(keys, remove_key, tree)) 278 | 279 | def test_031_remove_child_6(self): 280 | keys = [50, 75, 100, 150, 60, 65, 64, 80, 66] 281 | tree = self.TREE_CLASS.fromkeys(keys) 282 | remove_key = 75 283 | del tree[remove_key] 284 | self.assertTrue(check_integrity(keys, remove_key, tree)) 285 | 286 | def test_032_remove_root_1(self): 287 | keys = [50, ] 288 | tree = self.TREE_CLASS.fromkeys(keys) 289 | del tree[50] 290 | self.assertTrue(tree.is_empty) 291 | 292 | def test_033_remove_root_2(self): 293 | keys = [50, 25, 12, 33, 34] 294 | tree = self.TREE_CLASS.fromkeys(keys) 295 | remove_key = 50 296 | del tree[remove_key] 297 | self.assertTrue(check_integrity(keys, remove_key, tree)) 298 | 299 | def test_034_remove_root_3(self): 300 | keys = [50, 25, 12, 33, 34, 75] 301 | tree = self.TREE_CLASS.fromkeys(keys) 302 | remove_key = 50 303 | del tree[remove_key] 304 | self.assertTrue(check_integrity(keys, remove_key, tree)) 305 | 306 | def test_035_remove_root_4(self): 307 | keys = [50, 25, 12, 33, 34, 75, 60] 308 | tree = self.TREE_CLASS.fromkeys(keys) 309 | remove_key = 50 310 | del tree[remove_key] 311 | self.assertTrue(check_integrity(keys, remove_key, tree)) 312 | 313 | def test_036_remove_root_5(self): 314 | keys = [50, 25, 12, 33, 34, 75, 60, 61] 315 | tree = self.TREE_CLASS.fromkeys(keys) 316 | remove_key = 50 317 | del tree[remove_key] 318 | self.assertTrue(check_integrity(keys, remove_key, tree)) 319 | 320 | def test_037a_discard(self): 321 | keys = [50, 25, 12, 33, 34, 75, 60, 61] 322 | tree = self.TREE_CLASS.fromkeys(keys) 323 | try: 324 | tree.discard(17) 325 | except KeyError: 326 | self.assertTrue(False, "Discard raises KeyError") 327 | 328 | def test_037b_remove_keyerror(self): 329 | keys = [50, 25, 12, 33, 34, 75, 60, 61] 330 | tree = self.TREE_CLASS.fromkeys(keys) 331 | self.assertRaises(KeyError, tree.remove, 17) 332 | 333 | def test_038_remove_shuffeld(self): 334 | keys = [50, 25, 20, 35, 22, 23, 27, 75, 65, 90, 60, 70, 85, 57, 83, 58] 335 | remove_keys = keys[:] 336 | shuffle(remove_keys) 337 | for remove_key in remove_keys: 338 | tree = self.TREE_CLASS.fromkeys(keys) 339 | del tree[remove_key] 340 | self.assertTrue(check_integrity(keys, remove_key, tree)) 341 | 342 | def test_039_remove_random_numbers(self): 343 | try: 344 | fp = open('xtestkey.txt') 345 | keys = eval(fp.read()) 346 | fp.close() 347 | except IOError: 348 | keys = randomkeys(1000) 349 | shuffle(keys) 350 | tree = self.TREE_CLASS.fromkeys(keys) 351 | self.assertEqual(len(tree), len(keys)) 352 | for key in keys: 353 | del tree[key] 354 | self.assertEqual(len(tree), 0) 355 | 356 | def test_040_sort_order(self): 357 | keys = randomkeys(1000) 358 | tree = self.TREE_CLASS.fromkeys(keys) 359 | generator = iter(tree) 360 | a = next(generator) 361 | for b in generator: 362 | self.assertTrue(b > a) 363 | a = b 364 | 365 | def test_041_pop(self): 366 | tree = self.TREE_CLASS(self.default_values2) 367 | data = tree.pop(8) 368 | self.assertEqual(data, 45) 369 | self.assertFalse(8 in tree) 370 | self.assertRaises(KeyError, tree.pop, 8) 371 | self.assertEqual(tree.pop(8, 99), 99) 372 | 373 | def test_042_pop_item(self): 374 | tree = self.TREE_CLASS(self.default_values2) 375 | d = dict() 376 | while not tree.is_empty(): 377 | key, value = tree.pop_item() 378 | d[key] = value 379 | expected = {2: 12, 4: 34, 8: 45, 1: 16, 9: 35, 3: 57} 380 | self.assertEqual(expected, d) 381 | self.assertRaises(KeyError, tree.pop_item) 382 | 383 | def test_043_min_item(self): 384 | tree = self.TREE_CLASS(zip(set3, set3)) 385 | min_item = tree.min_item() 386 | self.assertEqual(min_item[1], 0) 387 | 388 | def test_044_min_item_error(self): 389 | tree = self.TREE_CLASS() 390 | self.assertRaises(ValueError, tree.min_item) 391 | 392 | def test_045_max_item(self): 393 | tree = self.TREE_CLASS(zip(set3, set3)) 394 | max_item = tree.max_item() 395 | self.assertEqual(max_item[1], 999) 396 | 397 | def test_046_max_item_error(self): 398 | tree = self.TREE_CLASS() 399 | self.assertRaises(ValueError, tree.max_item) 400 | 401 | def test_047_min_key(self): 402 | tree = self.TREE_CLASS(zip(set3, set3)) 403 | minkey = tree.min_key() 404 | self.assertEqual(minkey, 0) 405 | self.assertEqual(minkey, min(tree)) 406 | 407 | def test_048_min_key_error(self): 408 | tree = self.TREE_CLASS() 409 | self.assertRaises(ValueError, tree.min_key) 410 | 411 | def test_049_max_key(self): 412 | tree = self.TREE_CLASS(zip(set3, set3)) 413 | maxkey = tree.max_key() 414 | self.assertEqual(maxkey, 999) 415 | self.assertEqual(maxkey, max(tree)) 416 | 417 | def test_050_min_key_error(self): 418 | tree = self.TREE_CLASS() 419 | self.assertRaises(ValueError, tree.max_key) 420 | 421 | def test_051_prev_item(self): 422 | tree = self.TREE_CLASS(zip(set3, set3)) 423 | prev_value = None 424 | for key in tree.keys(): 425 | try: 426 | prev_item = tree.prev_item(key) 427 | except KeyError: # only on first key 428 | self.assertEqual(prev_value, None) 429 | if prev_value is not None: 430 | self.assertEqual(prev_value, prev_item[1]) 431 | prev_value = key 432 | 433 | def test_052_prev_key_extreme(self): 434 | # extreme degenerated binary tree (if unbalanced) 435 | tree = self.TREE_CLASS.fromkeys([1, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2]) 436 | self.assertEqual(tree.prev_key(2), 1) 437 | 438 | def test_053_prev_item_error(self): 439 | tree = self.TREE_CLASS() 440 | tree[0] = 'NULL' 441 | self.assertRaises(KeyError, tree.prev_item, 0) 442 | 443 | def test_054_succ_item(self): 444 | tree = self.TREE_CLASS(zip(set3, set3)) 445 | succ_value = None 446 | for key in tree.keys(reverse=True): 447 | try: 448 | succ_item = tree.succ_item(key) 449 | except KeyError: # only on last key 450 | self.assertEqual(succ_value, None) 451 | if succ_value is not None: 452 | self.assertEqual(succ_value, succ_item[1]) 453 | succ_value = key 454 | 455 | def test_055_succ_key_extreme(self): 456 | # extreme degenerated binary tree (if unbalanced) 457 | tree = self.TREE_CLASS.fromkeys([15, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 458 | self.assertEqual(tree.succ_key(10), 15) 459 | 460 | def test_056_succ_item_error(self): 461 | tree = self.TREE_CLASS() 462 | tree[0] = 'NULL' 463 | self.assertRaises(KeyError, tree.succ_item, 0) 464 | 465 | def test_057_prev_key(self): 466 | tree = self.TREE_CLASS(zip(set3, set3)) 467 | pkey = None 468 | for key in tree.keys(): 469 | try: 470 | prev_key = tree.prev_key(key) 471 | except KeyError: # only on first key 472 | self.assertEqual(pkey, None) 473 | if pkey is not None: 474 | self.assertEqual(pkey, prev_key) 475 | pkey = key 476 | 477 | def test_058_prev_key_error(self): 478 | tree = self.TREE_CLASS() 479 | tree[0] = 'NULL' 480 | self.assertRaises(KeyError, tree.prev_key, 0) 481 | 482 | def test_059_succ_key(self): 483 | tree = self.TREE_CLASS(zip(set3, set3)) 484 | skey = None 485 | for key in tree.keys(reverse=True): 486 | try: 487 | succ_key = tree.succ_key(key) 488 | except KeyError: # only on last key 489 | self.assertEqual(skey, None) 490 | if skey is not None: 491 | self.assertEqual(skey, succ_key) 492 | skey = key 493 | 494 | def test_060_succ_key_error(self): 495 | tree = self.TREE_CLASS() 496 | tree[0] = 'NULL' 497 | self.assertRaises(KeyError, tree.succ_key, 0) 498 | 499 | def test_061_prev_succ_on_empty_trees(self): 500 | tree = self.TREE_CLASS() 501 | self.assertRaises(KeyError, tree.succ_key, 0) 502 | self.assertRaises(KeyError, tree.prev_key, 0) 503 | self.assertRaises(KeyError, tree.succ_item, 0) 504 | self.assertRaises(KeyError, tree.prev_item, 0) 505 | 506 | def test_062_succ_prev_key_random_1000(self): 507 | keys = list(set([randint(0, 10000) for _ in range(1000)])) 508 | shuffle(keys) 509 | tree = self.TREE_CLASS.fromkeys(keys) 510 | 511 | skey = None 512 | for key in tree.keys(reverse=True): 513 | try: 514 | succ_key = tree.succ_key(key) 515 | except KeyError: # only on last key 516 | self.assertEqual(skey, None) 517 | if skey is not None: 518 | self.assertEqual(skey, succ_key) 519 | skey = key 520 | 521 | pkey = None 522 | for key in tree.keys(): 523 | try: 524 | prev_key = tree.prev_key(key) 525 | except KeyError: # only on first key 526 | self.assertEqual(pkey, None) 527 | if pkey is not None: 528 | self.assertEqual(pkey, prev_key) 529 | pkey = key 530 | 531 | def test_063_pop_min(self): 532 | tree = self.TREE_CLASS(zip(set3, set3)) 533 | keys = sorted(set3[:]) 534 | for key in keys: 535 | k, v = tree.pop_min() 536 | self.assertEqual(key, v) 537 | 538 | def test_064_pop_min_error(self): 539 | tree = self.TREE_CLASS() 540 | self.assertRaises(ValueError, tree.pop_min) 541 | 542 | def test_065_pop_max(self): 543 | tree = self.TREE_CLASS(zip(set3, set3)) 544 | keys = sorted(set3[:], reverse=True) 545 | for key in keys: 546 | k, v = tree.pop_max() 547 | self.assertEqual(key, v) 548 | 549 | def test_066_pop_max_error(self): 550 | tree = self.TREE_CLASS() 551 | self.assertRaises(ValueError, tree.pop_max) 552 | 553 | def test_067_nlargest(self): 554 | l = list(range(30)) 555 | shuffle(l) 556 | tree = self.TREE_CLASS(zip(l, l)) 557 | result = tree.nlargest(10) 558 | chk = [(x, x) for x in range(29, 19, -1)] 559 | self.assertEqual(chk, result) 560 | 561 | def test_068_nlargest_gt_len(self): 562 | items = list(zip(range(5), range(5))) 563 | tree = self.TREE_CLASS(items) 564 | result = tree.nlargest(10) 565 | self.assertEqual(result, list(reversed(items))) 566 | 567 | def test_069_nsmallest(self): 568 | l = list(range(30)) 569 | shuffle(l) 570 | tree = self.TREE_CLASS(zip(l, l)) 571 | result = tree.nsmallest(10) 572 | chk = [(x, x) for x in range(0, 10)] 573 | self.assertEqual(chk, result) 574 | 575 | def test_070_nsmallest_gt_len(self): 576 | items = list(zip(range(5), range(5))) 577 | tree = self.TREE_CLASS(items) 578 | result = tree.nsmallest(10) 579 | self.assertEqual(result, items) 580 | 581 | def test_071_reversed(self): 582 | tree = self.TREE_CLASS(zip(set3, set3)) 583 | result = reversed(sorted(set3)) 584 | for key, chk in zip(reversed(tree), result): 585 | self.assertEqual(chk, key) 586 | 587 | def test_077_delslice(self): 588 | T = self.TREE_CLASS.fromkeys([1, 2, 3, 4, 8, 9]) 589 | tree = T.copy() 590 | del tree[:2] 591 | self.assertEqual(list(tree.keys()), [2, 3, 4, 8, 9]) 592 | tree = T.copy() 593 | del tree[1:3] 594 | self.assertEqual(list(tree.keys()), [3, 4, 8, 9]) 595 | tree = T.copy() 596 | del tree[3:] 597 | self.assertEqual(list(tree.keys()), [1, 2]) 598 | tree = T.copy() 599 | del tree[:] 600 | self.assertEqual(list(tree.keys()), []) 601 | 602 | def test_080_intersection(self): 603 | l1 = list(range(30)) 604 | shuffle(l1) 605 | l2 = list(range(15, 45)) 606 | shuffle(l2) 607 | tree1 = self.TREE_CLASS(zip(l1, l1)) 608 | tree2 = self.TREE_CLASS(zip(l2, l2)) 609 | i = tree1 & tree2 610 | self.assertEqual(len(i), 15) 611 | self.assertEqual(i.min_key(), 15) 612 | self.assertEqual(i.max_key(), 29) 613 | 614 | def test_081_union_keys(self): 615 | l1 = list(range(30)) 616 | shuffle(l1) 617 | l2 = list(range(15, 45)) 618 | shuffle(l2) 619 | tree1 = self.TREE_CLASS(zip(l1, l1)) 620 | tree2 = self.TREE_CLASS(zip(l2, l2)) 621 | i = tree1 | tree2 622 | self.assertEqual(len(i), 45) 623 | self.assertEqual(i.min_key(), 0) 624 | self.assertEqual(i.max_key(), 44) 625 | 626 | def test_081_union_values(self): 627 | l1 = list(range(30)) 628 | shuffle(l1) 629 | l2 = list(range(15, 45)) 630 | shuffle(l2) 631 | tree1 = self.TREE_CLASS(zip(l1, l1)) 632 | tree2 = self.TREE_CLASS(zip(l2, l2)) 633 | union_tree = tree1 | tree2 634 | self.assertEqual(union_tree[44], 44) 635 | self.assertEqual(union_tree[1], 1) 636 | 637 | def test_082_difference(self): 638 | l1 = list(range(30)) 639 | shuffle(l1) 640 | l2 = list(range(15, 45)) 641 | shuffle(l2) 642 | 643 | tree1 = self.TREE_CLASS(zip(l1, l1)) 644 | tree2 = self.TREE_CLASS(zip(l2, l2)) 645 | i = tree1 - tree2 646 | self.assertEqual(len(i), 15) 647 | self.assertEqual(i.min_key(), 0) 648 | self.assertEqual(i.max_key(), 14) 649 | 650 | def test_083_symmetric_difference_keys(self): 651 | l1 = list(range(30)) 652 | shuffle(l1) 653 | l2 = list(range(15, 45)) 654 | shuffle(l2) 655 | 656 | tree1 = self.TREE_CLASS(zip(l1, l1)) 657 | tree2 = self.TREE_CLASS(zip(l2, l2)) 658 | new_tree = tree1 ^ tree2 659 | self.assertEqual(len(new_tree), 30) 660 | self.assertEqual(new_tree.min_key(), 0) 661 | self.assertEqual(new_tree.max_key(), 44) 662 | self.assertTrue(15 not in new_tree) 663 | self.assertTrue(29 not in new_tree) 664 | 665 | def test_083_symmetric_difference_values(self): 666 | l1 = list(range(30)) 667 | shuffle(l1) 668 | l2 = list(range(15, 45)) 669 | shuffle(l2) 670 | 671 | tree1 = self.TREE_CLASS(zip(l1, l1)) 672 | tree2 = self.TREE_CLASS(zip(l2, l2)) 673 | new_tree = tree1 ^ tree2 674 | self.assertEqual(new_tree[44], 44) 675 | self.assertEqual(new_tree[1], 1) 676 | 677 | @unittest.skipIf(PYPY, "getrefcount() not supported by pypy.") 678 | def test_084_refcount_get(self): 679 | tree = self.TREE_CLASS(self.default_values1) # key == value 680 | tree[700] = 701 681 | chk = tree[700] 682 | count = sys.getrefcount(chk) 683 | for _ in range(10): 684 | chk = tree[700] 685 | 686 | self.assertEqual(sys.getrefcount(chk), count) 687 | 688 | @unittest.skipIf(PYPY, "getrefcount() not supported by pypy.") 689 | def test_085_refcount_set(self): 690 | tree = self.TREE_CLASS(self.default_values1) # key == value 691 | chk = 800 692 | count = sys.getrefcount(chk) 693 | tree[801] = chk 694 | self.assertEqual(sys.getrefcount(chk), count + 1) 695 | 696 | @unittest.skipIf(PYPY, "getrefcount() not supported by pypy.") 697 | def test_086_refcount_del(self): 698 | tree = self.TREE_CLASS(self.default_values1) # key == value 699 | chk = 900 700 | count = sys.getrefcount(chk) 701 | tree[901] = chk 702 | self.assertEqual(sys.getrefcount(chk), count + 1) 703 | del tree[901] 704 | self.assertEqual(sys.getrefcount(chk), count) 705 | 706 | @unittest.skipIf(PYPY, "getrefcount() not supported by pypy.") 707 | def test_087_refcount_replace(self): 708 | tree = self.TREE_CLASS(self.default_values1) # key == value 709 | chk = 910 710 | count = sys.getrefcount(chk) 711 | tree[911] = chk 712 | self.assertEqual(sys.getrefcount(chk), count + 1) 713 | tree[911] = 912 # replace 910 with 912 714 | self.assertEqual(sys.getrefcount(chk), count) 715 | 716 | def test_088_pickle_protocol(self): 717 | tree = self.TREE_CLASS(self.default_values1) # key == value 718 | pickle_str = pickle.dumps(tree, -1) 719 | tree2 = pickle.loads(pickle_str) 720 | self.assertEqual(len(tree), len(tree2)) 721 | self.assertEqual(list(tree.keys()), list(tree2.keys())) 722 | self.assertEqual(list(tree.values()), list(tree2.values())) 723 | 724 | # [12, 34, 45, 16, 35, 57] 725 | def test_089_floor_item(self): 726 | tree = self.TREE_CLASS(self.default_values1) # key == value 727 | self.assertEqual(tree.floor_item(12), (12, 12)) 728 | self.assertEqual(tree.floor_item(13), (12, 12)) 729 | self.assertEqual(tree.floor_item(60), (57, 57)) 730 | 731 | def test_090a_floor_item_key_error(self): 732 | tree = self.TREE_CLASS(self.default_values1) # key == value 733 | with self.assertRaises(KeyError): 734 | tree.floor_item(11) 735 | 736 | def test_090b_floor_item_empty_tree(self): 737 | tree = self.TREE_CLASS() 738 | with self.assertRaises(KeyError): 739 | tree.floor_item(11) 740 | 741 | def test_091_floor_key(self): 742 | tree = self.TREE_CLASS(self.default_values1) # key == value 743 | self.assertEqual(tree.floor_key(12), 12) 744 | self.assertEqual(tree.floor_key(13), 12) 745 | self.assertEqual(tree.floor_key(60), 57) 746 | 747 | def test_092_floor_key_key_error(self): 748 | tree = self.TREE_CLASS(self.default_values1) # key == value 749 | with self.assertRaises(KeyError): 750 | tree.floor_key(11) 751 | 752 | def test_093_ceiling_item(self): 753 | tree = self.TREE_CLASS(self.default_values1) # key == value 754 | self.assertEqual(tree.ceiling_item(57), (57, 57)) 755 | self.assertEqual(tree.ceiling_item(56), (57, 57)) 756 | self.assertEqual(tree.ceiling_item(0), (12, 12)) 757 | 758 | def test_094a_ceiling_item_key_error(self): 759 | tree = self.TREE_CLASS(self.default_values1) # key == value 760 | with self.assertRaises(KeyError): 761 | tree.ceiling_item(60) 762 | 763 | def test_094a_ceiling_item_empty_tree(self): 764 | tree = self.TREE_CLASS() 765 | with self.assertRaises(KeyError): 766 | tree.ceiling_item(60) 767 | 768 | def test_095_ceiling_key(self): 769 | tree = self.TREE_CLASS(self.default_values1) # key == value 770 | self.assertEqual(tree.ceiling_key(57), 57) 771 | self.assertEqual(tree.ceiling_key(56), 57) 772 | self.assertEqual(tree.ceiling_key(0), 12) 773 | 774 | def test_096_ceiling_key_key_error(self): 775 | tree = self.TREE_CLASS(self.default_values1) # key == value 776 | with self.assertRaises(KeyError): 777 | tree.ceiling_key(60) 778 | 779 | def test_097_data_corruption(self): 780 | # Data corruption in FastRBTree in all versions before 1.0.2: 781 | # Error was located in the rb_insert() function in ctrees.c 782 | 783 | tree = self.TREE_CLASS() 784 | insert_keys = [14, 15.84, 16, 16, 16.3, 15.8, 16.48, 14.95, 15.07, 16.41, 16.43, 16.45, 16.4, 16.42, 16.47, 785 | 16.44, 16.46, 16.48, 16.51, 16.5, 16.49, 16.5, 16.49, 16.49, 16.47, 16.5, 16.48, 16.46, 16.44] 786 | for key in insert_keys: 787 | tree[key] = "unused_data" 788 | expected_keys = sorted(set(insert_keys)) 789 | self.assertEqual(expected_keys, list(tree.keys()), "Data corruption in %s!" % tree.__class__) 790 | 791 | def test_098_foreach(self): 792 | keys = [] 793 | def collect(key, value): 794 | keys.append(key) 795 | 796 | tree = self.TREE_CLASS(self.default_values1) # key == value 797 | tree.foreach(collect) 798 | self.assertEqual(list(tree.keys()), list(sorted(keys))) 799 | 800 | def test_099_deepcopy(self): 801 | data = { 802 | 2: 2, 803 | 3: 3, 804 | 1: list(range(10)), 805 | 4: 4, 806 | 5: 5, 807 | } 808 | tree = self.TREE_CLASS(data) 809 | deep_copy_tree = deepcopy(tree) 810 | self.assertEqual(tree[1], deep_copy_tree[1]) 811 | # change deep copy 812 | deep_copy_tree[1].append(99) 813 | self.assertNotEqual(tree[1], deep_copy_tree[1]) 814 | self.assertEqual(tree[1][-1], 9) 815 | 816 | def test_100_deepcopy_tree_in_container(self): 817 | data = { 818 | 2: 2, 819 | 3: 3, 820 | 1: list(range(10)), 821 | 4: 4, 822 | 5: 5, 823 | } 824 | tree = self.TREE_CLASS(data) 825 | container = [0, 1, tree, tree, [0, 1, tree]] 826 | copy = deepcopy(container) 827 | treecopy1 = copy[2] 828 | self.assertNotEqual(id(tree), id(treecopy1)) 829 | self.assertTrue(0 in copy) 830 | self.assertTrue(1 in treecopy1) 831 | treecopy2 = copy[3] 832 | treecopy3 = copy[4][2] 833 | # test if all three copies reference the same object 834 | # only one real copy of tree exists 835 | self.assertEqual(id(treecopy1), id(treecopy2)) 836 | self.assertEqual(id(treecopy1), id(treecopy3)) 837 | treecopy1[17] = 17 838 | self.assertTrue(17 in treecopy3) 839 | self.assertFalse(17 in tree) 840 | 841 | def test_101_deepcopy_tree_in_tree(self): 842 | data1 = { 843 | 2: 2, 844 | 3: 3, 845 | 1: list(range(10)), 846 | 4: 4, 847 | 5: 5, 848 | } 849 | data2 = { 850 | 7: 7, 851 | 3: 3, 852 | 5: list(range(10)), 853 | 4: 4, 854 | 9: 9, 855 | } 856 | tree1 = self.TREE_CLASS(data1) 857 | tree2 = self.TREE_CLASS(data2) 858 | tree1[6] = tree2 # 21 859 | tree1[8] = tree2 # 22 860 | tree1[1][5] = tree2 # 23 861 | copytree1 = deepcopy(tree1) 862 | copytree21 = copytree1[6] 863 | copytree22 = copytree1[8] 864 | copytree23 = copytree1[1][5] 865 | self.assertNotEqual(id(tree2), id(copytree21)) 866 | self.assertEqual(id(copytree21), id(copytree22)) # copied only once? 867 | self.assertEqual(id(copytree21), id(copytree23)) # tree in sublist, copied only once? 868 | self.assertEqual(id(copytree21[5]), id(copytree22[5])) # sublist copied only once? 869 | self.assertEqual(id(copytree21[5]), id(copytree23[5])) # sublist copied only once? 870 | 871 | def test_102_deepcopy_empty_tree(self): 872 | tree1 = self.TREE_CLASS() 873 | tree2 = deepcopy(tree1) 874 | self.assertEqual(len(tree2), 0) 875 | 876 | def test_103_foreach_with_empty_tree(self): 877 | tree1 = self.TREE_CLASS() 878 | # does not raise any exception 879 | tree1.foreach(lambda k, v: None) 880 | self.assertTrue(True) 881 | 882 | 883 | class TestBinaryTree(CheckTree, unittest.TestCase): 884 | TREE_CLASS = BinaryTree 885 | 886 | 887 | class TestAVLTree(CheckTree, unittest.TestCase): 888 | TREE_CLASS = AVLTree 889 | 890 | 891 | class TestRBTree(CheckTree, unittest.TestCase): 892 | TREE_CLASS = RBTree 893 | 894 | 895 | class TestFastBinaryTree(CheckTree, unittest.TestCase): 896 | TREE_CLASS = FastBinaryTree 897 | 898 | 899 | class TestFastAVLTree(CheckTree, unittest.TestCase): 900 | TREE_CLASS = FastAVLTree 901 | 902 | 903 | class TestFastRBTree(CheckTree, unittest.TestCase): 904 | TREE_CLASS = FastRBTree 905 | 906 | 907 | if __name__ == '__main__': 908 | unittest.main() 909 | -------------------------------------------------------------------------------- /tests/test_cython_avltree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Created: 28.04.2010 5 | # Copyright (c) 2010-2013 by Manfred Moitzi 6 | # License: MIT License 7 | 8 | import sys 9 | PYPY = hasattr(sys, 'pypy_version_info') 10 | 11 | import unittest 12 | from random import randint, shuffle 13 | 14 | if not PYPY: 15 | from bintrees.cython_trees import FastAVLTree 16 | 17 | @unittest.skipIf(PYPY, "Cython implementation not supported for pypy.") 18 | class TestTree(unittest.TestCase): 19 | values = [(2, 12), (4, 34), (8, 45), (1, 16), (9, 35), (3, 57)] 20 | keys = [2, 4, 8, 1, 9, 3] 21 | 22 | def test_create_tree(self): 23 | tree = FastAVLTree() 24 | self.assertEqual(tree.count, 0) 25 | tree.update(self.values) 26 | self.assertEqual(tree.count, 6) 27 | 28 | def test_get_value(self): 29 | tree = FastAVLTree(self.values) 30 | for key in self.keys: 31 | value = tree.get_value(key) 32 | self.assertTrue(value is not None) 33 | 34 | def test_get_value_not(self): 35 | tree = FastAVLTree() 36 | self.assertRaises(KeyError, tree.get_value, 17) 37 | 38 | def test_properties(self): 39 | tree = FastAVLTree(self.values) 40 | self.assertEqual(tree.count, 6) 41 | 42 | def test_clear_tree(self): 43 | tree = FastAVLTree(self.values) 44 | tree.clear() 45 | self.assertEqual(tree.count, 0) 46 | 47 | def test_insert(self): 48 | tree = FastAVLTree() 49 | for key in self.keys: 50 | tree.insert(key, key) 51 | value = tree.get_value(key) 52 | self.assertEqual(value, key) 53 | self.assertEqual(tree.count, 6) 54 | 55 | def test_remove(self): 56 | tree = FastAVLTree(self.values) 57 | for key in self.keys: 58 | tree.remove(key) 59 | self.assertRaises(KeyError, tree.get_value, key) 60 | self.assertEqual(tree.count, 0) 61 | 62 | def test_remove_random_numbers(self): 63 | keys = list(set([randint(0, 10000) for _ in range(500)])) 64 | shuffle(keys) 65 | tree = FastAVLTree(zip(keys, keys)) 66 | self.assertEqual(tree.count, len(keys)) 67 | for key in keys: 68 | tree.remove(key) 69 | self.assertEqual(tree.count, 0) 70 | 71 | if __name__ == '__main__': 72 | unittest.main() 73 | -------------------------------------------------------------------------------- /tests/test_cython_bintree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Created: 28.04.2010 5 | # Copyright (c) 2010-2013 by Manfred Moitzi 6 | # License: MIT License 7 | 8 | 9 | import sys 10 | PYPY = hasattr(sys, 'pypy_version_info') 11 | 12 | import unittest 13 | from random import randint, shuffle 14 | 15 | if not PYPY: 16 | from bintrees.cython_trees import FastBinaryTree 17 | 18 | @unittest.skipIf(PYPY, "Cython implementation not supported for pypy.") 19 | class TestTree(unittest.TestCase): 20 | values = [(2, 12), (4, 34), (8, 45), (1, 16), (9, 35), (3, 57)] 21 | keys = [2, 4, 8, 1, 9, 3] 22 | 23 | def test_create_tree(self): 24 | tree = FastBinaryTree() 25 | self.assertEqual(tree.count, 0) 26 | tree.update(self.values) 27 | self.assertEqual(tree.count, 6) 28 | 29 | def test_get_value(self): 30 | tree = FastBinaryTree(self.values) 31 | for key in self.keys: 32 | value = tree.get_value(key) 33 | self.assertTrue(value is not None) 34 | 35 | def test_get_value_not(self): 36 | tree = FastBinaryTree() 37 | self.assertRaises(KeyError, tree.get_value, 17) 38 | 39 | def test_properties(self): 40 | tree = FastBinaryTree(self.values) 41 | self.assertEqual(tree.count, 6) 42 | 43 | def test_clear_tree(self): 44 | tree = FastBinaryTree(self.values) 45 | tree.clear() 46 | self.assertEqual(tree.count, 0) 47 | 48 | def test_insert(self): 49 | tree = FastBinaryTree() 50 | for key in self.keys: 51 | tree.insert(key, key) 52 | value = tree.get_value(key) 53 | self.assertEqual(value, key) 54 | self.assertEqual(tree.count, 6) 55 | 56 | def test_remove(self): 57 | tree = FastBinaryTree(self.values) 58 | for key in self.keys: 59 | tree.remove(key) 60 | self.assertRaises(KeyError, tree.get_value, key) 61 | self.assertEqual(tree.count, 0) 62 | 63 | def test_remove_random_numbers(self): 64 | keys = list(set([randint(0, 10000) for _ in range(500)])) 65 | shuffle(keys) 66 | tree = FastBinaryTree(zip(keys, keys)) 67 | self.assertEqual(tree.count, len(keys)) 68 | for key in keys: 69 | tree.remove(key) 70 | self.assertEqual(tree.count, 0) 71 | if __name__ == '__main__': 72 | unittest.main() 73 | -------------------------------------------------------------------------------- /tests/test_cython_rbtree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman 4 | # Purpose: test binary trees 5 | # Created: 28.04.2010 6 | # Copyright (c) 2010-2013 by Manfred Moitzi 7 | # License: MIT License 8 | 9 | 10 | import sys 11 | PYPY = hasattr(sys, 'pypy_version_info') 12 | 13 | import unittest 14 | from random import randint, shuffle 15 | 16 | if not PYPY: 17 | from bintrees.cython_trees import FastRBTree 18 | 19 | @unittest.skipIf(PYPY, "Cython implementation not supported for pypy.") 20 | class TestTree(unittest.TestCase): 21 | values = [(2, 12), (4, 34), (8, 45), (1, 16), (9, 35), (3, 57)] 22 | keys = [2, 4, 8, 1, 9, 3] 23 | 24 | def test_create_tree(self): 25 | tree = FastRBTree() 26 | self.assertEqual(tree.count, 0) 27 | tree.update(self.values) 28 | self.assertEqual(tree.count, 6) 29 | 30 | def test_properties(self): 31 | tree = FastRBTree(self.values) 32 | self.assertEqual(tree.count, 6) 33 | 34 | def test_clear_tree(self): 35 | tree = FastRBTree(self.values) 36 | tree.clear() 37 | self.assertEqual(tree.count, 0) 38 | 39 | def test_insert(self): 40 | tree = FastRBTree() 41 | for key in self.keys: 42 | tree.insert(key, key) 43 | value = tree.get_value(key) 44 | self.assertEqual(value, key) 45 | self.assertEqual(tree.count, 6) 46 | 47 | def test_remove(self): 48 | tree = FastRBTree(self.values) 49 | for key in self.keys: 50 | tree.remove(key) 51 | self.assertRaises(KeyError, tree.get_value, key) 52 | self.assertEqual(tree.count, 0) 53 | 54 | def test_remove_random_numbers(self): 55 | keys = list(set([randint(0, 10000) for _ in range(500)])) 56 | shuffle(keys) 57 | tree = FastRBTree(zip(keys, keys)) 58 | self.assertEqual(tree.count, len(keys)) 59 | for key in keys: 60 | tree.remove(key) 61 | self.assertEqual(tree.count, 0) 62 | 63 | 64 | if __name__ == '__main__': 65 | unittest.main() 66 | -------------------------------------------------------------------------------- /tests/test_treeslice.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding:utf-8 3 | # Author: mozman -- 4 | # Created: 11.04.2011 5 | # Copyright (c) 2010-2013 by Manfred Moitzi 6 | # License: MIT License 7 | 8 | 9 | import unittest 10 | 11 | from bintrees import RBTree 12 | 13 | 14 | class TestTreeSlice(unittest.TestCase): 15 | def setUp(self): 16 | self.tree = RBTree({1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f'}) 17 | 18 | def test_in_slice_object(self): 19 | treeslice = self.tree[2:6] 20 | self.assertTrue(2 in treeslice) 21 | 22 | def test_not_in_slice_object(self): 23 | treeslice = self.tree[2:6] 24 | self.assertFalse(1 in treeslice) 25 | 26 | def test_in_slice_object_with_no_start(self): 27 | treeslice = self.tree[:6] 28 | self.assertTrue(1 in treeslice) 29 | self.assertFalse(6 in treeslice) 30 | 31 | def test_in_slice_object_with_no_stop(self): 32 | treeslice = self.tree[2:] 33 | self.assertTrue(6 in treeslice) 34 | self.assertFalse(1 in treeslice) 35 | 36 | def test_getitem_from_slice_object(self): 37 | treeslice = self.tree[2:6] 38 | self.assertEqual('b', treeslice[2]) 39 | 40 | def test_error_getitem_for_key_out_of_range(self): 41 | treeslice = self.tree[2:6] 42 | self.assertRaises(KeyError, treeslice.__getitem__, 6) 43 | self.assertRaises(KeyError, treeslice.__getitem__, 1) 44 | 45 | def test_error_getitem_for_key_in_range(self): 46 | treeslice = self.tree[2:6] 47 | self.assertRaises(KeyError, treeslice.__getitem__, 3.5) 48 | 49 | def test_iter_slice_object(self): 50 | treeslice = self.tree[2:6] 51 | self.assertEqual([2, 3, 4, 5], list(treeslice)) 52 | 53 | def test_iter_all(self): 54 | self.assertEqual([1, 2, 3, 4, 5, 6], list(self.tree[:])) 55 | 56 | def test_iter_values_slice_object(self): 57 | treeslice = self.tree[2:6] 58 | self.assertEqual(['b', 'c', 'd', 'e'], list(treeslice.values())) 59 | 60 | def test_iter_items_slice_object(self): 61 | treeslice = self.tree[2:6] 62 | self.assertEqual([(2, 'b'), (3, 'c'), (4, 'd'), (5, 'e')], list(treeslice.items())) 63 | 64 | def test_subslicing(self): 65 | subslice = self.tree[2:6][3:5] 66 | self.assertEqual([3, 4], list(subslice)) 67 | 68 | def test_subslicing_nobounds_1(self): 69 | subslice = self.tree[2:6][:5] 70 | self.assertEqual([2, 3, 4], list(subslice)) 71 | 72 | def test_subslicing_nobounds_2(self): 73 | subslice = self.tree[2:6][3:] 74 | self.assertEqual([3, 4, 5], list(subslice)) 75 | 76 | def test_subslicing_nobounds_3(self): 77 | subslice = self.tree[:4][:3] 78 | self.assertEqual([1, 2], list(subslice)) 79 | 80 | def test_subslicing_nobounds_4(self): 81 | subslice = self.tree[:4][2:] 82 | self.assertEqual([2, 3], list(subslice)) 83 | 84 | def test_subslicing_nobounds_5(self): 85 | subslice = self.tree[2:][1:] 86 | self.assertEqual([2, 3, 4, 5, 6], list(subslice)) 87 | 88 | def test_subslicing_nobounds_6(self): 89 | subslice = self.tree[2:][:5] 90 | self.assertEqual([2, 3, 4], list(subslice)) 91 | 92 | def test_subslicing_nobounds_7(self): 93 | subslice = self.tree[2:][:1] 94 | self.assertEqual([], list(subslice)) 95 | 96 | def test_repr(self): 97 | result = repr(self.tree[2:4]) 98 | self.assertEqual("RBTree({2: 'b', 3: 'c'})", result) 99 | 100 | if __name__ == '__main__': 101 | unittest.main() -------------------------------------------------------------------------------- /tests/testkey.txt: -------------------------------------------------------------------------------- 1 | [3943, 4136, 2894, 6036, 4643, 1154, 3930, 1675, 5662, 571, 2212, 3285, 9641, 6848, 2927, 9197, 8750, 9253, 7827, 8004, 8987, 5588, 1305, 5954, 8652, 5501, 3356, 1941, 5367, 6875, 7740, 7239, 3046, 7798, 4733, 4065, 2901, 5410, 9483, 3043, 9784, 5709, 6682, 992, 4258, 8829, 5203, 693, 6232, 2761, 1903, 820, 1508, 40, 1830, 4021, 7362, 5930, 1989, 9191, 6624, 2518, 9997, 6472, 363, 8389, 7426, 3348, 1811, 4851, 1796, 3855, 7409, 5665, 8384, 3835, 746, 9640, 7919, 5209, 9315, 8141, 6658, 8548, 7649, 6618, 5822, 6615, 5631, 8734, 1800, 2336, 7283, 3965, 2629, 6590, 9348, 50, 524, 1486, 4671, 1339, 5839, 28, 7604, 4644, 2450, 1924, 7358, 7842, 3812, 146, 3458, 2515, 3261, 891, 5532, 7989, 33, 2140, 566, 7962, 1102, 762, 5741, 9344, 1769, 3540, 2818, 8166, 2972, 1215, 7390, 9048, 1798, 4743, 9166, 8521, 788, 8697, 495, 4753, 3848, 7339, 9492, 4812, 6140, 4708, 4532, 6751, 5660, 4681, 6295, 7241, 2526, 8087, 6800, 5983, 8207, 8036, 7724, 9867, 8477, 7501, 2847, 1261, 487, 1199, 5188, 5319, 7297, 5029, 1046, 7344, 1005, 1284, 4616, 5232, 17, 2611, 8462, 8929, 5512, 4086, 5581, 8695, 1507, 1899, 3069, 7307, 3542, 8424, 7033, 3426, 8273, 6634, 6792, 9871, 4825, 3073, 8702, 7682, 358, 182, 8572, 2199, 9954, 1126, 8612, 8071, 65, 9028, 8528, 3842, 7028, 7496, 4850, 4779, 6778, 1918, 6282, 330, 1373, 8488, 7227, 9762, 4539, 8008, 7484, 5609, 8250, 6543, 9195, 9025, 8212, 1600, 8567, 432, 700, 8336, 9138, 6362, 3130, 7520, 5628, 8423, 1973, 8132, 5116, 1410, 9996, 7236, 7311, 6928, 580, 9205, 2208, 5099, 6981, 3159, 3933, 4378, 1633, 8294, 9963, 437, 1087, 9552, 6195, 1952, 404, 3911, 7013, 7249, 2587, 6217, 7115, 323, 7865, 5873, 5419, 898, 5161, 4023, 6529, 1684, 2232, 5400, 3011, 9497, 2715, 5439, 9130, 6429, 304, 9231, 5174, 7022, 4894, 3703, 3412, 9488, 3539, 1247, 6556, 3697, 2570, 429, 1780, 8288, 9376, 3743, 7646, 9671, 8643, 6205, 5879, 8609, 4282, 638, 3037, 202, 3912, 3878, 9790, 1323, 3780, 7873, 4203, 3369, 3641, 5131, 7290, 971, 5135, 2398, 2539, 9801, 7805, 7825, 4914, 2153, 9693, 9917, 6903, 8658, 6223, 1969, 5981, 3377, 3383, 1225, 2588, 6046, 8638, 1302, 2125, 335, 6818, 9323, 9888, 5655, 8796, 1422, 3181, 4738, 9531, 8582, 1557, 6741, 4981, 5320, 6208, 1425, 2867, 9759, 7353, 4845, 357, 2730, 5165, 5738, 5811, 889, 5404, 7308, 3164, 9806, 516, 212, 8226, 2480, 7872, 9707, 9938, 9724, 5255, 3885, 4217, 5571, 6057, 5221, 5573, 3771, 9458, 5229, 9645, 4482, 2502, 9228, 846, 5323, 6191, 3305, 4855, 4859, 6081, 3496, 3633, 8251, 512, 4201, 2751, 1131, 8762, 6267, 5843, 9201, 9319, 4525, 2329, 6980, 7176, 8668, 4587, 6216, 8272, 1650, 4292, 4278, 6301, 6707, 5007, 3714, 3892, 8463, 5721, 757, 9202, 4943, 2414, 3595, 5555, 6824, 6997, 8085, 9509, 9907, 2396, 4330, 7286, 6985, 636, 4218, 6416, 4452, 1055, 4192, 8996, 5919, 9526, 2535, 7652, 2631, 9594, 6714, 3388, 8357, 910, 1801, 3607, 6052, 4349, 8291, 4564, 6361, 551, 104, 4666] --------------------------------------------------------------------------------