From 11c54a194cfb9dc9f57d74427d78fad78d832611 Mon Sep 17 00:00:00 2001 From: Manfred Moitzi Date: Sat, 9 Jan 2016 07:17:58 +0100 Subject: [PATCH] removed warnings on import; added has_fast_tree_support(); release 2.0.4 --- .hgignore | 15 + LICENSE.txt | 45 ++ MANIFEST.in | 6 + NEWS.rst | 114 +++++ README.rst | 191 ++++++++ bintrees/__init__.py | 169 +++++++ bintrees/abctree.py | 820 +++++++++++++++++++++++++++++++ bintrees/avltree.py | 273 +++++++++++ bintrees/bintree.py | 125 +++++ bintrees/ctrees.c | 791 ++++++++++++++++++++++++++++++ bintrees/ctrees.h | 50 ++ bintrees/ctrees.pxd | 39 ++ bintrees/cython_trees.pyx | 265 ++++++++++ bintrees/rbtree.py | 239 +++++++++ bintrees/treeslice.py | 70 +++ issues/003_fastrbtree_crash.py | 62 +++ issues/004_rbtree_copy.py | 113 +++++ issues/005_btreeslow.py | 22 + issues/006_large_data_crash.py | 46 ++ issues/007_FastRBTree_error2.py | 81 ++++ makefile.unix | 43 ++ makefile.win | 46 ++ profiling/profile_avltree.py | 102 ++++ profiling/profile_big_rbtree.py | 94 ++++ profiling/profile_bintree.py | 118 +++++ profiling/profile_itemslice.py | 129 +++++ profiling/profile_min_max.py | 88 ++++ profiling/profile_prev_succ.py | 100 ++++ profiling/profile_pytrees.py | 152 ++++++ profiling/profile_rbtree.py | 103 ++++ profiling/testkeys.txt | 1 + setup.cfg | 5 + setup.py | 54 +++ testresults.ods | Bin 0 -> 21600 bytes tests/__init__.py | 0 tests/test_all_trees.py | 825 ++++++++++++++++++++++++++++++++ tests/test_cython_avltree.py | 72 +++ tests/test_cython_bintree.py | 72 +++ tests/test_cython_rbtree.py | 65 +++ tests/test_treeslice.py | 101 ++++ tests/testkey.txt | 1 + 41 files changed, 5707 insertions(+) create mode 100644 .hgignore create mode 100644 LICENSE.txt create mode 100644 MANIFEST.in create mode 100644 NEWS.rst create mode 100644 README.rst create mode 100644 bintrees/__init__.py create mode 100644 bintrees/abctree.py create mode 100644 bintrees/avltree.py create mode 100644 bintrees/bintree.py create mode 100644 bintrees/ctrees.c create mode 100644 bintrees/ctrees.h create mode 100644 bintrees/ctrees.pxd create mode 100644 bintrees/cython_trees.pyx create mode 100644 bintrees/rbtree.py create mode 100644 bintrees/treeslice.py create mode 100644 issues/003_fastrbtree_crash.py create mode 100644 issues/004_rbtree_copy.py create mode 100644 issues/005_btreeslow.py create mode 100644 issues/006_large_data_crash.py create mode 100644 issues/007_FastRBTree_error2.py create mode 100644 makefile.unix create mode 100644 makefile.win create mode 100644 profiling/profile_avltree.py create mode 100644 profiling/profile_big_rbtree.py create mode 100644 profiling/profile_bintree.py create mode 100644 profiling/profile_itemslice.py create mode 100644 profiling/profile_min_max.py create mode 100644 profiling/profile_prev_succ.py create mode 100644 profiling/profile_pytrees.py create mode 100644 profiling/profile_rbtree.py create mode 100644 profiling/testkeys.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 testresults.ods create mode 100644 tests/__init__.py create mode 100644 tests/test_all_trees.py create mode 100644 tests/test_cython_avltree.py create mode 100644 tests/test_cython_bintree.py create mode 100644 tests/test_cython_rbtree.py create mode 100644 tests/test_treeslice.py create mode 100644 tests/testkey.txt diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..aeb03d9 --- /dev/null +++ b/.hgignore @@ -0,0 +1,15 @@ +syntax: glob +build/* +dist/* +.tox/* +.idea/* +*.so +*.o +*.pyd +*.err +*.orig +*.bak +*.pyc +*.*~ +*.wpr +MANIFEST diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..3b2c783 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,45 @@ +Copyright (c) 2012, Manfred Moitzi + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Deutsche Übersetzung: + +Copyright (c) 2012, Manfred Moitzi + +Hiermit wird unentgeltlich, jeder Person, die eine Kopie der Software +und der zugehörigen Dokumentationen (die "Software") erhält, die +Erlaubnis erteilt, uneingeschränkt zu benutzen, inklusive und ohne +Ausnahme, dem Recht, sie zu verwenden, kopieren, ändern, fusionieren, +verlegen, verbreiten, unterlizenzieren und/oder zu verkaufen, und +Personen, die diese Software erhalten, diese Rechte zu geben, unter den +folgenden Bedingungen: + +Der obige Urheberrechtsvermerk und dieser Erlaubnisvermerk sind in allen +Kopien oder Teilkopien der Software beizulegen. + +DIE SOFTWARE WIRD OHNE JEDE AUSDRÜCKLICHE ODER IMPLIZIERTE GARANTIE +BEREITGESTELLT, EINSCHLIESSLICH DER GARANTIE ZUR BENUTZUNG FÜR DEN +VORGESEHENEN ODER EINEM BESTIMMTEN ZWECK SOWIE JEGLICHER +RECHTSVERLETZUNG, JEDOCH NICHT DARAUF BESCHRÄNKT. IN KEINEM FALL SIND +DIE AUTOREN ODER COPYRIGHTINHABER FÜR JEGLICHEN SCHADEN ODER SONSTIGE +ANSPRÜCHE HAFTBAR ZU MACHEN, OB INFOLGE DER ERFÜLLUNG EINES VERTRAGES, +EINES DELIKTES ODER ANDERS IM ZUSAMMENHANG MIT DER SOFTWARE ODER +SONSTIGER VERWENDUNG DER SOFTWARE ENTSTANDEN. + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..97eeb08 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include NEWS.rst README.rst LICENSE.txt +include tests/testkey.txt +include bintrees/ctrees.c +include bintrees/ctrees.h +recursive-include tests *.py +recursive-include bintrees *.pyx *.pxd \ No newline at end of file diff --git a/NEWS.rst b/NEWS.rst new file mode 100644 index 0000000..e68abf7 --- /dev/null +++ b/NEWS.rst @@ -0,0 +1,114 @@ + +NEWS +==== + +Version 2.0.4 - 2015-01-09 + + * removed logging statements on import + * added helper function bintrees.has_fast_free_support() + * HINT: pypy runs faster than CPython with Cython extension + +Version 2.0.3 - 2015-01-06 + + * replaced print function by logging.warning for import warning messages + * KNOWN ISSUE: unable to build Cython extension with MingW32 and CPython 3.5 & CPython 2.7.10 + +Version 2.0.2 - 2015-02-12 + + * fixed foreach cython-function by Sam Yaple + +Version 2.0.1 - 2013-10-01 + + * removed __del__() method to avoid problems with garbage collection + +Version 2.0.0 - 2013-06-01 + + * API change: consistent method naming with synonyms for dict/set compatibility + * code base refactoring + * removed tree walkers + * removed low level node stack implementation -> caused crashes + * optimizations for pypy: iter_items(), succ_item(), prev_item() + * tested with CPython2.7, CPython3.3, pypy-2.0 on Win7 and Linux Mint 15 x64 (pypy-1.9) + +Version 1.0.3 - 2013-05-01 + + * extended iter_items(startkey=None, endkey=None, reverse=reverse) -> better performance for slicing + * Cython implementation of iter_items() for Fast_X_Trees() + * added key parameter *reverse* to itemslice(), keyslice(), valueslice() + * tested with CPython2.7, CPython3.3, pypy-2.0 + +Version 1.0.2 - 2013-04-01 + + * bug fix: FastRBTree data corruption on inserting existing keys + * bug fix: union & symmetric_difference - copy all values to result tree + +Version 1.0.1 - 2013-02-01 + + * bug fixes + * refactorings by graingert + * skip useless tests for pypy + * new license: MIT License + * tested with CPython2.7, CPython3.2, CPython3.3, pypy-1.9, pypy-2.0-beta1 + * unified line endings to LF + * PEP8 refactorings + * added floor_item/key, ceiling_item/key methods, thanks to Dai Mikurube + +Version 1.0.0 - 2011-12-29 + + * bug fixes + * status: 5 - Production/Stable + * removed useless TreeIterator() class and T.treeiter() method. + * patch from Max Motovilov to use Visual Studio 2008 for building C-extensions + +Version 0.4.0 - 2011-04-14 + + * API change!!! + * full Python 3 support, also for Cython implementations + * removed user defined compare() function - keys have to be comparable! + * removed T.has_key(), use 'key in T' + * keys(), items(), values() generating 'views' + * removed iterkeys(), itervalues(), iteritems() methods + * replaced index slicing by key slicing + * removed index() and item_at() + * repr() produces a correct representation + * installs on systems without cython (tested with pypy) + * new license: GNU Library or Lesser General Public License (LGPL) + +Version 0.3.2 - 2011-04-09 + + * added itemslice(startkey, endkey), keyslice(startkey, endkey), + valueslice(startkey, endkey) - slicing by keys + * tested with pypy 1.4.1, damn fast + * Pure Python trees are working with Python 3 + * No Cython implementation for Python 3 + +Version 0.3.1 - 2010-09-10 + + * runs with Python 2.7 + +Version 0.3.0 - 2010-05-11 + + * low level functions written as c-module only interface to python is a cython + module + * support for the pickle protocol + +Version 0.2.1 - 2010-05-06 + + * added delslice del T[0:3] -> remove treenodes 0, 1, 2 + * added discard -> remove key without KeyError if not found + * added heap methods: min, max, nlarges, nsmallest ... + * added Set methods -> intersection, differnce, union, ... + * added slicing: T[5:10] get items with position (not key!) 5, 6, 7, 8, 9 + T[5] get item with key! 5 + * added index: T.index(key) -> get position of item + * added item_at: T.item_at(0) -> get item at position (not key!) 0 + T.item_at(0) O(n)! <==> T.min_item() O(log(n)) + +Version 0.2.0 - 2010-05-03 + + * TreeMixin Class as base for Python-Trees and as Mixin for Cython-Trees + +Version 0.1.0 - 2010-04-27 + + * Alpha status + * Initial release diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..ba94a72 --- /dev/null +++ b/README.rst @@ -0,0 +1,191 @@ +Binary Tree Package +=================== + +Abstract +======== + +This package provides Binary- RedBlack- and AVL-Trees written in Python and Cython/C. + +This Classes are much slower than the built-in *dict* class, but all +iterators/generators yielding data in sorted key order. Trees can be +uses as drop in replacement for *dicts* in most cases. + +Source of Algorithms +-------------------- + +AVL- and RBTree algorithms taken from Julienne Walker: http://eternallyconfuzzled.com/jsw_home.aspx + +Trees written in Python +----------------------- + + - *BinaryTree* -- unbalanced binary tree + - *AVLTree* -- balanced AVL-Tree + - *RBTree* -- balanced Red-Black-Tree + +Trees written with C-Functions and Cython as wrapper +---------------------------------------------------- + + - *FastBinaryTree* -- unbalanced binary tree + - *FastAVLTree* -- balanced AVL-Tree + - *FastRBTree* -- balanced Red-Black-Tree + +All trees provides the same API, the pickle protocol is supported. + +Cython-Trees have C-structs as tree-nodes and C-functions for low level operations: + + - insert + - remove + - get_value + - min_item + - max_item + - prev_item + - succ_item + - floor_item + - ceiling_item + +Constructor +~~~~~~~~~~~ + + * Tree() -> new empty tree; + * Tree(mapping) -> new tree initialized from a mapping (requires only an items() method) + * Tree(seq) -> new tree initialized from seq [(k1, v1), (k2, v2), ... (kn, vn)] + +Methods +~~~~~~~ + + * __contains__(k) -> True if T has a key k, else False, O(log(n)) + * __delitem__(y) <==> del T[y], del[s:e], O(log(n)) + * __getitem__(y) <==> T[y], T[s:e], O(log(n)) + * __iter__() <==> iter(T) + * __len__() <==> len(T), O(1) + * __max__() <==> max(T), get max item (k,v) of T, O(log(n)) + * __min__() <==> min(T), get min item (k,v) of T, O(log(n)) + * __and__(other) <==> T & other, intersection + * __or__(other) <==> T | other, union + * __sub__(other) <==> T - other, difference + * __xor__(other) <==> T ^ other, symmetric_difference + * __repr__() <==> repr(T) + * __setitem__(k, v) <==> T[k] = v, O(log(n)) + * clear() -> None, remove all items from T, O(n) + * copy() -> a shallow copy of T, O(n*log(n)) + * discard(k) -> None, remove k from T, if k is present, O(log(n)) + * get(k[,d]) -> T[k] if k in T, else d, O(log(n)) + * is_empty() -> True if len(T) == 0, O(1) + * items([reverse]) -> generator for (k, v) items of T, O(n) + * keys([reverse]) -> generator for keys of T, O(n) + * values([reverse]) -> generator for values of T, O(n) + * pop(k[,d]) -> v, remove specified key and return the corresponding value, O(log(n)) + * pop_item() -> (k, v), remove and return some (key, value) pair as a 2-tuple, O(log(n)) (synonym popitem() exist) + * 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) + * update(E) -> None. Update T from dict/iterable E, O(E*log(n)) + * 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) + * iter_items(s, e[, reverse]) -> generator for (k, v) items of T for s <= key < e, O(n) + * remove_items(keys) -> None, remove items by keys, O(n) + +slicing by keys +~~~~~~~~~~~~~~~ + + * item_slice(s, e[, reverse]) -> generator for (k, v) items of T for s <= key < e, O(n), synonym for iter_items(...) + * key_slice(s, e[, reverse]) -> generator for keys of T for s <= key < e, O(n) + * value_slice(s, e[, reverse]) -> generator for values of T for s <= key < e, O(n) + * T[s:e] -> TreeSlice object, with keys in range s <= key < e, O(n) + * del T[s:e] -> remove items by key slicing, for s <= key < e, O(n) + + start/end parameter: + + * if 's' is None or T[:e] TreeSlice/iterator starts with value of min_key(); + * if 'e' is None or T[s:] TreeSlice/iterator ends with value of max_key(); + * T[:] is a TreeSlice which represents the whole tree; + + The step argument of the regular slicing syntax T[s:e:step] will silently ignored. + + TreeSlice is a tree wrapper with range check and contains no references + to objects, deleting objects in the associated tree also deletes the object + in the TreeSlice. + + * TreeSlice[k] -> get value for key k, raises KeyError if k not exists in range s:e + * TreeSlice[s1:e1] -> TreeSlice object, with keys in range s1 <= key < e1 + - new lower bound is max(s, s1) + - new upper bound is min(e, e1) + + TreeSlice methods: + + * items() -> generator for (k, v) items of T, O(n) + * keys() -> generator for keys of T, O(n) + * values() -> generator for values of T, O(n) + * __iter__ <==> keys() + * __repr__ <==> repr(T) + * __contains__(key)-> True if TreeSlice has a key k, else False, O(log(n)) + +prev/succ operations +~~~~~~~~~~~~~~~~~~~~ + + * prev_item(key) -> get (k, v) pair, where k is predecessor to key, O(log(n)) + * prev_key(key) -> k, get the predecessor of key, O(log(n)) + * succ_item(key) -> get (k,v) pair as a 2-tuple, where k is successor to key, O(log(n)) + * succ_key(key) -> k, get the successor of key, O(log(n)) + * floor_item(key) -> get (k, v) pair, where k is the greatest key less than or equal to key, O(log(n)) + * floor_key(key) -> k, get the greatest key less than or equal to key, O(log(n)) + * ceiling_item(key) -> get (k, v) pair, where k is the smallest key greater than or equal to key, O(log(n)) + * ceiling_key(key) -> k, get the smallest key greater than or equal to key, O(log(n)) + +Heap methods +~~~~~~~~~~~~ + + * max_item() -> get largest (key, value) pair of T, O(log(n)) + * max_key() -> get largest key of T, O(log(n)) + * min_item() -> get smallest (key, value) pair of T, O(log(n)) + * min_key() -> get smallest key of T, O(log(n)) + * pop_min() -> (k, v), remove item with minimum key, O(log(n)) + * pop_max() -> (k, v), remove item with maximum key, O(log(n)) + * nlargest(i[,pop]) -> get list of i largest items (k, v), O(i*log(n)) + * nsmallest(i[,pop]) -> get list of i smallest items (k, v), O(i*log(n)) + +Set methods (using frozenset) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + * intersection(t1, t2, ...) -> Tree with keys *common* to all trees + * union(t1, t2, ...) -> Tree with keys from *either* trees + * difference(t1, t2, ...) -> Tree with keys in T but not any of t1, t2, ... + * symmetric_difference(t1) -> Tree with keys in either T and t1 but not both + * is_subset(S) -> True if every element in T is in S (synonym issubset() exist) + * is_superset(S) -> True if every element in S is in T (synonym issuperset() exist) + * is_disjoint(S) -> True if T has a null intersection with S (synonym isdisjoint() exist) + +Classmethods +~~~~~~~~~~~~ + + * from_keys(S[,v]) -> New tree with keys from S and values equal to v. (synonym fromkeys() exist) + +Helper functions +~~~~~~~~~~~~~~~~ + + * bintrees.has_fast_tree_support() -> True if Cython extension is working else False (False = using pure Python implementation) + +Installation +============ + +from source:: + + python setup.py install + +or from PyPI:: + + pip install bintrees + +Compiling the fast Trees requires Cython and on Windows is a C-Compiler necessary (MingW32 works fine, except for +CPython 2.7.10 & CPython 3.5). + +Download Binaries for Windows +============================= + +http://bitbucket.org/mozman/bintrees/downloads + +Documentation +============= + +this README.rst + +bintrees can be found on bitbucket.org at: + +http://bitbucket.org/mozman/bintrees diff --git a/bintrees/__init__.py b/bintrees/__init__.py new file mode 100644 index 0000000..ae39e1d --- /dev/null +++ b/bintrees/__init__.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Purpose: binary trees package +# Created: 03.05.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +from __future__ import absolute_import + +__doc__ = """ +Binary Tree Package +=================== + +Python Trees +------------ + +Balanced and unbalanced binary trees written in pure Python with a dict-like API. + +Classes +~~~~~~~ +* BinaryTree -- unbalanced binary tree +* AVLTree -- balanced AVL-Tree +* RBTree -- balanced Red-Black-Tree + +Cython Trees +------------ + +Basic tree functions written in Cython/C, merged with _ABCTree() to provide the +full API of the Python Trees. + +Classes +~~~~~~~ + +* FastBinaryTree -- unbalanced binary tree +* FastAVLTree -- balanced AVLTree +* FastRBTree -- balanced Red-Black-Tree + +Overview of API for all Classes +=============================== + +* TreeClass ([compare]) -> new empty tree. +* TreeClass(mapping, [compare]) -> new tree initialized from a mapping +* TreeClass(seq, [compare]) -> new tree initialized from seq [(k1, v1), (k2, v2), ... (kn, vn)] + +Methods +------- + +* __contains__(k) -> True if T has a key k, else False, O(log(n)) +* __delitem__(y) <==> del T[y], O(log(n)) +* __getitem__(y) <==> T[y], O(log(n)) +* __iter__() <==> iter(T) +* __len__() <==> len(T), O(1) +* __max__() <==> max(T), get max item (k,v) of T, O(log(n)) +* __min__() <==> min(T), get min item (k,v) of T, O(log(n)) +* __and__(other) <==> T & other, intersection +* __or__(other) <==> T | other, union +* __sub__(other) <==> T - other, difference +* __xor__(other) <==> T ^ other, symmetric_difference +* __repr__() <==> repr(T) +* __setitem__(k, v) <==> T[k] = v, O(log(n)) +* clear() -> None, Remove all items from T, , O(n) +* copy() -> a shallow copy of T, O(n*log(n)) +* discard(k) -> None, remove k from T, if k is present, O(log(n)) +* get(k[,d]) -> T[k] if k in T, else d, O(log(n)) +* is_empty() -> True if len(T) == 0, O(1) +* items([reverse]) -> list of T's (k, v) pairs, as 2-tuple, O(n) +* keys([reverse]) -> list of T's keys, O(n) +* values([reverse]) -> list of T's values, O(n) +* pop(k[,d]) -> v, remove specified key and return the corresponding value, O(log(n)) +* pop_item() -> (k, v), remove and return some (key, value) pair as a 2-tuple, O(log(n)) +* set_default(k[,d]) -> T.get(k, d), also set T[k]=d if k not in T, O(log(n)) +* update(E) -> None. Update T from dict/iterable E, O(E*log(n)) +* iter_items(s, e, reverse) -> generator for (k, v) items of T for s <= key < e, O(n) + +walk forward/backward, O(log(n)) + +* prev_item(key) -> get (k, v) pair, where k is predecessor to key, O(log(n)) +* prev_key(key) -> k, get the predecessor of key, O(log(n)) +* succ_item(key) -> get (k,v) pair as a 2-tuple, where k is successor to key, O(log(n)) +* succ_key(key) -> k, get the successor of key, O(log(n)) + +slicing by keys + +* item_slice(s, e, reverse) -> generator for (k, v) items of T for s <= key < e, O(n), synonym for iter_items(...) +* key_slice(s, e, reverse) -> generator for keys of T for s <= key < e, O(n) +* value_slice(s, e, reverse) -> generator for values of T for s <= key < e, O(n) +* T[s:e] -> TreeSlice object, with keys in range s <= key < e, O(n) +* del T[s:e] -> remove items by key slicing, for s <= key < e, O(n) + +if 's' is None or T[:e] TreeSlice/iterator starts with value of min_key() +if 'e' is None or T[s:] TreeSlice/iterator ends with value of max_key() +T[:] is a TreeSlice which represents the whole tree. + +The step argument of the regular slicing syntax T[s:e:step] will silently ignored. + +TreeSlice is a tree wrapper with range check, and contains no references +to objects, deleting objects in the associated tree also deletes the object +in the TreeSlice. + +* TreeSlice[k] -> get value for key k, raises KeyError if k not exists in range s:e +* TreeSlice[s1:e1] -> TreeSlice object, with keys in range s1 <= key < e1 + + * new lower bound is max(s, s1) + * new upper bound is min(e, e1) + +TreeSlice methods: + +* items() -> generator for (k, v) items of T, O(n) +* keys() -> generator for keys of T, O(n) +* values() -> generator for values of T, O(n) +* __iter__ <==> keys() +* __repr__ <==> repr(T) +* __contains__(key)-> True if TreeSlice has a key k, else False, O(log(n)) + +Heap methods + +* max_item() -> get biggest (key, value) pair of T, O(log(n)) +* max_key() -> get biggest key of T, O(log(n)) +* min_item() -> get smallest (key, value) pair of T, O(log(n)) +* min_key() -> get smallest key of T, O(log(n)) +* pop_min() -> (k, v), remove item with minimum key, O(log(n)) +* pop_max() -> (k, v), remove item with maximum key, O(log(n)) +* nlargest(i[,pop]) -> get list of i largest items (k, v), O(i*log(n)) +* nsmallest(i[,pop]) -> get list of i smallest items (k, v), O(i*log(n)) + +Set methods (using frozenset) + +* intersection(t1, t2, ...) -> Tree with keys *common* to all trees +* union(t1, t2, ...) -> Tree with keys from *either* trees +* difference(t1, t2, ...) -> Tree with keys in T but not any of t1, t2, ... +* symmetric_difference(t1) -> Tree with keys in either T and t1 but not both +* is_subset(S) -> True if every element in T is in S +* is_superset(S) -> True if every element in S is in T +* is_disjoint(S) -> True if T has a null intersection with S + +Classmethods + +* from_keys(S[,v]) -> New tree with keys from S and values equal to v. + +Helper functions + +* bintrees.has_fast_tree_support() -> True if Cython extension is working else False (False = using pure Python implementation) + +""" + +from .bintree import BinaryTree +from .avltree import AVLTree +from .rbtree import RBTree + + +def has_fast_tree_support(): + return FastBinaryTree is not BinaryTree + + +try: + from .cython_trees import FastBinaryTree +except ImportError: # fall back to pure Python version + FastBinaryTree = BinaryTree + +try: + from .cython_trees import FastAVLTree +except ImportError: # fall back to pure Python version + FastAVLTree = AVLTree + +try: + from .cython_trees import FastRBTree +except ImportError: # fall back to pure Python version + FastRBTree = RBTree diff --git a/bintrees/abctree.py b/bintrees/abctree.py new file mode 100644 index 0000000..5f16874 --- /dev/null +++ b/bintrees/abctree.py @@ -0,0 +1,820 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: Mozman +# Purpose: abstract base class for all binary trees +# Created: 03.05.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +from __future__ import absolute_import + +import sys +PYPY = hasattr(sys, 'pypy_version_info') + +from .treeslice import TreeSlice +from operator import attrgetter + + +class _ABCTree(object): + """ + Abstract-Base-Class for ABCTree and Cython trees. + + The _ABCTree Class + ================== + + T has to implement following properties + --------------------------------------- + + count -- get node count + + T has to implement following methods + ------------------------------------ + + get_value(...) + get_value(key) -> returns value for key + + insert(...) + insert(key, value) <==> T[key] = value, insert key into T + + remove(...) + remove(key) <==> del T[key], remove key from T + + clear(...) + T.clear() -> None. Remove all items from T. + + iter_items(...) + iter_items(s, e, [reverse]) -> iterate over all items with keys in range s <= key < e, yielding (k, v) tuple + + foreach(...) + foreach(f, [order]) -> visit all nodes of tree and call f(k, v) for each node + + pop_item(...) + T.pop_item() -> (k, v), remove and return some (key, value) + + min_item(...) + min_item() -> get smallest (key, value) pair of T + + max_item(...) + max_item() -> get largest (key, value) pair of T + + prev_item(...) + prev_item(key) -> get (k, v) pair, where k is predecessor to key + + succ_item(...) + succ_item(key) -> get (k,v) pair as a 2-tuple, where k is successor to key + + floor_item(...) + floor_item(key) -> get (k, v) pair, where k is the greatest key less than or equal to key + + ceiling_item(...) + ceiling_item(key) -> get (k, v) pair, where k is the smallest key greater than or equal to key + + Methods defined here + -------------------- + + * __contains__(k) -> True if T has a key k, else False, O(log(n)) + * __delitem__(y) <==> del T[y], del T[s:e], O(log(n)) + * __getitem__(y) <==> T[y], T[s:e], O(log(n)) + * __iter__() <==> iter(T) + * __len__() <==> len(T), O(1) + * __max__() <==> max(T), get max item (k,v) of T, O(log(n)) + * __min__() <==> min(T), get min item (k,v) of T, O(log(n)) + * __and__(other) <==> T & other, intersection + * __or__(other) <==> T | other, union + * __sub__(other) <==> T - other, difference + * __xor__(other) <==> T ^ other, symmetric_difference + * __repr__() <==> repr(T) + * __setitem__(k, v) <==> T[k] = v, O(log(n)) + * clear() -> None, remove all items from T, , O(n) + * remove_items(keys) -> None, remove items by keys + * copy() -> a shallow copy of T, O(n*log(n)) + * discard(k) -> None, remove k from T, if k is present, O(log(n)) + * get(k[,d]) -> T[k] if k in T, else d, O(log(n)) + * is_empty() -> True if len(T) == 0, O(1) + * keys([reverse]) -> generator for keys of T, O(n) + * values([reverse]) -> generator for values of T, O(n) + * pop(k[,d]) -> v, remove specified key and return the corresponding value, O(log(n)) + * set_default(k[,d]) -> value, T.get(k, d), also set T[k]=d if k not in T, O(log(n)) + * update(E) -> None. Update T from dict/iterable E, O(E*log(n)) + + slicing by keys + + * key_slice(s, e[, reverse]) -> generator for keys of T for s <= key < e, O(n) + * value_slice(s, e[, reverse]) -> generator for values of T for s <= key < e, O(n) + * item_slice(s, e[, reverse]) -> generator for items of T for s <= key < e, O(n) + * T[s:e] -> TreeSlice object, with keys in range s <= key < e, O(n) + * del T[s:e] -> remove items by key slicing, for s <= key < e, O(n) + + if 's' is None or T[:e] TreeSlice/iterator starts with value of min_key() + if 'e' is None or T[s:] TreeSlice/iterator ends with value of max_key() + T[:] is a TreeSlice which represents the whole tree. + + TreeSlice is a tree wrapper with range check and contains no references + to objects, deleting objects in the associated tree also deletes the object + in the TreeSlice. + + * TreeSlice[k] -> get value for key k, raises KeyError if k not exists in range s:e + * TreeSlice[s1:e1] -> TreeSlice object, with keys in range s1 <= key < e1 + + * new lower bound is max(s, s1) + * new upper bound is min(e, e1) + + TreeSlice methods: + + * items() -> generator for (k, v) items of T, O(n) + * keys() -> generator for keys of T, O(n) + * values() -> generator for values of T, O(n) + * __iter__ <==> keys() + * __repr__ <==> repr(T) + * __contains__(key)-> True if TreeSlice has a key k, else False, O(log(n)) + + prev/succ operations + + * prev_key(key) -> k, get the predecessor of key, O(log(n)) + * succ_key(key) -> k, get the successor of key, O(log(n)) + * floor_key(key) -> k, get the greatest key less than or equal to key, O(log(n)) + * ceiling_key(key) -> k, get the smallest key greater than or equal to key, O(log(n)) + + Heap methods + + * max_key() -> get largest key of T, O(log(n)) + * min_key() -> get smallest key of T, O(log(n)) + * pop_min() -> (k, v), remove item with minimum key, O(log(n)) + * pop_max() -> (k, v), remove item with maximum key, O(log(n)) + * nlargest(i[,pop]) -> get list of i largest items (k, v), O(i*log(n)) + * nsmallest(i[,pop]) -> get list of i smallest items (k, v), O(i*log(n)) + + Set methods (using frozenset) + + * intersection(t1, t2, ...) -> Tree with keys *common* to all trees + * union(t1, t2, ...) -> Tree with keys from *either* trees + * difference(t1, t2, ...) -> Tree with keys in T but not any of t1, t2, ... + * symmetric_difference(t1) -> Tree with keys in either T and t1 but not both + * is_subset(S) -> True if every element in T is in S + * is_superset(S) -> True if every element in S is in T + * is_disjoint(S) -> True if T has a null intersection with S + + Classmethods + + * from_keys(S[,v]) -> New tree with keys from S and values equal to v. + + """ + + def __repr__(self): + """T.__repr__(...) <==> repr(x)""" + tpl = "%s({%s})" % (self.__class__.__name__, '%s') + return tpl % ", ".join( ("%r: %r" % item for item in self.items()) ) + + def copy(self): + """T.copy() -> get a shallow copy of T.""" + tree = self.__class__() + self.foreach(tree.insert, order=-1) + return tree + __copy__ = copy + + def __contains__(self, key): + """k in T -> True if T has a key k, else False""" + try: + self.get_value(key) + return True + except KeyError: + return False + + def __len__(self): + """T.__len__() <==> len(x)""" + return self.count + + def __min__(self): + """T.__min__() <==> min(x)""" + return self.min_item() + + def __max__(self): + """T.__max__() <==> max(x)""" + return self.max_item() + + def __and__(self, other): + """T.__and__(other) <==> self & other""" + return self.intersection(other) + + def __or__(self, other): + """T.__or__(other) <==> self | other""" + return self.union(other) + + def __sub__(self, other): + """T.__sub__(other) <==> self - other""" + return self.difference(other) + + def __xor__(self, other): + """T.__xor__(other) <==> self ^ other""" + return self.symmetric_difference(other) + + def discard(self, key): + """T.discard(k) -> None, remove k from T, if k is present""" + try: + self.remove(key) + except KeyError: + pass + + def is_empty(self): + """T.is_empty() -> False if T contains any items else True""" + return self.count == 0 + + def keys(self, reverse=False): + """T.keys([reverse]) -> an iterator over the keys of T, in ascending + order if reverse is True, iterate in descending order, reverse defaults + to False + """ + return (item[0] for item in self.iter_items(reverse=reverse)) + __iter__ = keys + + def __reversed__(self): + return self.keys(reverse=True) + + def values(self, reverse=False): + """T.values([reverse]) -> an iterator over the values of T, in ascending order + if reverse is True, iterate in descending order, reverse defaults to False + """ + return (item[1] for item in self.iter_items(reverse=reverse)) + + def items(self, reverse=False): + """T.items([reverse]) -> an iterator over the (key, value) items of T, + in ascending order if reverse is True, iterate in descending order, + reverse defaults to False + """ + return self.iter_items(reverse=reverse) + + def __getitem__(self, key): + """T.__getitem__(y) <==> x[y]""" + if isinstance(key, slice): + return TreeSlice(self, key.start, key.stop) + else: + return self.get_value(key) + + def __setitem__(self, key, value): + """T.__setitem__(i, y) <==> x[i]=y""" + if isinstance(key, slice): + raise ValueError('setslice is not supported') + self.insert(key, value) + + def __delitem__(self, key): + """T.__delitem__(y) <==> del x[y]""" + if isinstance(key, slice): + self.remove_items(self.key_slice(key.start, key.stop)) + else: + self.remove(key) + + def remove_items(self, keys): + """T.remove_items(keys) -> None, remove items by keys""" + # convert generator to a tuple, because the content of the + # tree will be modified! + for key in tuple(keys): + self.remove(key) + + def key_slice(self, start_key, end_key, reverse=False): + """T.key_slice(start_key, end_key) -> key iterator: + start_key <= key < end_key. + + Yields keys in ascending order if reverse is False else in descending order. + """ + return (k for k, v in self.iter_items(start_key, end_key, reverse=reverse)) + + def value_slice(self, start_key, end_key, reverse=False): + """T.value_slice(start_key, end_key) -> value iterator: + start_key <= key < end_key. + + Yields values in ascending key order if reverse is False else in descending key order. + """ + return (v for k, v in self.iter_items(start_key, end_key, reverse=reverse)) + + def item_slice(self, start_key, end_key, reverse=False): + """T.item_slice(start_key, end_key) -> item iterator: + start_key <= key < end_key. + + Yields items in ascending key order if reverse is False else in descending key order. + """ + return self.iter_items(start_key, end_key, reverse) + + def __getstate__(self): + return dict(self.items()) + + def __setstate__(self, state): + # note for myself: this is called like __init__, so don't use clear() + # to remove existing data! + self._root = None + self._count = 0 + self.update(state) + + def set_default(self, key, default=None): + """T.set_default(k[,d]) -> T.get(k,d), also set T[k]=d if k not in T""" + try: + return self.get_value(key) + except KeyError: + self.insert(key, default) + return default + setdefault = set_default # for compatibility to dict() + + def update(self, *args): + """T.update(E) -> None. Update T from E : for (k, v) in E: T[k] = v""" + for items in args: + try: + generator = items.items() + except AttributeError: + generator = iter(items) + + for key, value in generator: + self.insert(key, value) + + @classmethod + def from_keys(cls, iterable, value=None): + """T.from_keys(S[,v]) -> New tree with keys from S and values equal to v.""" + tree = cls() + for key in iterable: + tree.insert(key, value) + return tree + fromkeys = from_keys # for compatibility to dict() + + def get(self, key, default=None): + """T.get(k[,d]) -> T[k] if k in T, else d. d defaults to None.""" + try: + return self.get_value(key) + except KeyError: + return default + + def pop(self, key, *args): + """T.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised + """ + if len(args) > 1: + raise TypeError("pop expected at most 2 arguments, got %d" % (1 + len(args))) + try: + value = self.get_value(key) + self.remove(key) + return value + except KeyError: + if len(args) == 0: + raise + else: + return args[0] + + def prev_key(self, key): + """Get predecessor to key, raises KeyError if key is min key + or key does not exist. + """ + return self.prev_item(key)[0] + + def succ_key(self, key): + """Get successor to key, raises KeyError if key is max key + or key does not exist. + """ + return self.succ_item(key)[0] + + def floor_key(self, key): + """Get the greatest key less than or equal to the given key, raises + KeyError if there is no such key. + """ + return self.floor_item(key)[0] + + def ceiling_key(self, key): + """Get the smallest key greater than or equal to the given key, raises + KeyError if there is no such key. + """ + return self.ceiling_item(key)[0] + + def pop_min(self): + """T.pop_min() -> (k, v), remove item with minimum key, raise ValueError + if T is empty. + """ + item = self.min_item() + self.remove(item[0]) + return item + + def pop_max(self): + """T.pop_max() -> (k, v), remove item with maximum key, raise ValueError + if T is empty. + """ + item = self.max_item() + self.remove(item[0]) + return item + + def min_key(self): + """Get min key of tree, raises ValueError if tree is empty. """ + return self.min_item()[0] + + def max_key(self): + """Get max key of tree, raises ValueError if tree is empty. """ + return self.max_item()[0] + + def nsmallest(self, n, pop=False): + """T.nsmallest(n) -> get list of n smallest items (k, v). + If pop is True, remove items from T. + """ + if pop: + return [self.pop_min() for _ in range(min(len(self), n))] + else: + items = self.items() + return [next(items) for _ in range(min(len(self), n))] + + def nlargest(self, n, pop=False): + """T.nlargest(n) -> get list of n largest items (k, v). + If pop is True remove items from T. + """ + if pop: + return [self.pop_max() for _ in range(min(len(self), n))] + else: + items = self.items(reverse=True) + return [next(items) for _ in range(min(len(self), n))] + + def intersection(self, *trees): + """T.intersection(t1, t2, ...) -> Tree, with keys *common* to all trees + """ + thiskeys = frozenset(self.keys()) + sets = _build_sets(trees) + rkeys = thiskeys.intersection(*sets) + return self.__class__(((key, self.get(key)) for key in rkeys)) + + def union(self, *trees): + """T.union(t1, t2, ...) -> Tree with keys from *either* trees + """ + thiskeys = frozenset(self.keys()) + rkeys = thiskeys.union(*_build_sets(trees)) + all_trees = [self] + all_trees.extend(trees) + return self.__class__(((key, _multi_tree_get(all_trees, key)) for key in rkeys)) + + def difference(self, *trees): + """T.difference(t1, t2, ...) -> Tree with keys in T but not any of t1, + t2, ... + """ + thiskeys = frozenset(self.keys()) + rkeys = thiskeys.difference(*_build_sets(trees)) + return self.__class__(((key, self.get(key)) for key in rkeys)) + + def symmetric_difference(self, tree): + """T.symmetric_difference(t1) -> Tree with keys in either T and t1 but + not both + """ + thiskeys = frozenset(self.keys()) + rkeys = thiskeys.symmetric_difference(frozenset(tree.keys())) + all_trees = [self, tree] + return self.__class__(((key, _multi_tree_get(all_trees, key)) for key in rkeys)) + + def is_subset(self, tree): + """T.issubset(tree) -> True if every element in x is in tree """ + thiskeys = frozenset(self.keys()) + return thiskeys.issubset(frozenset(tree.keys())) + issubset = is_subset # for compatibility to set() + + def is_superset(self, tree): + """T.issubset(tree) -> True if every element in tree is in x """ + thiskeys = frozenset(self.keys()) + return thiskeys.issuperset(frozenset(tree.keys())) + issuperset = is_superset # for compatibility to set() + + def is_disjoint(self, tree): + """T.isdisjoint(S) -> True if x has a null intersection with tree """ + thiskeys = frozenset(self.keys()) + return thiskeys.isdisjoint(frozenset(tree.keys())) + isdisjoint = is_disjoint # for compatibility to set() + + +def _build_sets(trees): + return [frozenset(tree.keys()) for tree in trees] + + +def _multi_tree_get(trees, key): + for tree in trees: + try: + return tree[key] + except KeyError: + pass + raise KeyError(key) + + +class CPYTHON_ABCTree(_ABCTree): + """ Base class for the Python implementation of trees. + + T has to implement following methods + ------------------------------------ + + insert(...) + insert(key, value) <==> T[key] = value, insert key into T + + remove(...) + remove(key) <==> del T[key], remove key from T + + Properties defined here + -------------------- + + * count -> get item count of tree + + Methods defined here + -------------------- + * __init__() Tree initializer + * get_value(key) -> returns value for key + * clear() -> None. Remove all items from tree. + * iter_items(start_key, end_key, [reverse]) -> iterate over all items, yielding (k, v) tuple + * foreach(f, [order]) -> visit all nodes of tree and call f(k, v) for each node, O(n) + * pop_item() -> (k, v), remove and return some (key, value) + * min_item() -> get smallest (key, value) pair of T, O(log(n)) + * max_item() -> get largest (key, value) pair of T, O(log(n)) + * prev_item(key) -> get (k, v) pair, where k is predecessor to key, O(log(n)) + * succ_item(key) -> get (k,v) pair as a 2-tuple, where k is successor to key, O(log(n)) + * floor_item(key) -> get (k, v) pair, where k is the greatest key less than or equal to key, O(log(n)) + * ceiling_item(key) -> get (k, v) pair, where k is the smallest key greater than or equal to key, O(log(n)) + """ + def __init__(self, items=None): + """T.__init__(...) initializes T; see T.__class__.__doc__ for signature""" + self._root = None + self._count = 0 + if items is not None: + self.update(items) + + def clear(self): + """T.clear() -> None. Remove all items from T.""" + def _clear(node): + if node is not None: + _clear(node.left) + _clear(node.right) + node.free() + _clear(self._root) + self._count = 0 + self._root = None + + @property + def count(self): + """Get items count.""" + return self._count + + def get_value(self, key): + node = self._root + while node is not None: + if key == node.key: + return node.value + elif key < node.key: + node = node.left + else: + node = node.right + raise KeyError(str(key)) + + def pop_item(self): + """T.pop_item() -> (k, v), remove and return some (key, value) pair as a + 2-tuple; but raise KeyError if T is empty. + """ + if self.is_empty(): + raise KeyError("pop_item(): tree is empty") + node = self._root + while True: + if node.left is not None: + node = node.left + elif node.right is not None: + node = node.right + else: + break + key = node.key + value = node.value + self.remove(key) + return key, value + popitem = pop_item # for compatibility to dict() + + def foreach(self, func, order=0): + """Visit all tree nodes and process key, value. + + parm func: function(key, value) + param int order: inorder = 0, preorder = -1, postorder = +1 + """ + def _traverse(node): + if order == -1: + func(node.key, node.value) + if node.left is not None: + _traverse(node.left) + if order == 0: + func(node.key, node.value) + if node.right is not None: + _traverse(node.right) + if order == +1: + func(node.key, node.value) + _traverse(self._root) + + def min_item(self): + """Get item with min key of tree, raises ValueError if tree is empty.""" + if self.is_empty(): + raise ValueError("Tree is empty") + node = self._root + while node.left is not None: + node = node.left + return node.key, node.value + + def max_item(self): + """Get item with max key of tree, raises ValueError if tree is empty.""" + if self.is_empty(): + raise ValueError("Tree is empty") + node = self._root + while node.right is not None: + node = node.right + return node.key, node.value + + def succ_item(self, key): + """Get successor (k,v) pair of key, raises KeyError if key is max key + or key does not exist. optimized for pypy. + """ + # removed graingets version, because it was little slower on CPython and much slower on pypy + # this version runs about 4x faster with pypy than the Cython version + # Note: Code sharing of succ_item() and ceiling_item() is possible, but has always a speed penalty. + node = self._root + succ_node = None + while node is not None: + if key == node.key: + break + elif key < node.key: + if (succ_node is None) or (node.key < succ_node.key): + succ_node = node + node = node.left + else: + node = node.right + + if node is None: # stay at dead end + raise KeyError(str(key)) + # found node of key + if node.right is not None: + # find smallest node of right subtree + node = node.right + while node.left is not None: + node = node.left + if succ_node is None: + succ_node = node + elif node.key < succ_node.key: + succ_node = node + elif succ_node is None: # given key is biggest in tree + raise KeyError(str(key)) + return succ_node.key, succ_node.value + + def prev_item(self, key): + """Get predecessor (k,v) pair of key, raises KeyError if key is min key + or key does not exist. optimized for pypy. + """ + # removed graingets version, because it was little slower on CPython and much slower on pypy + # this version runs about 4x faster with pypy than the Cython version + # Note: Code sharing of prev_item() and floor_item() is possible, but has always a speed penalty. + node = self._root + prev_node = None + + while node is not None: + if key == node.key: + break + elif key < node.key: + node = node.left + else: + if (prev_node is None) or (node.key > prev_node.key): + prev_node = node + node = node.right + + if node is None: # stay at dead end (None) + raise KeyError(str(key)) + # found node of key + if node.left is not None: + # find biggest node of left subtree + node = node.left + while node.right is not None: + node = node.right + if prev_node is None: + prev_node = node + elif node.key > prev_node.key: + prev_node = node + elif prev_node is None: # given key is smallest in tree + raise KeyError(str(key)) + return prev_node.key, prev_node.value + + def floor_item(self, key): + """Get the element (k,v) pair associated with the greatest key less + than or equal to the given key, raises KeyError if there is no such key. + """ + # Note: Code sharing of prev_item() and floor_item() is possible, but has always a speed penalty. + node = self._root + prev_node = None + while node is not None: + if key == node.key: + return node.key, node.value + elif key < node.key: + node = node.left + else: + if (prev_node is None) or (node.key > prev_node.key): + prev_node = node + node = node.right + # node must be None here + if prev_node: + return prev_node.key, prev_node.value + raise KeyError(str(key)) + + def ceiling_item(self, key): + """Get the element (k,v) pair associated with the smallest key greater + than or equal to the given key, raises KeyError if there is no such key. + """ + # Note: Code sharing of succ_item() and ceiling_item() is possible, but has always a speed penalty. + node = self._root + succ_node = None + while node is not None: + if key == node.key: + return node.key, node.value + elif key > node.key: + node = node.right + else: + if (succ_node is None) or (node.key < succ_node.key): + succ_node = node + node = node.left + # node must be None here + if succ_node: + return succ_node.key, succ_node.value + raise KeyError(str(key)) + + def iter_items(self, start_key=None, end_key=None, reverse=False): + """Iterates over the (key, value) items of the associated tree, + in ascending order if reverse is True, iterate in descending order, + reverse defaults to False""" + # optimized iterator (reduced method calls) - faster on CPython but slower on pypy + + if self.is_empty(): + return [] + if reverse: + return self._iter_items_backward(start_key, end_key) + else: + return self._iter_items_forward(start_key, end_key) + + def _iter_items_forward(self, start_key=None, end_key=None): + for item in self._iter_items(left=attrgetter("left"), right=attrgetter("right"), + start_key=start_key, end_key=end_key): + yield item + + def _iter_items_backward(self, start_key=None, end_key=None): + for item in self._iter_items(left=attrgetter("right"), right=attrgetter("left"), + start_key=start_key, end_key=end_key): + yield item + + def _iter_items(self, left=attrgetter("left"), right=attrgetter("right"), start_key=None, end_key=None): + node = self._root + stack = [] + go_left = True + in_range = self._get_in_range_func(start_key, end_key) + + while True: + if left(node) is not None and go_left: + stack.append(node) + node = left(node) + else: + if in_range(node.key): + yield node.key, node.value + if right(node) is not None: + node = right(node) + go_left = True + else: + if not len(stack): + return # all done + node = stack.pop() + go_left = False + + def _get_in_range_func(self, start_key, end_key): + if start_key is None and end_key is None: + return lambda x: True + else: + if start_key is None: + start_key = self.min_key() + if end_key is None: + return lambda x: x >= start_key + else: + return lambda x: start_key <= x < end_key + + +class PYPY_ABCTree(CPYTHON_ABCTree): + def iter_items(self, start_key=None, end_key=None, reverse=False): + """Iterates over the (key, value) items of the associated tree, + in ascending order if reverse is True, iterate in descending order, + reverse defaults to False""" + # optimized for pypy, but slower on CPython + if self.is_empty(): + return + direction = 1 if reverse else 0 + other = 1 - direction + go_down = True + stack = [] + node = self._root + in_range = self._get_in_range_func(start_key, end_key) + + while True: + if node[direction] is not None and go_down: + stack.append(node) + node = node[direction] + else: + if in_range(node.key): + yield node.key, node.value + if node[other] is not None: + node = node[other] + go_down = True + else: + if not len(stack): + return # all done + node = stack.pop() + go_down = False + +if PYPY: + ABCTree = PYPY_ABCTree +else: + ABCTree = CPYTHON_ABCTree diff --git a/bintrees/avltree.py b/bintrees/avltree.py new file mode 100644 index 0000000..0e7be85 --- /dev/null +++ b/bintrees/avltree.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman (python version) +# Purpose: avl tree module (Julienne Walker's unbounded none recursive algorithm) +# source: http://eternallyconfuzzled.com/tuts/datastructures/jsw_tut_avl.aspx +# Created: 01.05.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +# Conclusion of Julienne Walker + +# AVL trees are about as close to optimal as balanced binary search trees can +# get without eating up resources. You can rest assured that the O(log N) +# performance of binary search trees is guaranteed with AVL trees, but the extra +# bookkeeping required to maintain an AVL tree can be prohibitive, especially +# if deletions are common. Insertion into an AVL tree only requires one single +# or double rotation, but deletion could perform up to O(log N) rotations, as +# in the example of a worst case AVL (ie. Fibonacci) tree. However, those cases +# are rare, and still very fast. + +# AVL trees are best used when degenerate sequences are common, and there is +# little or no locality of reference in nodes. That basically means that +# searches are fairly random. If degenerate sequences are not common, but still +# possible, and searches are random then a less rigid balanced tree such as red +# black trees or Andersson trees are a better solution. If there is a significant +# amount of locality to searches, such as a small cluster of commonly searched +# items, a splay tree is theoretically better than all of the balanced trees +# because of its move-to-front design. + +from __future__ import absolute_import + +from .abctree import ABCTree +from array import array + +__all__ = ['AVLTree'] + +MAXSTACK = 32 + + +class Node(object): + """Internal object, represents a tree node.""" + __slots__ = ['left', 'right', 'balance', 'key', 'value'] + + def __init__(self, key=None, value=None): + self.left = None + self.right = None + self.key = key + self.value = value + self.balance = 0 + + def __getitem__(self, key): + """N.__getitem__(key) <==> x[key], where key is 0 (left) or 1 (right).""" + return self.left if key == 0 else self.right + + def __setitem__(self, key, value): + """N.__setitem__(key, value) <==> x[key]=value, where key is 0 (left) or 1 (right).""" + if key == 0: + self.left = value + else: + self.right = value + + def free(self): + """Remove all references.""" + self.left = None + self.right = None + self.key = None + self.value = None + + +def height(node): + return node.balance if node is not None else -1 + + +def jsw_single(root, direction): + other_side = 1 - direction + save = root[other_side] + root[other_side] = save[direction] + save[direction] = root + rlh = height(root.left) + rrh = height(root.right) + slh = height(save[other_side]) + root.balance = max(rlh, rrh) + 1 + save.balance = max(slh, root.balance) + 1 + return save + + +def jsw_double(root, direction): + other_side = 1 - direction + root[other_side] = jsw_single(root[other_side], other_side) + return jsw_single(root, direction) + + +class AVLTree(ABCTree): + """ + AVLTree implements a balanced binary tree with a dict-like interface. + + see: http://en.wikipedia.org/wiki/AVL_tree + + In computer science, an AVL tree is a self-balancing binary search tree, and + it is the first such data structure to be invented. In an AVL tree, the + heights of the two child subtrees of any node differ by at most one; + therefore, it is also said to be height-balanced. Lookup, insertion, and + deletion all take O(log n) time in both the average and worst cases, where n + is the number of nodes in the tree prior to the operation. Insertions and + deletions may require the tree to be rebalanced by one or more tree rotations. + + The AVL tree is named after its two inventors, G.M. Adelson-Velskii and E.M. + Landis, who published it in their 1962 paper "An algorithm for the + organization of information." + + AVLTree() -> new empty tree. + AVLTree(mapping) -> new tree initialized from a mapping + AVLTree(seq) -> new tree initialized from seq [(k1, v1), (k2, v2), ... (kn, vn)] + + see also abctree.ABCTree() class. + """ + def _new_node(self, key, value): + """Create a new tree node.""" + self._count += 1 + return Node(key, value) + + def insert(self, key, value): + """T.insert(key, value) <==> T[key] = value, insert key, value into tree.""" + if self._root is None: + self._root = self._new_node(key, value) + else: + node_stack = [] # node stack + dir_stack = array('I') # direction stack + done = False + top = 0 + node = self._root + # search for an empty link, save path + while True: + if key == node.key: # update existing item + node.value = value + return + direction = 1 if key > node.key else 0 + dir_stack.append(direction) + node_stack.append(node) + if node[direction] is None: + break + node = node[direction] + + # Insert a new node at the bottom of the tree + node[direction] = self._new_node(key, value) + + # Walk back up the search path + top = len(node_stack) - 1 + while (top >= 0) and not done: + direction = dir_stack[top] + other_side = 1 - direction + top_node = node_stack[top] + left_height = height(top_node[direction]) + right_height = height(top_node[other_side]) + + # Terminate or rebalance as necessary */ + if left_height - right_height == 0: + done = True + if left_height - right_height >= 2: + a = top_node[direction][direction] + b = top_node[direction][other_side] + + if height(a) >= height(b): + node_stack[top] = jsw_single(top_node, other_side) + else: + node_stack[top] = jsw_double(top_node, other_side) + + # Fix parent + if top != 0: + node_stack[top - 1][dir_stack[top - 1]] = node_stack[top] + else: + self._root = node_stack[0] + done = True + + # Update balance factors + top_node = node_stack[top] + left_height = height(top_node[direction]) + right_height = height(top_node[other_side]) + + top_node.balance = max(left_height, right_height) + 1 + top -= 1 + + def remove(self, key): + """T.remove(key) <==> del T[key], remove item from tree.""" + if self._root is None: + raise KeyError(str(key)) + else: + node_stack = [None] * MAXSTACK # node stack + dir_stack = array('I', [0] * MAXSTACK) # direction stack + top = 0 + node = self._root + + while True: + # Terminate if not found + if node is None: + raise KeyError(str(key)) + elif node.key == key: + break + + # Push direction and node onto stack + direction = 1 if key > node.key else 0 + dir_stack[top] = direction + + node_stack[top] = node + node = node[direction] + top += 1 + + # Remove the node + if (node.left is None) or (node.right is None): + # Which child is not null? + direction = 1 if node.left is None else 0 + + # Fix parent + if top != 0: + node_stack[top - 1][dir_stack[top - 1]] = node[direction] + else: + self._root = node[direction] + node.free() + self._count -= 1 + else: + # Find the inorder successor + heir = node.right + + # Save the path + dir_stack[top] = 1 + node_stack[top] = node + top += 1 + + while heir.left is not None: + dir_stack[top] = 0 + node_stack[top] = heir + top += 1 + heir = heir.left + + # Swap data + node.key = heir.key + node.value = heir.value + + # Unlink successor and fix parent + xdir = 1 if node_stack[top - 1].key == node.key else 0 + node_stack[top - 1][xdir] = heir.right + heir.free() + self._count -= 1 + + # Walk back up the search path + top -= 1 + while top >= 0: + direction = dir_stack[top] + other_side = 1 - direction + top_node = node_stack[top] + left_height = height(top_node[direction]) + right_height = height(top_node[other_side]) + b_max = max(left_height, right_height) + + # Update balance factors + top_node.balance = b_max + 1 + + # Terminate or rebalance as necessary + if (left_height - right_height) == -1: + break + if (left_height - right_height) <= -2: + a = top_node[other_side][direction] + b = top_node[other_side][other_side] + if height(a) <= height(b): + node_stack[top] = jsw_single(top_node, direction) + else: + node_stack[top] = jsw_double(top_node, direction) + # Fix parent + if top != 0: + node_stack[top - 1][dir_stack[top - 1]] = node_stack[top] + else: + self._root = node_stack[0] + top -= 1 diff --git a/bintrees/bintree.py b/bintrees/bintree.py new file mode 100644 index 0000000..846bee3 --- /dev/null +++ b/bintrees/bintree.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Purpose: binary tree module +# Created: 28.04.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +from __future__ import absolute_import + +from .abctree import ABCTree + +__all__ = ['BinaryTree'] + + +class Node(object): + """Internal object, represents a tree node.""" + __slots__ = ['key', 'value', 'left', 'right'] + + def __init__(self, key, value): + self.key = key + self.value = value + self.left = None + self.right = None + + def __getitem__(self, key): + """N.__getitem__(key) <==> x[key], where key is 0 (left) or 1 (right).""" + return self.left if key == 0 else self.right + + def __setitem__(self, key, value): + """N.__setitem__(key, value) <==> x[key]=value, where key is 0 (left) or 1 (right).""" + if key == 0: + self.left = value + else: + self.right = value + + def free(self): + """Set references to None.""" + self.left = None + self.right = None + self.value = None + self.key = None + + +class BinaryTree(ABCTree): + """ + BinaryTree implements an unbalanced binary tree with a dict-like interface. + + see: http://en.wikipedia.org/wiki/Binary_tree + + A binary tree is a tree data structure in which each node has at most two + children. + + BinaryTree() -> new empty tree. + BinaryTree(mapping,) -> new tree initialized from a mapping + BinaryTree(seq) -> new tree initialized from seq [(k1, v1), (k2, v2), ... (kn, vn)] + + see also abctree.ABCTree() class. + """ + def _new_node(self, key, value): + """Create a new tree node.""" + self._count += 1 + return Node(key, value) + + def insert(self, key, value): + """T.insert(key, value) <==> T[key] = value, insert key, value into tree.""" + if self._root is None: + self._root = self._new_node(key, value) + else: + parent = None + direction = 0 + node = self._root + while True: + if node is None: + parent[direction] = self._new_node(key, value) + break + if key == node.key: + node.value = value # replace value + break + else: + parent = node + direction = 0 if key <= node.key else 1 + node = node[direction] + + def remove(self, key): + """T.remove(key) <==> del T[key], remove item from tree.""" + node = self._root + if node is None: + raise KeyError(str(key)) + else: + parent = None + direction = 0 + while True: + if key == node.key: + # remove node + if (node.left is not None) and (node.right is not None): + # find replacment node: smallest key in right-subtree + parent = node + direction = 1 + replacement = node.right + while replacement.left is not None: + parent = replacement + direction = 0 + replacement = replacement.left + parent[direction] = replacement.right + #swap places + node.key = replacement.key + node.value = replacement.value + node = replacement # delete replacement! + else: + down_dir = 1 if node.left is None else 0 + if parent is None: # root + self._root = node[down_dir] + else: + parent[direction] = node[down_dir] + node.free() + self._count -= 1 + break + else: + direction = 0 if key < node.key else 1 + parent = node + node = node[direction] + if node is None: + raise KeyError(str(key)) + diff --git a/bintrees/ctrees.c b/bintrees/ctrees.c new file mode 100644 index 0000000..61c8092 --- /dev/null +++ b/bintrees/ctrees.c @@ -0,0 +1,791 @@ +/* + * ctrees.c + * + * Author: mozman + * Copyright (c) 2010-2013 by Manfred Moitzi + * License: MIT-License + */ + +#include "ctrees.h" +#include + +#define LEFT 0 +#define RIGHT 1 +#define KEY(node) (node->key) +#define VALUE(node) (node->value) +#define LEFT_NODE(node) (node->link[LEFT]) +#define RIGHT_NODE(node) (node->link[RIGHT]) +#define LINK(node, dir) (node->link[dir]) +#define XDATA(node) (node->xdata) +#define RED(node) (node->xdata) +#define BALANCE(node) (node->xdata) + +static node_t * +ct_new_node(PyObject *key, PyObject *value, int xdata) +{ + node_t *new_node = PyMem_Malloc(sizeof(node_t)); + if (new_node != NULL) { + KEY(new_node) = key; + Py_INCREF(key); + VALUE(new_node) = value; + Py_INCREF(value); + LEFT_NODE(new_node) = NULL; + RIGHT_NODE(new_node) = NULL; + XDATA(new_node) = xdata; + } + return new_node; +} + +static void +ct_delete_node(node_t *node) +{ + if (node != NULL) { + Py_XDECREF(KEY(node)); + Py_XDECREF(VALUE(node)); + LEFT_NODE(node) = NULL; + RIGHT_NODE(node) = NULL; + PyMem_Free(node); + } +} + +extern void +ct_delete_tree(node_t *root) +{ + if (root == NULL) + return; + if (LEFT_NODE(root) != NULL) { + ct_delete_tree(LEFT_NODE(root)); + } + if (RIGHT_NODE(root) != NULL) { + ct_delete_tree(RIGHT_NODE(root)); + } + ct_delete_node(root); +} + +static void +ct_swap_data(node_t *node1, node_t *node2) +{ + PyObject *tmp; + tmp = KEY(node1); + KEY(node1) = KEY(node2); + KEY(node2) = tmp; + tmp = VALUE(node1); + VALUE(node1) = VALUE(node2); + VALUE(node2) = tmp; +} + +int +ct_compare(PyObject *key1, PyObject *key2) +{ + int res; + + res = PyObject_RichCompareBool(key1, key2, Py_LT); + if (res > 0) + return -1; + else if (res < 0) { + PyErr_SetString(PyExc_TypeError, "invalid type for key"); + return 0; + } + /* second compare: + +1 if key1 > key2 + 0 if not -> equal + -1 means error, if error, it should happen at the first compare + */ + return PyObject_RichCompareBool(key1, key2, Py_GT); +} + +extern node_t * +ct_find_node(node_t *root, PyObject *key) +{ + int res; + while (root != NULL) { + res = ct_compare(key, KEY(root)); + if (res == 0) /* key found */ + return root; + else { + root = LINK(root, (res > 0)); + } + } + return NULL; /* key not found */ +} + +extern node_t* +ct_get_leaf_node(node_t *node) +{ + if (node == NULL) + return NULL; + for(;;) { + if (LEFT_NODE(node) != NULL) + node = LEFT_NODE(node); + else if (RIGHT_NODE(node) != NULL) + node = RIGHT_NODE(node); + else return node; + } +} + +extern PyObject * +ct_get_item(node_t *root, PyObject *key) +{ + node_t *node; + PyObject *tuple; + + node = ct_find_node(root, key); + if (node != NULL) { + tuple = PyTuple_New(2); + PyTuple_SET_ITEM(tuple, 0, KEY(node)); + PyTuple_SET_ITEM(tuple, 1, VALUE(node)); + return tuple; + } + Py_RETURN_NONE; +} + +extern node_t * +ct_max_node(node_t *root) +/* get node with largest key */ +{ + if (root == NULL) + return NULL; + while (RIGHT_NODE(root) != NULL) + root = RIGHT_NODE(root); + return root; +} + +extern node_t * +ct_min_node(node_t *root) +// get node with smallest key +{ + if (root == NULL) + return NULL; + while (LEFT_NODE(root) != NULL) + root = LEFT_NODE(root); + return root; +} + +extern int +ct_bintree_remove(node_t **rootaddr, PyObject *key) +/* attention: rootaddr is the address of the root pointer */ +{ + node_t *node, *parent, *replacement; + int direction, cmp_res, down_dir; + + node = *rootaddr; + + if (node == NULL) + return 0; /* root is NULL */ + parent = NULL; + direction = 0; + + while (1) { + cmp_res = ct_compare(key, KEY(node)); + if (cmp_res == 0) /* key found, remove node */ + { + if ((LEFT_NODE(node) != NULL) && (RIGHT_NODE(node) != NULL)) { + /* find replacement node: smallest key in right-subtree */ + parent = node; + direction = RIGHT; + replacement = RIGHT_NODE(node); + while (LEFT_NODE(replacement) != NULL) { + parent = replacement; + direction = LEFT; + replacement = LEFT_NODE(replacement); + } + LINK(parent, direction) = RIGHT_NODE(replacement); + /* swap places */ + ct_swap_data(node, replacement); + node = replacement; /* delete replacement node */ + } + else { + down_dir = (LEFT_NODE(node) == NULL) ? RIGHT : LEFT; + if (parent == NULL) /* root */ + { + *rootaddr = LINK(node, down_dir); + } + else { + LINK(parent, direction) = LINK(node, down_dir); + } + } + ct_delete_node(node); + return 1; /* remove was success full */ + } + else { + direction = (cmp_res < 0) ? LEFT : RIGHT; + parent = node; + node = LINK(node, direction); + if (node == NULL) + return 0; /* error key not found */ + } + } +} + +extern int +ct_bintree_insert(node_t **rootaddr, PyObject *key, PyObject *value) +/* attention: rootaddr is the address of the root pointer */ +{ + node_t *parent, *node; + int direction, cval; + node = *rootaddr; + if (node == NULL) { + node = ct_new_node(key, value, 0); /* new node is also the root */ + if (node == NULL) + return -1; /* got no memory */ + *rootaddr = node; + } + else { + direction = LEFT; + parent = NULL; + while (1) { + if (node == NULL) { + node = ct_new_node(key, value, 0); + if (node == NULL) + return -1; /* get no memory */ + LINK(parent, direction) = node; + return 1; + } + cval = ct_compare(key, KEY(node)); + if (cval == 0) { + /* key exists, replace value object */ + Py_XDECREF(VALUE(node)); /* release old value object */ + VALUE(node) = value; /* set new value object */ + Py_INCREF(value); /* take new value object */ + return 0; + } + else { + parent = node; + direction = (cval < 0) ? LEFT : RIGHT; + node = LINK(node, direction); + } + } + } + return 1; +} + +static int +is_red (node_t *node) +{ + return (node != NULL) && (RED(node) == 1); +} + +#define rb_new_node(key, value) ct_new_node(key, value, 1) + +static node_t * +rb_single(node_t *root, int dir) +{ + node_t *save = root->link[!dir]; + + root->link[!dir] = save->link[dir]; + save->link[dir] = root; + + RED(root) = 1; + RED(save) = 0; + return save; +} + +static node_t * +rb_double(node_t *root, int dir) +{ + root->link[!dir] = rb_single(root->link[!dir], !dir); + return rb_single(root, dir); +} + +#define rb_new_node(key, value) ct_new_node(key, value, 1) + +extern int +rb_insert(node_t **rootaddr, PyObject *key, PyObject *value) +{ + int new_node = 0; + node_t *root = *rootaddr; + + if (root == NULL) { + /* + We have an empty tree; attach the + new node directly to the root + */ + root = rb_new_node(key, value); + new_node = 1; + if (root == NULL) + return -1; // got no memory + } + else { + node_t head; /* False tree root */ + node_t *g, *t; /* Grandparent & parent */ + node_t *p, *q; /* Iterator & parent */ + int dir = 0; + int last = 0; + + /* Set up our helpers */ + t = &head; + g = NULL; + p = NULL; + RIGHT_NODE(t) = root; + LEFT_NODE(t) = NULL; + q = RIGHT_NODE(t); + + /* Search down the tree for a place to insert */ + for (;;) { + int cmp_res; + if (q == NULL) { + /* Insert a new node at the first null link */ + q = rb_new_node(key, value); + new_node = 1; + p->link[dir] = q; + if (q == NULL) + return -1; // get no memory + } + else if (is_red(q->link[0]) && is_red(q->link[1])) { + /* Simple red violation: color flip */ + RED(q) = 1; + RED(q->link[0]) = 0; + RED(q->link[1]) = 0; + } + + if (is_red(q) && is_red(p)) { + /* Hard red violation: rotations necessary */ + int dir2 = (t->link[1] == g); + + if (q == p->link[last]) + t->link[dir2] = rb_single(g, !last); + else + t->link[dir2] = rb_double(g, !last); + } + + /* Stop working if we inserted a new node. */ + if (new_node) + break; + + cmp_res = ct_compare(KEY(q), key); + if (cmp_res == 0) { /* if key exists */ + Py_XDECREF(VALUE(q)); /* release old value object */ + VALUE(q) = value; /* set new value object */ + Py_INCREF(value); /* take new value object */ + break; + } + last = dir; + dir = (cmp_res < 0); + + /* Move the helpers down */ + if (g != NULL) + t = g; + + g = p; + p = q; + q = q->link[dir]; + } + /* Update the root (it may be different) */ + root = head.link[1]; + } + + /* Make the root black for simplified logic */ + RED(root) = 0; + (*rootaddr) = root; + return new_node; +} + +extern int +rb_remove(node_t **rootaddr, PyObject *key) +{ + node_t *root = *rootaddr; + + node_t head = { { NULL } }; /* False tree root */ + node_t *q, *p, *g; /* Helpers */ + node_t *f = NULL; /* Found item */ + int dir = 1; + + if (root == NULL) + return 0; + + /* Set up our helpers */ + q = &head; + g = p = NULL; + RIGHT_NODE(q) = root; + + /* + Search and push a red node down + to fix red violations as we go + */ + while (q->link[dir] != NULL) { + int last = dir; + int cmp_res; + + /* Move the helpers down */ + g = p, p = q; + q = q->link[dir]; + + cmp_res = ct_compare(KEY(q), key); + + dir = cmp_res < 0; + + /* + Save the node with matching data and keep + going; we'll do removal tasks at the end + */ + if (cmp_res == 0) + f = q; + + /* Push the red node down with rotations and color flips */ + if (!is_red(q) && !is_red(q->link[dir])) { + if (is_red(q->link[!dir])) + p = p->link[last] = rb_single(q, dir); + else if (!is_red(q->link[!dir])) { + node_t *s = p->link[!last]; + + if (s != NULL) { + if (!is_red(s->link[!last]) && + !is_red(s->link[last])) { + /* Color flip */ + RED(p) = 0; + RED(s) = 1; + RED(q) = 1; + } + else { + int dir2 = g->link[1] == p; + + if (is_red(s->link[last])) + g->link[dir2] = rb_double(p, last); + else if (is_red(s->link[!last])) + g->link[dir2] = rb_single(p, last); + + /* Ensure correct coloring */ + RED(q) = RED(g->link[dir2]) = 1; + RED(g->link[dir2]->link[0]) = 0; + RED(g->link[dir2]->link[1]) = 0; + } + } + } + } + } + + /* Replace and remove the saved node */ + if (f != NULL) { + ct_swap_data(f, q); + p->link[p->link[1] == q] = q->link[q->link[0] == NULL]; + ct_delete_node(q); + } + + /* Update the root (it may be different) */ + root = head.link[1]; + + /* Make the root black for simplified logic */ + if (root != NULL) + RED(root) = 0; + *rootaddr = root; + return (f != NULL); +} + +#define avl_new_node(key, value) ct_new_node(key, value, 0) +#define height(p) ((p) == NULL ? -1 : (p)->xdata) +#define avl_max(a, b) ((a) > (b) ? (a) : (b)) + +static node_t * +avl_single(node_t *root, int dir) +{ + node_t *save = root->link[!dir]; + int rlh, rrh, slh; + + /* Rotate */ + root->link[!dir] = save->link[dir]; + save->link[dir] = root; + + /* Update balance factors */ + rlh = height(root->link[0]); + rrh = height(root->link[1]); + slh = height(save->link[!dir]); + + BALANCE(root) = avl_max(rlh, rrh) + 1; + BALANCE(save) = avl_max(slh, BALANCE(root)) + 1; + + return save; +} + +static node_t * +avl_double(node_t *root, int dir) +{ + root->link[!dir] = avl_single(root->link[!dir], !dir); + return avl_single(root, dir); +} + +extern int +avl_insert(node_t **rootaddr, PyObject *key, PyObject *value) +{ + node_t *root = *rootaddr; + + if (root == NULL) { + root = avl_new_node(key, value); + if (root == NULL) + return -1; // got no memory + } + else { + node_t *it, *up[32]; + int upd[32], top = 0; + int done = 0; + int cmp_res; + + it = root; + /* Search for an empty link, save the path */ + for (;;) { + /* Push direction and node onto stack */ + cmp_res = ct_compare(KEY(it), key); + if (cmp_res == 0) { + Py_XDECREF(VALUE(it)); // release old value object + VALUE(it) = value; // set new value object + Py_INCREF(value); // take new value object + return 0; + } + // upd[top] = it->data < data; + upd[top] = (cmp_res < 0); + up[top++] = it; + + if (it->link[upd[top - 1]] == NULL) + break; + it = it->link[upd[top - 1]]; + } + + /* Insert a new node at the bottom of the tree */ + it->link[upd[top - 1]] = avl_new_node(key, value); + if (it->link[upd[top - 1]] == NULL) + return -1; // got no memory + + /* Walk back up the search path */ + while (--top >= 0 && !done) { + // int dir = (cmp_res < 0); + int lh, rh, max; + + cmp_res = ct_compare(KEY(up[top]), key); + + lh = height(up[top]->link[upd[top]]); + rh = height(up[top]->link[!upd[top]]); + + /* Terminate or rebalance as necessary */ + if (lh - rh == 0) + done = 1; + if (lh - rh >= 2) { + node_t *a = up[top]->link[upd[top]]->link[upd[top]]; + node_t *b = up[top]->link[upd[top]]->link[!upd[top]]; + + if (height( a ) >= height( b )) + up[top] = avl_single(up[top], !upd[top]); + else + up[top] = avl_double(up[top], !upd[top]); + + /* Fix parent */ + if (top != 0) + up[top - 1]->link[upd[top - 1]] = up[top]; + else + root = up[0]; + done = 1; + } + /* Update balance factors */ + lh = height(up[top]->link[upd[top]]); + rh = height(up[top]->link[!upd[top]]); + max = avl_max(lh, rh); + BALANCE(up[top]) = max + 1; + } + } + (*rootaddr) = root; + return 1; +} + +extern int +avl_remove(node_t **rootaddr, PyObject *key) +{ + node_t *root = *rootaddr; + int cmp_res; + + if (root != NULL) { + node_t *it, *up[32]; + int upd[32], top = 0; + + it = root; + for (;;) { + /* Terminate if not found */ + if (it == NULL) + return 0; + cmp_res = ct_compare(KEY(it), key); + if (cmp_res == 0) + break; + + /* Push direction and node onto stack */ + upd[top] = (cmp_res < 0); + up[top++] = it; + it = it->link[upd[top - 1]]; + } + + /* Remove the node */ + if (it->link[0] == NULL || + it->link[1] == NULL) { + /* Which child is not null? */ + int dir = it->link[0] == NULL; + + /* Fix parent */ + if (top != 0) + up[top - 1]->link[upd[top - 1]] = it->link[dir]; + else + root = it->link[dir]; + + ct_delete_node(it); + } + else { + /* Find the inorder successor */ + node_t *heir = it->link[1]; + + /* Save the path */ + upd[top] = 1; + up[top++] = it; + + while ( heir->link[0] != NULL ) { + upd[top] = 0; + up[top++] = heir; + heir = heir->link[0]; + } + /* Swap data */ + ct_swap_data(it, heir); + /* Unlink successor and fix parent */ + up[top - 1]->link[up[top - 1] == it] = heir->link[1]; + ct_delete_node(heir); + } + + /* Walk back up the search path */ + while (--top >= 0) { + int lh = height(up[top]->link[upd[top]]); + int rh = height(up[top]->link[!upd[top]]); + int max = avl_max(lh, rh); + + /* Update balance factors */ + BALANCE(up[top]) = max + 1; + + /* Terminate or rebalance as necessary */ + if (lh - rh == -1) + break; + if (lh - rh <= -2) { + node_t *a = up[top]->link[!upd[top]]->link[upd[top]]; + node_t *b = up[top]->link[!upd[top]]->link[!upd[top]]; + + if (height(a) <= height(b)) + up[top] = avl_single(up[top], upd[top]); + else + up[top] = avl_double(up[top], upd[top]); + + /* Fix parent */ + if (top != 0) + up[top - 1]->link[upd[top - 1]] = up[top]; + else + root = up[0]; + } + } + } + (*rootaddr) = root; + return 1; +} + +extern node_t * +ct_succ_node(node_t *root, PyObject *key) +{ + node_t *succ = NULL; + node_t *node = root; + int cval; + + while (node != NULL) { + cval = ct_compare(key, KEY(node)); + if (cval == 0) + break; + else if (cval < 0) { + if ((succ == NULL) || + (ct_compare(KEY(node), KEY(succ)) < 0)) + succ = node; + node = LEFT_NODE(node); + } else + node = RIGHT_NODE(node); + } + if (node == NULL) + return NULL; + /* found node of key */ + if (RIGHT_NODE(node) != NULL) { + /* find smallest node of right subtree */ + node = RIGHT_NODE(node); + while (LEFT_NODE(node) != NULL) + node = LEFT_NODE(node); + if (succ == NULL) + succ = node; + else if (ct_compare(KEY(node), KEY(succ)) < 0) + succ = node; + } + return succ; +} + +extern node_t * +ct_prev_node(node_t *root, PyObject *key) +{ + node_t *prev = NULL; + node_t *node = root; + int cval; + + while (node != NULL) { + cval = ct_compare(key, KEY(node)); + if (cval == 0) + break; + else if (cval < 0) + node = LEFT_NODE(node); + else { + if ((prev == NULL) || (ct_compare(KEY(node), KEY(prev)) > 0)) + prev = node; + node = RIGHT_NODE(node); + } + } + if (node == NULL) /* stay at dead end (None) */ + return NULL; + /* found node of key */ + if (LEFT_NODE(node) != NULL) { + /* find biggest node of left subtree */ + node = LEFT_NODE(node); + while (RIGHT_NODE(node) != NULL) + node = RIGHT_NODE(node); + if (prev == NULL) + prev = node; + else if (ct_compare(KEY(node), KEY(prev)) > 0) + prev = node; + } + return prev; +} + +extern node_t * +ct_floor_node(node_t *root, PyObject *key) +{ + node_t *prev = NULL; + node_t *node = root; + int cval; + + while (node != NULL) { + cval = ct_compare(key, KEY(node)); + if (cval == 0) + return node; + else if (cval < 0) + node = LEFT_NODE(node); + else { + if ((prev == NULL) || (ct_compare(KEY(node), KEY(prev)) > 0)) + prev = node; + node = RIGHT_NODE(node); + } + } + return prev; +} + +extern node_t * +ct_ceiling_node(node_t *root, PyObject *key) +{ + node_t *succ = NULL; + node_t *node = root; + int cval; + + while (node != NULL) { + cval = ct_compare(key, KEY(node)); + if (cval == 0) + return node; + else if (cval < 0) { + if ((succ == NULL) || + (ct_compare(KEY(node), KEY(succ)) < 0)) + succ = node; + node = LEFT_NODE(node); + } else + node = RIGHT_NODE(node); + } + return succ; +} diff --git a/bintrees/ctrees.h b/bintrees/ctrees.h new file mode 100644 index 0000000..3dbbb00 --- /dev/null +++ b/bintrees/ctrees.h @@ -0,0 +1,50 @@ +/* + * ctrees.h + * + * Author: mozman + * Copyright (c) 2010-2013 by Manfred Moitzi + * License: MIT-License + */ + +#ifndef __CTREES_H +#define __CTREES_H + +#include + +typedef struct tree_node node_t; + +struct tree_node { + node_t *link[2]; + PyObject *key; + PyObject *value; + int xdata; +}; + +typedef node_t* nodeptr; + +/* common binary tree functions */ +void ct_delete_tree(node_t *root); +int ct_compare(PyObject *key1, PyObject *key2); +PyObject *ct_get_item(node_t *root, PyObject *key); +node_t *ct_find_node(node_t *root, PyObject *key); +node_t *ct_get_leaf_node(node_t *node); +node_t *ct_succ_node(node_t *root, PyObject *key); +node_t *ct_prev_node(node_t *root, PyObject *key); +node_t *ct_max_node(node_t *root); +node_t *ct_min_node(node_t *root); +node_t *ct_floor_node(node_t *root, PyObject *key); +node_t *ct_ceiling_node(node_t *root, PyObject *key); + +/* unbalanced binary tree */ +int ct_bintree_insert(node_t **root, PyObject *key, PyObject *value); +int ct_bintree_remove(node_t **root, PyObject *key); + +/* avl-tree functions */ +int avl_insert(node_t **root, PyObject *key, PyObject *value); +int avl_remove(node_t **root, PyObject *key); + +/* rb-tree functions */ +int rb_insert(node_t **root, PyObject *key, PyObject *value); +int rb_remove(node_t **root, PyObject *key); + +#endif diff --git a/bintrees/ctrees.pxd b/bintrees/ctrees.pxd new file mode 100644 index 0000000..224d23d --- /dev/null +++ b/bintrees/ctrees.pxd @@ -0,0 +1,39 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Created: 08.05.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +cdef extern from "ctrees.h": + ctypedef struct PyObject: + pass + + ctypedef struct node_t: + node_t *link[2] + PyObject *key + PyObject *value + + int ct_compare(object key1, object key2) + void ct_delete_tree(node_t *root) + node_t *ct_find_node(node_t *root, object key) + node_t *ct_get_leaf_node(node_t *node) + PyObject *ct_get_item(node_t *root, object key) + node_t *ct_max_node(node_t *root) + node_t *ct_min_node(node_t *root) + node_t *ct_succ_node(node_t *root, object key) + node_t *ct_prev_node(node_t *root, object key) + node_t *ct_floor_node(node_t *root, object key) + node_t *ct_ceiling_node(node_t *root, object key) + int ct_index_of(node_t *root, object key) + node_t *ct_node_at(node_t *root, int index) + + # binary-tree functions + int ct_bintree_insert(node_t **root, object key, object value) + int ct_bintree_remove(node_t **root, object key) + # avl-tree functions + int avl_insert(node_t **root, object key, object value) + int avl_remove(node_t **root, object key) + # rb-tree functions + int rb_insert(node_t **root, object key, object value) + int rb_remove(node_t **root, object key) \ No newline at end of file diff --git a/bintrees/cython_trees.pyx b/bintrees/cython_trees.pyx new file mode 100644 index 0000000..f38fa5d --- /dev/null +++ b/bintrees/cython_trees.pyx @@ -0,0 +1,265 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Purpose: Binary trees implemented in Cython/C +# Created: 28.04.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +from .abctree import _ABCTree +from ctrees cimport * + +DEF MAXSTACK = 64 + +cdef class NodeStack: + """Simple stack for tree nodes.""" + cdef node_t* stack[MAXSTACK] + cdef int stackptr + + def __cinit__(self): + self.stackptr = 0 + + cdef push(self, node_t* node): + if self.stackptr >= MAXSTACK: + raise RuntimeError("Stack overflow in NodeStack.push().") + self.stack[self.stackptr] = node + self.stackptr += 1 + + cdef node_t* pop(self): + if self.stackptr <= 0: + raise RuntimeError("Stack underflow in NodeStack.pop().") + self.stackptr -= 1 + return self.stack[self.stackptr] + + cdef bint is_empty(self): + return self.stackptr == 0 + +cdef class _BaseTree: + cdef node_t *root # private (hidden) for CPython + cdef readonly int count # public readonly access for CPython + + def __cinit__(self, items=None): + self.root = NULL + self.count = 0 + + def __init__(self, items=None): + if items is not None: + self.update(items) + + def __dealloc__(self): + ct_delete_tree(self.root) + + def __getstate__(self): + return dict(self.items()) + + def __setstate__(self, state): + self.update(state) + + def clear(self): + ct_delete_tree(self.root) + self.count = 0 + self.root = NULL + + def get_value(self, key): + cdef node_t *result = ct_find_node(self.root, key) + if result == NULL: + raise KeyError(key) + else: + return result.value + + def max_item(self): + """Get item with max key of tree, raises ValueError if tree is empty.""" + cdef node_t *node = ct_max_node(self.root) + if node == NULL: + raise ValueError("Tree is empty") + return node.key, node.value + + def min_item(self): + """Get item with min key of tree, raises ValueError if tree is empty.""" + cdef node_t *node = ct_min_node(self.root) + if node == NULL: + raise ValueError("Tree is empty") + return node.key, node.value + + def succ_item(self, key): + """Get successor (k,v) pair of key, raises KeyError if key is max key + or key does not exist. + """ + cdef node_t *node = ct_succ_node(self.root, key) + if node == NULL: # given key is biggest in tree + raise KeyError(str(key)) + return node.key, node.value + + def prev_item(self, key): + """Get predecessor (k,v) pair of key, raises KeyError if key is min key + or key does not exist. + """ + cdef node_t *node = ct_prev_node(self.root, key) + if node == NULL: # given key is smallest in tree + raise KeyError(str(key)) + return node.key, node.value + + def floor_item(self, key): + """Get (k,v) pair associated with the greatest key less than or equal to + the given key, raises KeyError if there is no such key. + """ + cdef node_t *node = ct_floor_node(self.root, key) + if node == NULL: # given key is smaller than min-key in tree + raise KeyError(str(key)) + return node.key, node.value + + def ceiling_item(self, key): + """Get (k,v) pair associated with the smallest key greater than or equal to + the given key, raises KeyError if there is no such key. + """ + cdef node_t *node = ct_ceiling_node(self.root, key) + if node == NULL: # given key is greater than max-key in tree + raise KeyError(str(key)) + return node.key, node.value + + def iter_items(self, start_key=None, end_key=None, reverse=False): + """Iterate over the (key, value) items in ascending order + if reverse is True iterate in descending order. + """ + if self.count == 0: + return + cdef int direction = 1 if reverse else 0 + cdef int other = 1 - direction + cdef bint go_down = True + cdef NodeStack stack = NodeStack() + cdef node_t *node + + node = self.root + while True: + if node.link[direction] != NULL and go_down: + stack.push(node) + node = node.link[direction] + else: + if (start_key is None or ct_compare(start_key, node.key) < 1) and \ + (end_key is None or ct_compare(end_key, node.key) > 0): + yield node.key, node.value + if node.link[other] != NULL: + node = node.link[other] + go_down = True + else: + if stack.is_empty(): + return # all done + node = stack.pop() + go_down = False + + def pop_item(self): + """ T.pop_item() -> (k, v), remove and return some (key, value) pair as a + 2-tuple; but raise KeyError if T is empty. + """ + if self.count == 0: + raise KeyError("pop_item(): tree is empty") + + cdef node_t *node = ct_get_leaf_node(self.root) + key = node.key + value = node.value + self.remove(key) + return key, value + popitem = pop_item # for compatibility to dict() + + def foreach(self, func, int order=0): + """Visit all tree nodes and process tree data by func(key, Value). + + parm func: function(key, value) + param int order: inorder = 0, preorder = -1, postorder = +1 + """ + if self.count == 0: + return + cdef NodeStack stack = NodeStack() + cdef NodeStack tempstack = NodeStack() + cdef node_t *node = self.root + + if order == 0: + while not stack.is_empty() or node: + if node: + stack.push(node) + node = node.link[0] + else: + node = stack.pop() + func(node.key, node.value) + node = node.link[1] + elif order == -1: + stack.push(node) + while not stack.is_empty(): + node = stack.pop() + func(node.key, node.value) + if node.link[1]: + stack.push(node.link[1]) + if node.link[0]: + stack.push(node.link[0]) + elif order == +1: + tempstack.push(node) + while not tempstack.is_empty(): + node = tempstack.pop() + stack.push(node) + if node.link[0]: + tempstack.push(node.link[0]) + if node.link[1]: + tempstack.push(node.link[1]) + while not stack.is_empty(): + node = stack.pop() + func(node.key, node.value) + + +cdef class _BinaryTree(_BaseTree): + def insert(self, key, value): + cdef int result = ct_bintree_insert(&self.root, key, value) + if result < 0: + raise MemoryError('Can not allocate memory for node structure.') + self.count += result + + def remove(self, key): + cdef int result + result = ct_bintree_remove(&self.root, key) + if result == 0: + raise KeyError(str(key)) + else: + self.count -= 1 + + +class FastBinaryTree(_BinaryTree, _ABCTree): + pass + + +cdef class _AVLTree(_BaseTree): + def insert(self, key, value): + cdef int result = avl_insert(&self.root, key, value) + if result < 0: + raise MemoryError('Can not allocate memory for node structure.') + else: + self.count += result + + def remove(self, key): + cdef int result = avl_remove(&self.root, key) + if result == 0: + raise KeyError(str(key)) + else: + self.count -= 1 + + +class FastAVLTree(_AVLTree, _ABCTree): + pass + + +cdef class _RBTree(_BaseTree): + def insert(self, key, value): + cdef int result = rb_insert(&self.root, key, value) + if result < 0: + raise MemoryError('Can not allocate memory for node structure.') + else: + self.count += result + + def remove(self, key): + cdef int result = rb_remove(&self.root, key) + if result == 0: + raise KeyError(str(key)) + else: + self.count -= 1 + + +class FastRBTree(_RBTree, _ABCTree): + pass diff --git a/bintrees/rbtree.py b/bintrees/rbtree.py new file mode 100644 index 0000000..4b01ccf --- /dev/null +++ b/bintrees/rbtree.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman (python version) +# Purpose: red-black tree module (Julienne Walker's none recursive algorithm) +# source: http://eternallyconfuzzled.com/tuts/datastructures/jsw_tut_rbtree.aspx +# Created: 01.05.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +# Conclusion of Julian Walker + +# Red black trees are interesting beasts. They're believed to be simpler than +# AVL trees (their direct competitor), and at first glance this seems to be the +# case because insertion is a breeze. However, when one begins to play with the +# deletion algorithm, red black trees become very tricky. However, the +# counterweight to this added complexity is that both insertion and deletion +# can be implemented using a single pass, top-down algorithm. Such is not the +# case with AVL trees, where only the insertion algorithm can be written top-down. +# Deletion from an AVL tree requires a bottom-up algorithm. + +# So when do you use a red black tree? That's really your decision, but I've +# found that red black trees are best suited to largely random data that has +# occasional degenerate runs, and searches have no locality of reference. This +# takes full advantage of the minimal work that red black trees perform to +# maintain balance compared to AVL trees and still allows for speedy searches. + +# Red black trees are popular, as most data structures with a whimsical name. +# For example, in Java and C++, the library map structures are typically +# implemented with a red black tree. Red black trees are also comparable in +# speed to AVL trees. While the balance is not quite as good, the work it takes +# to maintain balance is usually better in a red black tree. There are a few +# misconceptions floating around, but for the most part the hype about red black +# trees is accurate. + +from __future__ import absolute_import + +from .abctree import ABCTree + +__all__ = ['RBTree'] + + +class Node(object): + """Internal object, represents a tree node.""" + __slots__ = ['key', 'value', 'red', 'left', 'right'] + + def __init__(self, key=None, value=None): + self.key = key + self.value = value + self.red = True + self.left = None + self.right = None + + def free(self): + self.left = None + self.right = None + self.key = None + self.value = None + + def __getitem__(self, key): + """N.__getitem__(key) <==> x[key], where key is 0 (left) or 1 (right).""" + return self.left if key == 0 else self.right + + def __setitem__(self, key, value): + """N.__setitem__(key, value) <==> x[key]=value, where key is 0 (left) or 1 (right).""" + if key == 0: + self.left = value + else: + self.right = value + + +def is_red(node): + if (node is not None) and node.red: + return True + else: + return False + + +def jsw_single(root, direction): + other_side = 1 - direction + save = root[other_side] + root[other_side] = save[direction] + save[direction] = root + root.red = True + save.red = False + return save + + +def jsw_double(root, direction): + other_side = 1 - direction + root[other_side] = jsw_single(root[other_side], other_side) + return jsw_single(root, direction) + + +class RBTree(ABCTree): + """ + RBTree implements a balanced binary tree with a dict-like interface. + + see: http://en.wikipedia.org/wiki/Red_black_tree + + A red-black tree is a type of self-balancing binary search tree, a data + structure used in computing science, typically used to implement associative + arrays. The original structure was invented in 1972 by Rudolf Bayer, who + called them "symmetric binary B-trees", but acquired its modern name in a + paper in 1978 by Leonidas J. Guibas and Robert Sedgewick. It is complex, + but has good worst-case running time for its operations and is efficient in + practice: it can search, insert, and delete in O(log n) time, where n is + total number of elements in the tree. Put very simply, a red-black tree is a + binary search tree which inserts and removes intelligently, to ensure the + tree is reasonably balanced. + + RBTree() -> new empty tree. + RBTree(mapping) -> new tree initialized from a mapping + RBTree(seq) -> new tree initialized from seq [(k1, v1), (k2, v2), ... (kn, vn)] + + see also abctree.ABCTree() class. + """ + def _new_node(self, key, value): + """Create a new tree node.""" + self._count += 1 + return Node(key, value) + + def insert(self, key, value): + """T.insert(key, value) <==> T[key] = value, insert key, value into tree.""" + if self._root is None: # Empty tree case + self._root = self._new_node(key, value) + self._root.red = False # make root black + return + + head = Node() # False tree root + grand_parent = None + grand_grand_parent = head + parent = None # parent + direction = 0 + last = 0 + + # Set up helpers + grand_grand_parent.right = self._root + node = grand_grand_parent.right + # Search down the tree + while True: + if node is None: # Insert new node at the bottom + node = self._new_node(key, value) + parent[direction] = node + elif is_red(node.left) and is_red(node.right): # Color flip + node.red = True + node.left.red = False + node.right.red = False + + # Fix red violation + if is_red(node) and is_red(parent): + direction2 = 1 if grand_grand_parent.right is grand_parent else 0 + if node is parent[last]: + grand_grand_parent[direction2] = jsw_single(grand_parent, 1 - last) + else: + grand_grand_parent[direction2] = jsw_double(grand_parent, 1 - last) + + # Stop if found + if key == node.key: + node.value = value # set new value for key + break + + last = direction + direction = 0 if key < node.key else 1 + # Update helpers + if grand_parent is not None: + grand_grand_parent = grand_parent + grand_parent = parent + parent = node + node = node[direction] + + self._root = head.right # Update root + self._root.red = False # make root black + + def remove(self, key): + """T.remove(key) <==> del T[key], remove item from tree.""" + if self._root is None: + raise KeyError(str(key)) + head = Node() # False tree root + node = head + node.right = self._root + parent = None + grand_parent = None + found = None # Found item + direction = 1 + + # Search and push a red down + while node[direction] is not None: + last = direction + + # Update helpers + grand_parent = parent + parent = node + node = node[direction] + + direction = 1 if key > node.key else 0 + + # Save found node + if key == node.key: + found = node + + # Push the red node down + if not is_red(node) and not is_red(node[direction]): + if is_red(node[1 - direction]): + parent[last] = jsw_single(node, direction) + parent = parent[last] + elif not is_red(node[1 - direction]): + sibling = parent[1 - last] + if sibling is not None: + if (not is_red(sibling[1 - last])) and (not is_red(sibling[last])): + # Color flip + parent.red = False + sibling.red = True + node.red = True + else: + direction2 = 1 if grand_parent.right is parent else 0 + if is_red(sibling[last]): + grand_parent[direction2] = jsw_double(parent, last) + elif is_red(sibling[1-last]): + grand_parent[direction2] = jsw_single(parent, last) + # Ensure correct coloring + grand_parent[direction2].red = True + node.red = True + grand_parent[direction2].left.red = False + grand_parent[direction2].right.red = False + + # Replace and remove if found + if found is not None: + found.key = node.key + found.value = node.value + parent[int(parent.right is node)] = node[int(node.left is None)] + node.free() + self._count -= 1 + + # Update root and make it black + self._root = head.right + if self._root is not None: + self._root.red = False + if not found: + raise KeyError(str(key)) \ No newline at end of file diff --git a/bintrees/treeslice.py b/bintrees/treeslice.py new file mode 100644 index 0000000..b1a2759 --- /dev/null +++ b/bintrees/treeslice.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman -- +# Purpose: TreeSlice +# Created: 11.04.2011 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + + +class TreeSlice(object): + __slots__ = ['_tree', '_start', '_stop'] + + def __init__(self, tree, start, stop): + self._tree = tree + self._start = start + self._stop = stop + + def __repr__(self): + tpl = "%s({%s})" % (self._tree.__class__.__name__, '%s') + return tpl % ", ".join( ("%r: %r" % item for item in self.items()) ) + + def __contains__(self, key): + if self._is_in_range(key): + return key in self._tree + else: + return False + + def _is_in_range(self, key): + if self._start is not None and key < self._start: + return False + if self._stop is not None and key >= self._stop: + return False + return True + + def __getitem__(self, key): + if isinstance(key, slice): + return self._sub_slice(key.start, key.stop) + if self._is_in_range(key): + return self._tree[key] + else: + raise KeyError(key) + + def _sub_slice(self, start, stop): + def newstart(): + if start is None: + return self._start + elif self._start is None: + return start + else: + return max(start, self._start) + + def newstop(): + if stop is None: + return self._stop + elif self._stop is None: + return stop + else: + return min(stop, self._stop) + + return TreeSlice(self._tree, newstart(), newstop()) + + def keys(self): + return self._tree.key_slice(self._start, self._stop) + __iter__ = keys + + def values(self): + return self._tree.value_slice(self._start, self._stop) + + def items(self): + return self._tree.iter_items(self._start, self._stop) diff --git a/issues/003_fastrbtree_crash.py b/issues/003_fastrbtree_crash.py new file mode 100644 index 0000000..785dacc --- /dev/null +++ b/issues/003_fastrbtree_crash.py @@ -0,0 +1,62 @@ +from bintrees import RBTree, FastRBTree + +# Commenting in the first outcommented line in below function +# results in the trees being unequal and a KeyError occurring. They should +# not be unequal (the RBTree is correct) +# Commenting in further lines results in further discrepancies + +def populate(tree): + tree[14] = tree.get(14,0) + 212 + tree[15.84] = tree.get(15.84,0) + 623 + tree[16] = tree.get(16,0) + 693 + tree[16] = 1213 + tree[16.3] = tree.get(16.3,0) + 1952 + tree[15.8] = tree.get(15.8,0) + 1934 + tree[16.48] = tree.get(16.48,0) + 65 + tree[14.95] = tree.get(14.95,0) + 325 + tree[15.07] = tree.get(15.07,0) + 1293 + tree[16.41] = tree.get(16.41,0) + 2000 + tree[16.43] = tree.get(16.43,0) + 2000 + tree[16.45] = tree.get(16.45,0) + 2000 + tree[16.4] = tree.get(16.4,0) + 2000 + tree[16.42] = tree.get(16.42,0) + 2000 + tree[16.47] = tree.get(16.47,0) + 2000 + tree[16.44] = tree.get(16.44,0) + 2000 + tree[16.46] = tree.get(16.46,0) + 2000 + tree[16.48] = tree.get(16.48,0) + 2065 + tree[16.51] = tree.get(16.51,0) + 600 + tree[16.5] = tree.get(16.5,0) + 600 + tree[16.49] = tree.get(16.49,0) + 600 + tree[16.5] = 1400 + tree[16.49] = 2600 + tree[16.49] = 3159 + tree[16.47] = 2694 + tree[16.5] = 2079 + tree[16.48] = 2599 + tree[16.46] = 2564 + tree[16.44] = 2709 +# tree[16.45] = 2644 +# tree[16.43] = 2621 +# tree[16.49] = 3959 +# tree[16.47] = 3494 +# tree[16.48] = 3399 +# tree[16.46] = 3364 +# tree[16.44] = 3509 +# tree[16.45] = 3444 +# tree[16.43] = 3421 +# tree[16.46] = 3735 +# del tree[15.84] +# tree[16.43] = 4921 +# tree[16.48] = 4099 +# tree[16.5] = 1279 +# tree[16.49] = 1959 +# tree[16.39] = tree.get(16.39,0) + 2000 + +rbt = RBTree() +frbt = FastRBTree() +populate(rbt) +populate(frbt) + +print('RBT len: {0} FRBT len: {1}'.format(len(rbt), len(frbt))) +for key, value in rbt.items(): + print("RBTree[{key}] = {value} <-> FastRBTree[{key}] = {value2}".format(key=key, value=value, value2=frbt[key])) diff --git a/issues/004_rbtree_copy.py b/issues/004_rbtree_copy.py new file mode 100644 index 0000000..58c8ae1 --- /dev/null +++ b/issues/004_rbtree_copy.py @@ -0,0 +1,113 @@ +''' +Created on Apr 11, 2013 + +@author: matthijssnel +''' + +from bintrees import FastRBTree + + +def print_node(key, value): + print("Key: {}; Value:{}".format(key, value)) + + +def populate(tree): + tree[20.5] = tree.get(20.5, 0) + 644 + tree[17.35] = tree.get(17.35, 0) + 32 + tree[19.5] = tree.get(19.5, 0) + 440 + tree[20.0] = tree.get(20.0, 0) + 73 + tree[18.5] = tree.get(18.5, 0) + 1500 + tree[20.8] = tree.get(20.8, 0) + 330 + tree[21.0] = tree.get(21.0, 0) + 450 + tree[19.25] = tree.get(19.25, 0) + 137 + tree[18.7] = tree.get(18.7, 0) + 740 + tree[20.12] = tree.get(20.12, 0) + 500 + tree[19.85] = tree.get(19.85, 0) + 300 + del tree[17.35] + tree[18.5] = 1662 + tree[17.23] = tree.get(17.23, 0) + 4594 + tree[16.6] = tree.get(16.6, 0) + 2000 + tree[16.62] = tree.get(16.62, 0) + 2000 + tree[16.66] = tree.get(16.66, 0) + 2000 + tree[16.68] = tree.get(16.68, 0) + 2000 + tree[16.61] = tree.get(16.61, 0) + 2000 + tree[16.64] = tree.get(16.64, 0) + 2000 + tree[16.67] = tree.get(16.67, 0) + 2000 + tree[16.57] = tree.get(16.57, 0) + 600 + tree[16.58] = tree.get(16.58, 0) + 600 + tree[16.59] = tree.get(16.59, 0) + 600 + del tree[16.68] + tree[16.59] = 2600 + tree[16.59] = 2000 + tree[16.56] = tree.get(16.56, 0) + 600 + tree[16.59] = 2800 + del tree[16.67] + tree[16.58] = 2600 + tree[16.56] = 5796 + tree[16.56] = 600 + tree[16.57] = 2600 + tree[16.56] = 1400 + tree[16.55] = tree.get(16.55, 0) + 5196 + tree[16.53] = tree.get(16.53, 0) + 548 + tree[16.55] = 5829 + tree[16.54] = tree.get(16.54, 0) + 657 + tree[16.56] = 1964 + tree[16.58] = 3119 + tree[16.6] = 2691 + tree[16.57] = 3245 + tree[16.59] = 3385 + tree[16.58] = 3919 + tree[16.6] = 3491 + tree[16.57] = 4045 + del tree[16.66] + tree[16.56] = 2764 + tree[16.55] = 6629 + tree[16.58] = 3319 + tree[16.55] = 7229 + tree[16.55] = 2033 + tree[16.55] = 2833 + tree[16.54] = 1457 + tree[16.54] = 6653 + tree[16.54] = 5996 + tree[16.62] = 2492 + del tree[16.53] + tree[16.61] = 2708 + tree[16.54] = 5196 + tree[16.55] = 2033 + tree[16.62] = 2000 + tree[16.54] = 5801 + tree[16.62] = 2800 + tree[16.61] = 3508 + tree[16.58] = 3687 + tree[16.61] = 2800 + tree[16.53] = tree.get(16.53, 0) + 522 + tree[16.55] = 2833 + tree[16.54] = 6601 + tree[16.54] = 1405 + tree[16.53] = 5718 + tree[16.6] = 2800 + tree[16.52] = tree.get(16.52, 0) + 537 + tree[16.58] = 3319 + tree[16.56] = 3133 + tree.copy() + del tree[16.52] + tree[16.6] = 3471 + tree[16.6] = 2800 + tree[16.52] = tree.get(16.52, 0) + 655 + tree[16.54] = 2905 + tree[16.57] = 3445 + del tree[16.64] + tree[16.54] = 3705 + tree[16.53] = 6518 + tree[16.59] = 2800 + tree[16.51] = tree.get(16.51, 0) + 523 + clone = tree.copy() + print("\nOriginal Tree:") + tree.foreach(print_node) + print("\nClone Tree:") + clone.foreach(print_node) + + +tree = FastRBTree() +populate(tree) + diff --git a/issues/005_btreeslow.py b/issues/005_btreeslow.py new file mode 100644 index 0000000..0649972 --- /dev/null +++ b/issues/005_btreeslow.py @@ -0,0 +1,22 @@ +import bintrees +import time + +t = bintrees.FastRBTree.fromkeys(range(1000000), True) +start = 50000 + +key = start +t0 = time.time() +while True: + try: + key, _ = t.succ_item(key) + except KeyError: + break +t1 = time.time() +print("Iterating using succ_item(): %f sec" % (t1-t0)) + + +t0 = time.time() +for key, _ in t.item_slice(start, None): + pass +t1 = time.time() +print("Iterating using item_slice(): %f sec" % (t1-t0)) diff --git a/issues/006_large_data_crash.py b/issues/006_large_data_crash.py new file mode 100644 index 0000000..340584a --- /dev/null +++ b/issues/006_large_data_crash.py @@ -0,0 +1,46 @@ +from random import randint +import bintrees + + +class HyperGraph: + def __init__(self): + self.alerts = [] + for x in range(0, 3000): + alert = [] + for y in range(0, 6): + alert.append(repr(randint(0, 7))) + self.alerts.append(alert) + self.hyper_dict = {} + self.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), (3, 4), (3, 5), (4, 5)] + for alert in self.alerts: + for c in self.hcombinations: + key = alert[:] + for x in range(2): + key[c[x]] = '*' + hyperkey = tuple(key) + if hyperkey in self.hyper_dict: + self.hyper_dict[hyperkey].alerts.get(("test1", "test2")) + else: + self.hyper_dict[hyperkey] = Hyperedge(key) + + for alert in self.alerts: + for c in self.hcombinations: + tmpkey = alert[:] + for x in range(2): + tmpkey[c[x]] = '*' + tmpkey = tuple(tmpkey) + if tmpkey in self.hyper_dict: + del (self.hyper_dict[tmpkey]) + + +class Hyperedge: + def __init__(self, hyperkey): + self.hyperkey = hyperkey + self.alerts = bintrees.FastRBTree() + self.alerts.insert(("test1", "test2"), 1) # replace list by tuple -> no crash + + +for x in range(10): + alertgraph = HyperGraph() + print("finished iteration {} of 10".format(x)) diff --git a/issues/007_FastRBTree_error2.py b/issues/007_FastRBTree_error2.py new file mode 100644 index 0000000..6776394 --- /dev/null +++ b/issues/007_FastRBTree_error2.py @@ -0,0 +1,81 @@ +from random import randint +from bintrees import FastRBTree as Tree + +class Hyperedge: + def __init__(self, hyperkey, col, hlabel): + self.hyperkey = hyperkey + self.col = col + self._alerts = Tree() + self.insert_alert(hlabel, 1) + self.nalerts = 1 + + def get_alert(self, key): + return self._alerts.get(key) + + def insert_alert(self, alert_key, count): + self._alerts.insert(alert_key, count) + + def foreach_alert(self, func): + self._alerts.foreach(func) + + def pop_alert(self, key): + return self._alerts.pop(key) + + +def treeloop(hlabel, dupcount): + alert = hyperedge.hyperkey[:] + for x in range(2): + alert[hyperedge.col[x]] = hlabel[x] + for c in hcombinations: + if hyperedge.col != c: + label = [] + for x in range(2): + label.append(alert[c[x]]) + alert[c[x]] = '*' + tmpkey = tuple(alert) + for x in range(2): + alert[c[x]] = label[x] + hlabel = tuple(label) + if tmpkey in hyper_dict: + tmpedge = hyper_dict[tmpkey] + hypersize_list.discard((tmpedge.nalerts, tmpedge.hyperkey)) + tmpedge.nalerts -= tmpedge.pop_alert(hlabel) + if tmpedge.nalerts > 0: + hypersize_list.insert((tmpedge.nalerts, tmpedge.hyperkey), tmpedge) + + +for z in range(10): + hyper_dict = {} + hypersize_list = Tree() + 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), + (3, 4), (3, 5), (4, 5)] + for x in range(0, 3000): + alert = [] + for y in range(0, 6): + alert.append(repr(randint(0, 8 - 1)) + "test") + for c in hcombinations: + label = [] + key = alert[:] + for x in range(2): + key[c[x]] = '*' + label.append(alert[c[x]]) + + hyperkey = tuple(key) + hlabel = tuple(label) + if hyperkey in hyper_dict: + hyper_dict[hyperkey].nalerts += 1 + result = hyper_dict[hyperkey].get_alert(hlabel) + if result is not None: + hyper_dict[hyperkey].insert_alert(hlabel, result + 1) + else: + hyper_dict[hyperkey].insert_alert(hlabel, 1) + else: + hyper_dict[hyperkey] = Hyperedge(key, c, hlabel) + + for hyperedge in hyper_dict.values(): + hypersize_list.insert((hyperedge.nalerts, hyperedge.hyperkey), hyperedge) + + while not hypersize_list.is_empty(): + (key, hyperedge) = hypersize_list.pop_max() + hyperedge.foreach_alert(treeloop) + print("Completed iteration %d of 10" % z) diff --git a/makefile.unix b/makefile.unix new file mode 100644 index 0000000..a4e688a --- /dev/null +++ b/makefile.unix @@ -0,0 +1,43 @@ +# Author: mozman +# License: MIT-License + +FLAGS = --inplace --force +CMD = setup.py build_ext +RUNTESTS = -m unittest discover + +PYTHON2 = python +PYTHON3 = python3 +PYPY = pypy + +build2: + $(PYTHON2) $(CMD) $(FLAGS) + +build3: + $(PYTHON3) $(CMD) $(FLAGS) + +test2: + $(PYTHON2) $(RUNTESTS) + +test3: + $(PYTHON3) $(RUNTESTS) + +testpypy: + $(PYPY) $(RUNTESTS) + +testall: build2 test2 build3 test3 testpypy + +packages: + $(PYTHON2) setup.py sdist --formats=zip,gztar + $(PYTHON2) setup.py bdist_wheel + $(PYTHON2) setup.py bdist --formats=wininst + $(PYTHON3) setup.py bdist_wheel + $(PYTHON3) setup.py bdist --formats=wininst + + +release: + $(PYTHON2) setup.py sdist --formats=zip,gztar upload + $(PYTHON2) setup.py bdist_wheel upload + $(PYTHON2) setup.py bdist --formats=wininst upload + $(PYTHON3) setup.py bdist_wheel upload + $(PYTHON3) setup.py bdist --formats=wininst upload + diff --git a/makefile.win b/makefile.win new file mode 100644 index 0000000..a363dcb --- /dev/null +++ b/makefile.win @@ -0,0 +1,46 @@ +# Author: mozman +# License: MIT-License + +FLAGS = --inplace --force +CMD = setup.py build_ext +RUNTESTS = -m unittest discover + +PYTHON2 = py -2.7 +PYTHON3 = py -3.4 +PYPY = pypy.exe +PYPY3 = C:\pypy3-2.4.0\pypy.exe + +build2: + $(PYTHON2) $(CMD) $(FLAGS) + +build3: + $(PYTHON3) $(CMD) $(FLAGS) + +test2: + $(PYTHON2) $(RUNTESTS) + +test3: + $(PYTHON3) $(RUNTESTS) + +testpypy: + $(PYPY) $(RUNTESTS) + +testpypy3: + $(PYPY3) $(RUNTESTS) + +testall: build2 test2 build3 test3 testpypy testpypy3 + +packages: + $(PYTHON2) setup.py sdist --formats=zip,gztar + $(PYTHON2) setup.py bdist_wheel + $(PYTHON2) setup.py bdist --formats=wininst + $(PYTHON3) setup.py bdist_wheel + $(PYTHON3) setup.py bdist --formats=wininst + + +release: + $(PYTHON2) setup.py sdist --formats=zip,gztar upload + $(PYTHON2) setup.py bdist_wheel upload + $(PYTHON2) setup.py bdist --formats=wininst upload + $(PYTHON3) setup.py bdist_wheel upload + $(PYTHON3) setup.py bdist --formats=wininst upload diff --git a/profiling/profile_avltree.py b/profiling/profile_avltree.py new file mode 100644 index 0000000..2630cb8 --- /dev/null +++ b/profiling/profile_avltree.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Purpose: profile AVLTree, FastAVLTree +# Created: 01.05.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +import sys +from timeit import Timer +from random import shuffle + +from bintrees import AVLTree, has_fast_tree_support +from bintrees import FastAVLTree + +COUNT = 100 + +setup_AVLTree = """ +from __main__ import avl_build_delete, avl_build, avl_search +""" +setup_FastAVLTree = """ +from __main__ import cavl_build_delete, cavl_build, cavl_search +""" + +try: + fp = open('testkeys.txt') + keys = eval(fp.read()) + fp.close() +except IOError: + print("create 'testkeys.txt' with profile_bintree.py\n") + sys.exit() + +py_searchtree = AVLTree.from_keys(keys) +cy_searchtree = FastAVLTree.from_keys(keys) + + +def avl_build_delete(): + tree = AVLTree.from_keys(keys) + for key in keys: + del tree[key] + + +def cavl_build_delete(): + tree = FastAVLTree.from_keys(keys) + for key in keys: + del tree[key] + + +def avl_build(): + tree = AVLTree.from_keys(keys) + + +def cavl_build(): + tree = FastAVLTree.from_keys(keys) + + +def avl_search(): + for key in keys: + obj = py_searchtree[key] + + +def cavl_search(): + for key in keys: + obj = cy_searchtree[key] + + +def print_result(time, text): + print("Operation: %s takes %.2f seconds\n" % (text, time)) + + +def main(): + fp = open('testkeys.txt', 'w') + fp.write(repr(keys)) + fp.close() + print ("Nodes: %d" % len(keys)) + + t = Timer("avl_build()", setup_AVLTree) + print_result(t.timeit(COUNT), 'AVLTree build only') + + t = Timer("cavl_build()", setup_FastAVLTree) + print_result(t.timeit(COUNT), 'FastAVLTree build only') + + t = Timer("avl_build_delete()", setup_AVLTree) + print_result(t.timeit(COUNT), 'AVLTree build & delete') + + t = Timer("cavl_build_delete()", setup_FastAVLTree) + print_result(t.timeit(COUNT), 'FastAVLTree build & delete') + + # shuffle search keys + shuffle(keys) + t = Timer("avl_search()", setup_AVLTree) + print_result(t.timeit(COUNT), 'AVLTree search') + + t = Timer("cavl_search()", setup_FastAVLTree) + print_result(t.timeit(COUNT), 'FastAVLTree search') + +if __name__ == '__main__': + if not has_fast_tree_support(): + print("Cython extension for FastAVLTree is NOT working.") + else: + print("Cython extension for FastAVLTree is working.") + main() diff --git a/profiling/profile_big_rbtree.py b/profiling/profile_big_rbtree.py new file mode 100644 index 0000000..a77611b --- /dev/null +++ b/profiling/profile_big_rbtree.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Purpose: profile big trees +# Created: 15.02.2015 +# Copyright (c) 2015 by Manfred Moitzi +# License: MIT License + +from timeit import Timer +from random import shuffle + +from bintrees import FastRBTree, has_fast_tree_support + +COUNT = 1 +ITEMS = 2**22 + +setup_dict = """ +from __main__ import dict_build_delete, dict_build, dict_search +""" +setup_FastRBTree = """ +from __main__ import crb_build_delete, crb_build, crb_search +""" + + +keys = [k for k in range(ITEMS)] +shuffle(keys) + +fastrbtree_searchtree = FastRBTree.from_keys(keys) +dict_searchtree = dict.fromkeys(keys) + + +def dict_build_delete(): + d = dict.fromkeys(keys) + for key in keys: + del d[key] + + +def crb_build_delete(): + tree = FastRBTree.from_keys(keys) + for key in keys: + del tree[key] + + +def dict_build(): + d = dict.fromkeys(keys) + + +def crb_build(): + tree = FastRBTree.from_keys(keys) + + +def dict_search(): + for key in keys: + obj = dict_searchtree[key] + + +def crb_search(): + for key in keys: + obj = fastrbtree_searchtree[key] + + +def print_result(time, text): + print("Operation: %s takes %.2f seconds\n" % (text, time)) + + +def main(): + print("Nodes: %d" % len(keys)) + + t = Timer("dict_build()", setup_dict) + print_result(t.timeit(COUNT), 'dict build only') + + t = Timer("crb_build()", setup_FastRBTree) + print_result(t.timeit(COUNT), 'FastRBTree build only') + + t = Timer("dict_build_delete()", setup_dict) + print_result(t.timeit(COUNT), 'dict build & delete') + + t = Timer("crb_build_delete()", setup_FastRBTree) + print_result(t.timeit(COUNT), 'FastRBTree build & delete') + + # shuffle search keys + shuffle(keys) + t = Timer("dict_search()", setup_dict) + print_result(t.timeit(COUNT), 'dict search') + + t = Timer("crb_search()", setup_FastRBTree) + print_result(t.timeit(COUNT), 'FastRBTree search') + +if __name__ == '__main__': + if not has_fast_tree_support(): + print("Cython extension for FastRBTree is NOT working.") + else: + print("Cython extension for FastRBTree is working.") + main() \ No newline at end of file diff --git a/profiling/profile_bintree.py b/profiling/profile_bintree.py new file mode 100644 index 0000000..4138cca --- /dev/null +++ b/profiling/profile_bintree.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Purpose: profile BinaryTree, FastBinaryTree +# Created: 01.05.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +from timeit import Timer +from bintrees import BinaryTree +from bintrees import FastBinaryTree, has_fast_tree_support +from random import shuffle + +COUNT = 100 +KEYS = 5000 +KEYRANGE = 1000000 + +setup_BinaryTree = """ +from __main__ import bintree_build_delete, bintree_build, bintree_search, iterbintree +""" +setup_FastBinaryTree = """ +from __main__ import cbintree_build_delete, cbintree_build, cbintree_search, itercbintree +""" + + +def random_keys(): + import random + return random.sample(range(KEYRANGE), KEYS) +try: + with open('testkeys.txt') as fp: + keys = eval(fp.read()) +except IOError: + keys = random_keys() + +py_searchtree = BinaryTree.from_keys(keys) +cy_searchtree = FastBinaryTree.from_keys(keys) + + +def bintree_build_delete(): + tree = BinaryTree.from_keys(keys) + for key in keys: + del tree[key] + + +def cbintree_build_delete(): + tree = FastBinaryTree.from_keys(keys) + for key in keys: + del tree[key] + + +def bintree_build(): + tree = BinaryTree.from_keys(keys) + + +def cbintree_build(): + tree = FastBinaryTree.from_keys(keys) + + +def bintree_search(): + for key in keys: + obj = py_searchtree[key] + + +def cbintree_search(): + for key in keys: + obj = cy_searchtree[key] + + +def iterbintree(): + items = list(py_searchtree.items()) + + +def itercbintree(): + items = list(cy_searchtree.items()) + + +def print_result(time, text): + print("Operation: %s takes %.2f seconds\n" % (text, time)) + + +def main(): + fp = open('testkeys.txt', 'w') + fp.write(repr(keys)) + fp.close() + print ("Nodes: %d" % len(keys)) + + t = Timer("bintree_build()", setup_BinaryTree) + print_result(t.timeit(COUNT), 'BinaryTree build only') + + t = Timer("cbintree_build()", setup_FastBinaryTree) + print_result(t.timeit(COUNT), 'FastBinaryTree build only') + + t = Timer("bintree_build_delete()", setup_BinaryTree) + print_result(t.timeit(COUNT), 'BinaryTree build & delete') + + t = Timer("cbintree_build_delete()", setup_FastBinaryTree) + print_result(t.timeit(COUNT), 'FastBinaryTree build & delete') + + # shuffle search keys + shuffle(keys) + t = Timer("bintree_search()", setup_BinaryTree) + print_result(t.timeit(COUNT), 'BinaryTree search') + + t = Timer("cbintree_search()", setup_FastBinaryTree) + print_result(t.timeit(COUNT), 'FastBinaryTree search') + + t = Timer("iterbintree()", setup_BinaryTree) + print_result(t.timeit(COUNT), 'BinaryTree iter all items') + + t = Timer("itercbintree()", setup_FastBinaryTree) + print_result(t.timeit(COUNT), 'FastBinaryTree iter all items') + +if __name__ == '__main__': + if not has_fast_tree_support(): + print("Cython extension for FastBinaryTree is NOT working.") + else: + print("Cython extension for FastBinaryTree is working.") + main() diff --git a/profiling/profile_itemslice.py b/profiling/profile_itemslice.py new file mode 100644 index 0000000..f69c074 --- /dev/null +++ b/profiling/profile_itemslice.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Purpose: profile RBTree, FastRBTree +# Created: 17.05.2013 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +import sys +from timeit import Timer + +from bintrees import RBTree as PTree +from bintrees import FastRBTree as FTree, has_fast_tree_support + +COUNT = 100 + +setup_RBTree_ps = """ +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 +""" +setup_FastRBTree_ps = """ +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 +""" + +try: + with open('testkeys.txt') as fp: + keys = eval(fp.read()) +except IOError: + print("create 'testkeys.txt' with profile_bintree.py\n") + sys.exit() + +ptree = PTree.from_keys(keys) +ftree = FTree.from_keys(keys) +sorted_keys = list(ftree.keys()) +median_key = sorted_keys[int(len(sorted_keys) / 2)] + + +def rb_iter_by_prev_item(): + key = median_key + while True: + try: + key, value = ptree.prev_item(key) + except KeyError: + break + + +def rb_iter_by_succ_item(): + key = median_key + while True: + try: + key, value = ptree.succ_item(key) + except KeyError: + break + + +def rb_iter_by_item_slice_prev(): + for item in ptree.iter_items(None, median_key): + pass + + +def rb_iter_by_item_slice_succ(): + for item in ptree.iter_items(median_key, None): + pass + + +def crb_iter_by_prev_item(): + key = median_key + while True: + try: + key, value = ftree.prev_item(key) + except KeyError: + break + + +def crb_iter_by_succ_item(): + key = median_key + while True: + try: + key, value = ftree.succ_item(key) + except KeyError: + break + + +def crb_iter_by_item_slice_prev(): + for item in ftree.iter_items(None, median_key): + pass + + +def crb_iter_by_item_slice_succ(): + for item in ftree.iter_items(median_key, None): + pass + + +def print_result(time, text): + print("Operation: %s == [ %.2f s ]\n" % (text, time)) + + +def main(): + print ("Iterating {}x {} keys out of {} Nodes.\n".format(COUNT, len(keys)/2, len(keys))) + + t = Timer("rb_iter_by_prev_item()", setup_RBTree_ps) + print_result(t.timeit(COUNT), 'RBTree iterating by k, v = prev_item(k)') + + t = Timer("rb_iter_by_succ_item()", setup_RBTree_ps) + print_result(t.timeit(COUNT), 'RBTree iterating by k, v = succ_item(k)') + + t = Timer("rb_iter_by_item_slice_prev()", setup_RBTree_ps) + print_result(t.timeit(COUNT), 'RBTree itemslice(None, median_key)') + + t = Timer("rb_iter_by_item_slice_succ()", setup_RBTree_ps) + print_result(t.timeit(COUNT), 'RBTree itemslice(median_key: None)') + + t = Timer("crb_iter_by_prev_item()", setup_FastRBTree_ps) + print_result(t.timeit(COUNT), 'FastRBTree iterating by k, v = prev_item(k)') + + t = Timer("crb_iter_by_succ_item()", setup_FastRBTree_ps) + print_result(t.timeit(COUNT), 'FastRBTree iterating by k, v = succ_item(k)') + + t = Timer("crb_iter_by_item_slice_prev()", setup_FastRBTree_ps) + print_result(t.timeit(COUNT), 'FastRBTree itemslice(None, median_key)') + + t = Timer("crb_iter_by_item_slice_succ()", setup_FastRBTree_ps) + print_result(t.timeit(COUNT), 'FastRBTree itemslice(median_key, None)') + +if __name__ == '__main__': + if not has_fast_tree_support(): + print("Cython extension for FastRBTree is NOT working.") + else: + print("Cython extension for FastRBTree is working.") + main() diff --git a/profiling/profile_min_max.py b/profiling/profile_min_max.py new file mode 100644 index 0000000..3751f04 --- /dev/null +++ b/profiling/profile_min_max.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Purpose: profile RBTree, FastRBTree +# Created: 02.05.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +import sys +from timeit import Timer +from random import shuffle + +from bintrees import RBTree +from bintrees import FastRBTree, has_fast_tree_support + +COUNT = 100 + +setup_RBTree = """ +from __main__ import rb_pop_min, rb_pop_max +""" +setup_FastRBTree = """ +from __main__ import keys, crb_pop_min, crb_pop_max +""" + +try: + fp = open('testkeys.txt') + keys = eval(fp.read()) + fp.close() + bskeys = zip(keys, keys) +except IOError: + print("create 'testkeys.txt' with profile_bintree.py\n") + sys.exit() + + +def rb_pop_min(): + tree = RBTree.fromkeys(keys) + while tree.count: + tree.pop_min() + + +def rb_pop_max(): + tree = RBTree.fromkeys(keys) + while tree.count: + tree.pop_max() + + +def crb_pop_min(): + tree = FastRBTree.fromkeys(keys) + while tree.count: + tree.pop_min() + + +def crb_pop_max(): + tree = FastRBTree.fromkeys(keys) + while tree.count: + tree.pop_max() + + +def print_result(time, text): + print("Operation: %s takes %.2f seconds\n" % (text, time)) + + +def main(): + fp = open('testkeys.txt', 'w') + fp.write(repr(keys)) + fp.close() + print("Nodes: %d" % len(keys)) + + shuffle(keys) + + t = Timer("rb_pop_min()", setup_RBTree) + print_result(t.timeit(COUNT), 'RBTree pop_min') + + t = Timer("rb_pop_max()", setup_RBTree) + print_result(t.timeit(COUNT), 'RBTree pop_max') + + t = Timer("crb_pop_min()", setup_FastRBTree) + print_result(t.timeit(COUNT), 'FastRBTree pop_min') + + t = Timer("crb_pop_max()", setup_FastRBTree) + print_result(t.timeit(COUNT), 'FastRBTree pop_max') + +if __name__ == '__main__': + if not has_fast_tree_support(): + print("Cython extension for FastRBTree is NOT working.") + else: + print("Cython extension for FastRBTree is working.") + main() diff --git a/profiling/profile_prev_succ.py b/profiling/profile_prev_succ.py new file mode 100644 index 0000000..31c89dd --- /dev/null +++ b/profiling/profile_prev_succ.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Purpose: profile RBTree, FastRBTree +# Created: 02.05.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +import sys +from timeit import Timer +from random import shuffle + +from bintrees import RBTree as PTree +from bintrees import FastRBTree as FTree, has_fast_tree_support + +COUNT = 100 + +setup_RBTree_ps = """ +from __main__ import keys, rb_prev, rb_succ +""" +setup_FastRBTree_ps = """ +from __main__ import keys, crb_prev, crb_succ +""" + +try: + fp = open('testkeys.txt') + keys = eval(fp.read()) + fp.close() + bskeys = zip(keys, keys) +except IOError: + print("create 'testkeys.txt' with profile_bintree.py\n") + sys.exit() + +ptree = PTree.from_keys(keys) +ftree = FTree.from_keys(keys) + + +def rb_prev(): + for key in keys: + try: + item = ptree.prev_item(key) + except KeyError: + pass + + +def rb_succ(): + for key in keys: + try: + item = ptree.succ_item(key) + except KeyError: + pass + + +def crb_prev(): + for key in keys: + try: + item = ftree.prev_item(key) + except KeyError: + pass + + +def crb_succ(): + for key in keys: + try: + item = ftree.succ_item(key) + except KeyError: + pass + + +def print_result(time, text): + print("Operation: %s takes %.2f seconds\n" % (text, time)) + + +def main(): + fp = open('testkeys.txt', 'w') + fp.write(repr(keys)) + fp.close() + print ("Nodes: %d" % len(keys)) + + shuffle(keys) + + t = Timer("rb_prev()", setup_RBTree_ps) + print_result(t.timeit(COUNT), 'PythonTree prev') + + t = Timer("rb_succ()", setup_RBTree_ps) + print_result(t.timeit(COUNT), 'PythonTree succ') + + t = Timer("crb_prev()", setup_FastRBTree_ps) + print_result(t.timeit(COUNT), 'FastXTree prev') + + t = Timer("crb_succ()", setup_FastRBTree_ps) + print_result(t.timeit(COUNT), 'FastXTree succ') + + +if __name__ == '__main__': + if not has_fast_tree_support(): + print("Cython extension for FastRBTree is NOT working.") + else: + print("Cython extension for FastRBTree is working.") + main() diff --git a/profiling/profile_pytrees.py b/profiling/profile_pytrees.py new file mode 100644 index 0000000..7e90cf8 --- /dev/null +++ b/profiling/profile_pytrees.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Purpose: profile pure python trees, works also with ipy +# Created: 01.05.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +from timeit import Timer +from bintrees import BinaryTree +from bintrees import AVLTree +from bintrees import RBTree + +from random import shuffle + +COUNT = 100 +KEYS = 5000 +KEYRANGE = 1000000 + +setup_BinaryTree_bd = """ +from __main__ import keys, bintree_build_delete, BinaryTree +""" +setup_BinaryTree_b = """ +from __main__ import keys, bintree_build, BinaryTree +""" +setup_BinaryTree_s = """ +from __main__ import keys, bintree_search, py_searchtree +""" +setup_AVLTree_bd = """ +from __main__ import keys, avl_build_delete, AVLTree +""" +setup_AVLTree_b = """ +from __main__ import keys, avl_build, AVLTree +""" +setup_AVLTree_s = """ +from __main__ import keys, avl_search, py_searchtree +""" +setup_RBTree_bd = """ +from __main__ import keys, rb_build_delete, RBTree +""" +setup_RBTree_b = """ +from __main__ import keys, rb_build, RBTree +""" +setup_RBTree_s = """ +from __main__ import keys, rb_search, py_searchtree +""" + + +def random_keys(): + from random import shuffle, randint + keys = set() + while len(keys) < KEYS: + keys.add(randint(0, KEYRANGE)) + keys = list(keys) + shuffle(keys) + return keys +try: + fp = open('testkeys.txt') + keys = eval(fp.read()) + fp.close() +except IOError: + keys = random_keys() + +py_searchtree = BinaryTree.from_keys(keys) + + +def bintree_build_delete(): + tree = BinaryTree.from_keys(keys) + for key in keys: + del tree[key] + + +def bintree_build(): + tree = BinaryTree.from_keys(keys) + + +def bintree_search(): + for key in keys: + obj = py_searchtree[key] + + +def avl_build_delete(): + tree = AVLTree.from_keys(keys) + for key in keys: + del tree[key] + + +def avl_build(): + tree = AVLTree.from_keys(keys) + + +def avl_search(): + for key in keys: + obj = py_searchtree[key] + + +def rb_build_delete(): + tree = RBTree.from_keys(keys) + for key in keys: + del tree[key] + + +def rb_build(): + tree = RBTree.from_keys(keys) + + +def rb_search(): + for key in keys: + obj = py_searchtree[key] + + +def print_result(time, text): + print("Operation: %s takes %.2f seconds\n" % (text, time)) + + +def main(): + fp = open('testkeys.txt', 'w') + fp.write(repr(keys)) + fp.close() + print ("Nodes: %d" % len(keys)) + + t = Timer("bintree_build()", setup_BinaryTree_b) + print_result(t.timeit(COUNT), 'BinaryTree build only') + + t = Timer("avl_build()", setup_AVLTree_b) + print_result(t.timeit(COUNT), 'AVLTree build only') + + t = Timer("rb_build()", setup_RBTree_b) + print_result(t.timeit(COUNT), 'RBTree build only') + + t = Timer("bintree_build_delete()", setup_BinaryTree_bd) + print_result(t.timeit(COUNT), 'BinaryTree build & delete') + + t = Timer("avl_build_delete()", setup_AVLTree_bd) + print_result(t.timeit(COUNT), 'AVLTree build & delete') + + t = Timer("rb_build_delete()", setup_RBTree_bd) + print_result(t.timeit(COUNT), 'RBTree build & delete') + + shuffle(keys) + + t = Timer("bintree_search()", setup_BinaryTree_s) + print_result(t.timeit(COUNT), 'BinaryTree search') + + t = Timer("avl_search()", setup_AVLTree_s) + print_result(t.timeit(COUNT), 'AVLTree search') + + t = Timer("rb_search()", setup_RBTree_s) + print_result(t.timeit(COUNT), 'RBTree search') + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/profiling/profile_rbtree.py b/profiling/profile_rbtree.py new file mode 100644 index 0000000..5d8c0a5 --- /dev/null +++ b/profiling/profile_rbtree.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Purpose: profile RBTree, FastRBTree +# Created: 02.05.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +import sys +from timeit import Timer +from random import shuffle + +from bintrees import RBTree +from bintrees import FastRBTree, has_fast_tree_support + +COUNT = 100 + +setup_RBTree = """ +from __main__ import rb_build_delete, rb_build, rb_search +""" +setup_FastRBTree = """ +from __main__ import crb_build_delete, crb_build, crb_search +""" + +try: + fp = open('testkeys.txt') + keys = eval(fp.read()) + fp.close() + bskeys = zip(keys, keys) +except IOError: + print("create 'testkeys.txt' with profile_bintree.py\n") + sys.exit() + +py_searchtree = RBTree.from_keys(keys) +cy_searchtree = FastRBTree.from_keys(keys) + + +def rb_build_delete(): + tree = RBTree.from_keys(keys) + for key in keys: + del tree[key] + + +def crb_build_delete(): + tree = FastRBTree.from_keys(keys) + for key in keys: + del tree[key] + + +def rb_build(): + tree = RBTree.from_keys(keys) + + +def crb_build(): + tree = FastRBTree.from_keys(keys) + + +def rb_search(): + for key in keys: + obj = py_searchtree[key] + + +def crb_search(): + for key in keys: + obj = cy_searchtree[key] + + +def print_result(time, text): + print("Operation: %s takes %.2f seconds\n" % (text, time)) + + +def main(): + fp = open('testkeys.txt', 'w') + fp.write(repr(keys)) + fp.close() + print ("Nodes: %d" % len(keys)) + + t = Timer("rb_build()", setup_RBTree) + print_result(t.timeit(COUNT), 'RBTree build only') + + t = Timer("crb_build()", setup_FastRBTree) + print_result(t.timeit(COUNT), 'FastRBTree build only') + + t = Timer("rb_build_delete()", setup_RBTree) + print_result(t.timeit(COUNT), 'RBTree build & delete') + + t = Timer("crb_build_delete()", setup_FastRBTree) + print_result(t.timeit(COUNT), 'FastRBTree build & delete') + + # shuffle search keys + shuffle(keys) + t = Timer("rb_search()", setup_RBTree) + print_result(t.timeit(COUNT), 'RBTree search') + + t = Timer("crb_search()", setup_FastRBTree) + print_result(t.timeit(COUNT), 'FastRBTree search') + +if __name__ == '__main__': + if not has_fast_tree_support(): + print("Cython extension for FastRBTree is NOT working.") + else: + print("Cython extension for FastRBTree is working.") + main() diff --git a/profiling/testkeys.txt b/profiling/testkeys.txt new file mode 100644 index 0000000..aecad04 --- /dev/null +++ b/profiling/testkeys.txt @@ -0,0 +1 @@ +[877748, 262657, 573777, 438814, 438074, 470730, 723112, 549394, 314875, 112898, 223796, 934205, 295830, 581664, 124260, 674084, 162090, 754774, 180302, 192246, 822704, 88201, 444353, 182106, 469036, 374792, 924289, 820408, 129566, 604354, 194346, 410635, 271882, 623965, 506629, 676211, 620239, 345868, 894532, 83717, 882761, 139204, 110923, 558195, 183234, 629235, 189380, 904780, 527701, 470249, 854058, 457909, 208703, 66913, 162736, 155797, 901633, 387493, 454333, 871698, 5445, 62184, 458007, 524953, 30183, 978487, 306262, 781033, 825224, 642571, 342603, 369950, 595460, 210748, 877391, 630224, 497374, 688694, 767548, 963849, 579230, 332700, 9270, 538785, 66519, 802165, 263672, 12499, 363772, 111682, 682776, 947559, 72825, 386002, 677296, 132430, 743512, 442031, 440842, 261304, 416549, 984214, 671379, 497208, 232060, 977016, 633033, 865782, 624660, 205185, 57939, 422296, 261601, 977099, 336077, 391951, 746845, 676674, 771396, 324910, 548059, 317449, 376354, 192709, 17731, 65042, 707241, 864994, 287120, 378342, 727097, 147411, 202819, 52809, 800800, 207524, 817984, 90452, 848382, 977838, 470074, 257034, 286660, 145644, 708397, 560022, 122367, 809702, 117834, 727268, 541963, 600975, 832852, 966486, 628772, 468536, 93807, 985908, 702271, 991446, 139501, 402345, 452038, 245318, 197621, 703376, 618839, 613479, 781435, 300661, 865528, 159168, 375322, 445190, 878466, 323575, 511705, 324544, 932825, 882714, 25741, 828369, 493156, 575291, 956840, 557222, 382441, 187525, 79639, 217106, 579252, 458698, 843471, 267077, 277204, 858829, 654673, 25188, 70434, 502850, 954041, 325884, 83085, 269073, 28165, 82998, 118410, 47276, 157599, 292357, 186413, 345000, 917173, 175717, 968089, 362773, 987645, 249681, 525282, 890696, 921603, 227147, 75883, 261670, 469371, 89687, 686933, 448848, 766137, 635169, 10930, 350791, 708271, 369684, 91320, 501286, 871047, 965168, 964543, 426635, 330983, 570208, 659676, 402336, 942463, 340992, 354428, 201705, 843222, 299195, 170015, 960478, 301401, 103353, 175872, 260908, 534442, 82196, 30821, 544933, 517729, 828563, 493895, 285526, 875206, 115713, 46618, 144671, 825221, 805420, 550556, 910799, 871000, 106598, 452641, 193400, 643762, 988801, 706547, 691679, 465346, 294774, 705084, 444603, 700292, 564968, 398952, 680289, 531384, 264578, 620522, 163071, 444916, 496889, 305885, 41559, 626689, 281550, 80894, 594771, 507947, 187118, 110621, 341081, 271528, 843171, 740977, 702038, 680879, 255614, 260517, 150168, 285302, 725295, 385104, 909555, 963735, 9916, 504875, 682841, 420821, 670939, 281774, 572326, 370304, 67050, 975619, 195857, 986085, 370299, 635827, 287072, 140413, 585338, 809362, 388159, 410279, 72280, 620655, 137639, 905620, 461119, 188243, 976671, 320217, 968138, 418418, 313070, 289286, 65489, 861850, 760027, 942929, 423460, 365553, 494643, 752485, 913176, 582532, 915929, 912116, 783869, 486008, 27888, 294495, 650216, 57576, 274472, 478955, 212118, 747796, 731726, 969131, 162669, 655440, 114437, 869733, 362605, 584651, 479250, 949, 712289, 653763, 99353, 724871, 457109, 841874, 791313, 983916, 563792, 602664, 772685, 735375, 586506, 777504, 603609, 514847, 997282, 155240, 545899, 72217, 404211, 426919, 988792, 307814, 593080, 184952, 291388, 971650, 578928, 619336, 796746, 491558, 143046, 282111, 233371, 233784, 69890, 832865, 619645, 193080, 417279, 171454, 747302, 191070, 573319, 234484, 324394, 9911, 315993, 143712, 603388, 542669, 564161, 424142, 780332, 234719, 639747, 734217, 543248, 140772, 380293, 986410, 193051, 407355, 552954, 556073, 591428, 411341, 598943, 88809, 889234, 249475, 973664, 355078, 58162, 970386, 473306, 372531, 239041, 288822, 517862, 392660, 160261, 999960, 651077, 716301, 664376, 751727, 319965, 182318, 786766, 980527, 547100, 422031, 955244, 915976, 86937, 745998, 424058, 768497, 284212, 937878, 374794, 673590, 578019, 863511, 911851, 954633, 426222, 280871, 226826, 124407, 810821, 406497, 9636, 417629, 977344, 754943, 589228, 296416, 509625, 217438, 162388, 715446, 3389, 781996, 464015, 241260, 900624, 575141, 478181, 980369, 666494, 858246, 742043, 964490, 189697, 664654, 468694, 346735, 90133, 147209, 66024, 55435, 936564, 421631, 371424, 645973, 538553, 562123, 147095, 418554, 818190, 482516, 133923, 972789, 355836, 842311, 61231, 50946, 680184, 12462, 443045, 253429, 802777, 799572, 396548, 297342, 887349, 254717, 423152, 175860, 716932, 861671, 563107, 480643, 798617, 236048, 216372, 942525, 772561, 879173, 512878, 859757, 958136, 303912, 198070, 727588, 861316, 713859, 603132, 869989, 73700, 616458, 389036, 736648, 220598, 515381, 81676, 749664, 316841, 118366, 356996, 619284, 598549, 468946, 601564, 65714, 21451, 777494, 854600, 451687, 575709, 775539, 374302, 715231, 220703, 531559, 767900, 571749, 238085, 141286, 219889, 889603, 837343, 824636, 95769, 964014, 682293, 608350, 579673, 546003, 45457, 684672, 598114, 366059, 932723, 102846, 464222, 915285, 452208, 537715, 12530, 457, 808139, 479827, 771076, 389692, 979309, 4859, 90679, 982158, 952989, 462324, 34039, 63445, 297937, 466668, 28201, 345251, 623185, 769784, 802495, 407997, 173256, 652364, 488897, 958704, 771003, 541606, 160206, 737594, 536132, 509063, 158195, 2513, 588461, 494824, 630774, 23031, 873655, 723086, 62204, 588780, 341434, 583052, 221366, 963607, 992068, 500245, 809659, 309055, 873530, 625930, 91015, 294272, 783096, 273746, 53934, 481359, 317549, 57824, 505669, 107944, 390382, 378633, 459500, 214581, 229883, 583259, 56922, 98222, 280709, 431063, 664663, 340125, 895101, 709202, 805086, 73408, 570268, 718037, 667833, 195994, 614711, 793634, 767520, 806155, 899362, 128572, 925070, 86680, 245529, 353477, 516287, 332355, 801872, 761506, 560243, 104182, 137607, 289770, 354261, 734440, 639475, 851354, 340380, 692614, 963246, 870094, 509300, 915463, 738703, 343260, 114832, 727727, 612994, 863220, 436754, 263568, 574742, 400516, 409616, 668021, 924281, 168453, 940700, 748671, 41655, 479751, 36067, 814643, 635088, 841177, 429030, 258405, 110483, 735041, 121286, 121222, 502879, 117728, 421279, 984347, 798794, 71486, 801614, 43644, 499725, 642710, 610784, 932564, 677140, 242646, 676756, 161664, 429129, 690716, 750777, 352701, 207324, 457930, 654438, 265442, 124424, 475756, 628837, 830442, 955434, 785985, 787037, 386441, 259741, 879989, 604252, 751744, 696929, 699582, 382830, 577521, 353201, 449872, 774940, 715345, 665485, 214878, 87625, 558142, 486676, 889715, 46579, 354995, 133698, 556610, 823588, 946219, 672295, 843712, 662174, 844605, 301593, 598567, 971652, 212478, 64695, 251233, 908890, 594037, 27920, 278795, 753540, 567219, 753643, 639091, 254644, 169865, 68653, 38034, 652573, 675175, 873562, 514007, 979110, 993082, 337538, 960861, 381636, 787844, 730423, 240143, 321092, 717489, 606247, 170828, 993758, 64190, 382782, 239930, 345748, 460596, 44324, 774274, 142962, 654643, 448249, 344211, 713496, 186060, 981036, 670150, 756895, 72426, 787714, 457671, 337526, 814141, 886323, 315371, 805159, 794819, 456816, 583999, 642275, 70936, 545762, 778288, 542185, 518446, 633467, 615230, 892058, 683062, 144546, 431033, 302515, 578748, 904316, 919878, 372161, 74041, 444382, 847321, 16110, 958657, 914402, 679605, 441189, 373878, 52188, 331718, 444308, 95704, 345676, 968051, 506827, 376372, 963651, 962454, 915776, 778096, 414404, 145300, 15435, 343220, 728412, 549784, 846102, 223020, 166593, 856561, 982389, 325092, 814177, 943338, 566473, 716253, 509549, 784021, 103513, 380577, 505294, 568587, 72430, 351247, 397857, 152236, 161325, 587971, 603198, 295313, 185087, 254878, 375691, 229054, 626833, 378057, 588807, 908413, 929616, 769372, 120006, 508422, 827812, 999136, 650081, 784850, 101628, 143966, 953783, 970861, 537960, 480417, 183303, 655831, 235115, 696871, 286862, 774967, 15334, 498266, 72566, 59679, 670646, 56091, 195283, 737889, 938004, 870766, 41890, 323115, 987973, 160194, 983524, 496419, 57418, 690281, 286900, 296257, 911868, 319037, 958808, 20859, 972228, 176195, 169458, 642144, 300738, 464190, 615165, 827855, 893559, 374713, 763484, 560400, 129169, 813225, 996022, 654040, 828314, 387664, 81117, 176347, 536825, 867152, 876867, 362013, 754873, 683221, 594703, 289591, 622981, 901596, 574273, 898590, 860940, 906729, 537971, 356863, 682684, 70947, 41103, 525029, 999141, 342798, 926652, 842750, 481802, 817670, 242483, 857405, 944888, 461541, 581370, 342298, 175689, 254564, 53085, 606756, 192766, 153867, 346372, 253352, 367976, 347811, 744369, 274722, 38707, 267069, 224623, 28780, 289028, 300506, 813005, 186609, 476206, 984305, 794118, 50442, 548541, 818879, 172777, 941028, 176925, 611602, 560859, 49556, 799447, 306626, 980291, 887117, 972539, 645881, 680172, 477799, 325552, 269573, 594903, 656161, 658379, 61689, 781334, 539388, 653035, 150456, 624416, 133616, 755777, 92780, 296414, 397263, 315789, 198049, 977854, 802878, 568526, 560727, 894413, 358916, 975134, 185828, 421197, 958546, 312383, 961188, 33495, 243093, 290002, 745616, 913484, 888524, 396891, 892676, 329698, 330879, 396955, 457613, 253449, 270072, 298365, 214492, 191894, 775656, 951521, 361876, 589358, 164156, 844840, 875982, 311258, 452760, 351417, 280437, 185101, 880083, 127181, 945962, 327725, 953074, 198518, 765144, 881761, 923164, 629993, 987982, 326010, 841813, 376662, 378315, 574614, 53042, 54026, 754273, 725942, 149134, 114438, 715126, 443562, 198268, 481097, 297857, 295850, 426102, 151856, 224091, 40686, 947499, 135935, 642580, 375259, 183275, 678950, 339179, 399759, 724679, 56674, 976005, 881079, 253744, 775789, 235553, 676173, 600260, 203707, 467118, 179686, 994942, 544181, 294411, 131819, 174910, 553055, 911343, 137598, 331955, 458809, 438997, 517967, 591901, 624982, 73534, 265187, 435890, 351542, 716227, 313780, 658857, 986630, 738799, 555326, 626629, 859434, 305280, 626594, 121415, 762505, 958742, 661882, 923184, 56905, 579887, 81002, 274031, 600221, 225479, 975920, 894196, 256663, 446622, 161854, 170745, 422894, 162179, 743296, 239243, 405346, 972193, 80464, 424109, 152405, 346692, 708654, 537667, 384004, 411540, 797268, 442529, 185172, 71297, 330526, 715374, 831232, 799296, 396174, 588210, 88323, 937383, 311237, 729897, 338944, 739240, 189299, 600384, 688560, 736537, 387403, 372286, 490620, 140548, 974583, 991436, 431575, 648257, 221798, 668064, 387075, 308907, 814292, 478515, 237513, 922282, 522891, 794686, 672268, 612829, 779747, 447044, 810631, 634526, 63280, 399949, 317271, 596972, 986934, 504300, 81190, 86301, 812583, 71532, 812003, 973756, 193974, 276272, 124311, 585686, 354954, 50233, 65265, 643417, 13716, 499411, 526733, 127806, 315565, 444813, 23403, 261212, 690280, 323021, 241793, 158043, 442235, 686905, 3428, 30642, 680750, 61494, 102234, 400601, 681462, 954504, 948737, 403027, 270034, 64932, 210567, 329820, 375855, 643643, 478993, 320485, 697868, 768365, 895677, 696639, 31469, 558461, 51838, 976796, 990575, 739182, 47562, 401213, 378838, 822574, 85280, 714310, 359466, 103479, 502987, 674088, 344114, 152835, 374276, 704680, 417834, 692555, 657656, 477156, 154957, 657097, 396646, 41820, 8001, 17919, 911909, 226479, 43198, 727649, 194842, 1388, 126159, 98372, 31664, 4766, 267496, 133746, 143451, 728559, 760731, 924141, 78115, 838750, 349871, 634607, 673722, 115185, 172999, 645381, 267778, 751182, 630559, 17957, 361679, 588745, 754238, 138399, 163959, 152829, 64334, 445334, 981312, 324637, 943896, 547097, 498117, 964256, 405651, 963215, 839452, 130199, 129913, 468572, 840447, 69292, 554360, 124585, 185772, 60359, 719881, 543223, 825116, 405146, 831412, 515699, 216293, 498007, 146655, 707705, 55670, 870588, 139940, 660908, 356453, 834014, 35760, 124110, 681253, 508016, 145837, 203031, 840194, 915282, 782681, 329233, 414300, 858213, 649187, 587361, 826357, 928141, 577678, 116800, 679264, 474337, 217169, 992832, 146160, 348140, 73418, 139449, 265509, 523882, 178081, 781420, 87110, 997461, 308016, 391706, 812831, 688527, 362457, 738868, 659898, 740798, 352129, 769147, 142767, 6623, 277519, 522200, 283656, 865489, 119481, 139584, 492539, 791187, 192664, 41326, 530779, 746015, 450185, 33802, 849264, 74608, 979774, 977388, 595388, 413073, 389156, 34923, 175534, 664757, 13303, 46580, 389617, 708657, 193867, 212570, 645497, 172633, 57609, 913461, 363636, 580877, 541964, 148598, 176039, 265799, 725702, 719564, 457847, 487417, 75206, 705137, 70634, 312729, 311170, 214675, 660928, 314527, 153204, 466621, 344625, 143963, 707516, 260636, 890830, 377523, 487522, 595240, 994332, 143346, 97487, 814102, 30794, 723359, 313671, 530665, 460889, 170714, 442119, 94420, 453223, 887158, 847503, 955158, 283216, 529720, 473809, 611310, 777992, 449334, 170489, 110437, 741583, 106605, 82424, 6365, 339372, 213267, 461324, 666598, 671331, 557651, 337017, 327562, 67550, 548927, 316760, 584731, 110647, 936292, 172135, 188934, 809398, 365917, 28693, 818059, 570578, 97624, 716241, 817319, 424741, 79622, 972273, 380317, 205717, 395319, 182088, 237093, 272105, 15039, 866787, 187670, 343156, 214612, 837418, 947963, 12634, 889519, 583704, 236677, 859894, 770477, 367635, 337407, 917343, 263988, 607135, 830283, 640718, 11595, 283116, 254520, 465432, 818354, 972234, 742567, 793918, 263210, 471755, 834104, 411347, 42670, 217093, 143497, 18955, 314743, 365847, 246100, 750245, 992536, 813808, 634321, 958975, 863790, 949876, 965372, 370799, 393358, 680520, 881467, 858640, 891760, 122416, 167996, 699832, 598064, 619447, 967149, 480773, 663960, 350665, 447235, 613603, 901816, 623856, 169406, 264805, 953145, 680026, 272847, 304932, 96005, 144549, 580548, 939583, 543713, 11207, 88207, 318147, 147571, 405852, 896780, 654127, 765496, 185805, 592675, 518826, 423289, 413524, 281178, 919339, 556563, 175673, 756369, 776711, 87703, 643696, 200650, 753801, 227055, 143920, 83149, 694964, 632576, 367615, 285463, 799599, 926286, 476741, 286271, 361190, 539972, 5226, 134304, 783880, 218227, 314515, 352778, 914810, 247094, 933207, 182119, 32419, 947195, 586474, 252988, 468459, 449449, 55205, 533828, 481949, 994662, 675239, 416390, 571326, 398593, 985444, 676539, 4017, 970153, 460724, 381861, 868968, 253475, 885729, 868908, 29377, 222576, 374555, 689977, 841045, 892195, 207042, 51863, 62962, 757709, 152206, 512481, 568571, 669585, 603121, 592907, 27841, 194984, 213905, 523248, 66515, 902340, 118817, 55296, 894365, 716861, 185936, 722106, 957440, 261357, 376681, 78603, 952194, 920094, 217643, 214107, 68227, 337534, 485868, 894104, 462185, 804546, 28155, 159078, 150612, 63025, 521056, 1952, 986112, 725571, 794414, 245581, 92077, 416521, 488209, 401472, 707624, 546651, 543136, 857065, 689900, 152021, 575048, 877194, 786346, 742787, 470932, 337664, 599303, 202049, 244532, 235980, 309103, 898716, 608688, 76896, 962759, 876389, 783964, 34177, 231387, 958531, 497563, 691299, 50844, 114609, 26953, 409810, 47729, 142368, 424371, 19040, 743002, 211793, 276728, 901140, 981875, 81241, 110996, 10093, 433908, 808505, 441186, 650701, 151267, 239066, 123835, 769429, 142486, 326977, 269332, 738192, 737915, 740815, 649607, 416016, 715158, 620895, 856570, 208911, 525807, 751934, 524370, 478115, 492112, 458417, 540307, 186463, 447817, 161153, 596113, 934463, 909198, 695305, 919774, 833928, 540078, 614496, 26442, 601770, 60532, 472830, 651601, 188782, 59066, 751209, 379437, 764001, 567383, 106887, 197783, 915982, 196625, 112162, 652516, 143620, 163447, 618035, 987483, 840121, 584586, 153453, 363393, 126752, 818463, 44615, 977118, 225156, 155483, 353951, 678289, 270145, 210799, 286549, 635244, 704567, 839512, 369934, 641276, 642831, 714219, 502949, 540333, 352562, 935523, 701575, 538245, 473519, 531970, 884847, 38189, 532098, 990377, 4429, 610896, 56095, 711037, 770661, 292547, 355214, 590594, 740665, 208954, 166392, 471425, 30218, 483175, 250841, 777687, 659886, 499673, 870166, 484212, 181760, 201512, 144555, 536739, 383309, 28963, 829709, 922521, 556713, 370854, 897031, 529751, 279169, 744702, 724514, 371760, 502, 372658, 509151, 744264, 924518, 248441, 467352, 541220, 792987, 402694, 864482, 322931, 21401, 394377, 963776, 657208, 231169, 854046, 402044, 689214, 163618, 610677, 349452, 334351, 557902, 521371, 13949, 367523, 782185, 504887, 783375, 176441, 424702, 675836, 299992, 304449, 933486, 156723, 869314, 203154, 530715, 992850, 671000, 337861, 550605, 649136, 683193, 488200, 788143, 975478, 954303, 377550, 397246, 219924, 6524, 782264, 391739, 525114, 879668, 504449, 556267, 802126, 324385, 951609, 63149, 218621, 718232, 188175, 719159, 636058, 207059, 178015, 152681, 2770, 482175, 397827, 356535, 936244, 930487, 918426, 179397, 309123, 830227, 815620, 858015, 508470, 555117, 376533, 829535, 708381, 7734, 835796, 425197, 774382, 10301, 581026, 841236, 960256, 323990, 488862, 78155, 935384, 984317, 790328, 867609, 33085, 170516, 781840, 500209, 825732, 976223, 992067, 250054, 830792, 994209, 405076, 45020, 643570, 554134, 416835, 176337, 59287, 206329, 62016, 380014, 213935, 924738, 22269, 853902, 889442, 445343, 723291, 47034, 88202, 859086, 105415, 556389, 859615, 35995, 945646, 171511, 865641, 277835, 622685, 913288, 835386, 641024, 948568, 789935, 325234, 415004, 425949, 621804, 736846, 360395, 614497, 363486, 25072, 110334, 925179, 282743, 835631, 920435, 453409, 634346, 478948, 230684, 231473, 479990, 97301, 493818, 544870, 887605, 768306, 926776, 852346, 598818, 455150, 358508, 511005, 614424, 822131, 638006, 19696, 136747, 921814, 366130, 468952, 740346, 16622, 755677, 64293, 728183, 988214, 343693, 962176, 16709, 559625, 16321, 746702, 661712, 219274, 325186, 476109, 7220, 744795, 834361, 956787, 416492, 746815, 270398, 507152, 959252, 203156, 843965, 58687, 926794, 707821, 506528, 672387, 467467, 361223, 19384, 858573, 990235, 949901, 41609, 618274, 431482, 897612, 855170, 576485, 92007, 607270, 470764, 248642, 700753, 775335, 812873, 1309, 922801, 903377, 338688, 489074, 202185, 772942, 575629, 375278, 8796, 961772, 391556, 163234, 470394, 733657, 209633, 572149, 556898, 370499, 515559, 767677, 292168, 299068, 122584, 198757, 113275, 626730, 213838, 244197, 873144, 766012, 380992, 596444, 711811, 826239, 107391, 146833, 549530, 540284, 66869, 381689, 660909, 690715, 84880, 869901, 189198, 934561, 703281, 321984, 711086, 820570, 169740, 705153, 909107, 888228, 550147, 93966, 216536, 775333, 437470, 169860, 250769, 895618, 862792, 797911, 474282, 36804, 541183, 552124, 543962, 262507, 75242, 435387, 945919, 396462, 347988, 632406, 645719, 110484, 831352, 276055, 823421, 980351, 239137, 291112, 100706, 97312, 962862, 240856, 448215, 78904, 702046, 48716, 542176, 428800, 737973, 289786, 513024, 758598, 262033, 256010, 343030, 470829, 830040, 278564, 855641, 417649, 188045, 396662, 917269, 761221, 461503, 348738, 616075, 257976, 612895, 832974, 893709, 653880, 164185, 408418, 556362, 758898, 849893, 93011, 181872, 402499, 16659, 676668, 815790, 503358, 762046, 458708, 745135, 587285, 67893, 141434, 555047, 283504, 735651, 359832, 394108, 403524, 58724, 200258, 429782, 47954, 643788, 830719, 674410, 342814, 167535, 913973, 308202, 625421, 882247, 456147, 447023, 328700, 770172, 1129, 867364, 38210, 396578, 277215, 879166, 736837, 623052, 441489, 799948, 718972, 183954, 622780, 943216, 886894, 612356, 816031, 86041, 748214, 775564, 391356, 65300, 539467, 792313, 689524, 280284, 288826, 260924, 619386, 763864, 899958, 459820, 988012, 306983, 712924, 793374, 760640, 206134, 858316, 685067, 708179, 823724, 676130, 963423, 839176, 274402, 924554, 507664, 479639, 901974, 535409, 130652, 98086, 214203, 113098, 857113, 14698, 88530, 874580, 896324, 492895, 399667, 203963, 438286, 402071, 412549, 54848, 260675, 695490, 595768, 82513, 533441, 50515, 302347, 70327, 403114, 389669, 106744, 419134, 181253, 921213, 592131, 863438, 681361, 204517, 567852, 987895, 437730, 841080, 729427, 621681, 518798, 502876, 761505, 290478, 648230, 671226, 582432, 29784, 2536, 502548, 482291, 998853, 979122, 299222, 508856, 938602, 596703, 505489, 660134, 274817, 716484, 327248, 579964, 445446, 257339, 365883, 976079, 536269, 395873, 682374, 300010, 54122, 725694, 555385, 514634, 405692, 472484, 703858, 130907, 198005, 712848, 893476, 114285, 151091, 772144, 749863, 79945, 99603, 699928, 54018, 337016, 108285, 467832, 591021, 277992, 246231, 179052, 627404, 800485, 631893, 331795, 999271, 497242, 887663, 631999, 26722, 755984, 159928, 729556, 57919, 710554, 465406, 52387, 799653, 772802, 554296, 797065, 150899, 482727, 117882, 465643, 708050, 686727, 755334, 308416, 140396, 727936, 793259, 860455, 534156, 535577, 884719, 217118, 126208, 677895, 306984, 672522, 54984, 205793, 821557, 147448, 324415, 949862, 113587, 930069, 901960, 217878, 977654, 560290, 413484, 517097, 814927, 651757, 650865, 802554, 649738, 970784, 794981, 330626, 830645, 504068, 939725, 839998, 161304, 116717, 811326, 429061, 845581, 97391, 673104, 802185, 470172, 843661, 323468, 280312, 584845, 410117, 63580, 341186, 584261, 798012, 541395, 120813, 621989, 431734, 738669, 109019, 372535, 71461, 126494, 192581, 39288, 667072, 66326, 184448, 45875, 214170, 619082, 183585, 57072, 690030, 741568, 905838, 276205, 609955, 531151, 332479, 302453, 384576, 150727, 464647, 295147, 814426, 146900, 901348, 844456, 336168, 447880, 597736, 865816, 574144, 223069, 850502, 47222, 455086, 105058, 163526, 391025, 475763, 932320, 385580, 494149, 382437, 183070, 83452, 534653, 831304, 860702, 629406, 172132, 769388, 410238, 808469, 658160, 876341, 127252, 205595, 232386, 231981, 548658, 472244, 688542, 708918, 78839, 195735, 740751, 994939, 309127, 435871, 653161, 179574, 329557, 294710, 79709, 376572, 196539, 517684, 737018, 269533, 769083, 834227, 890786, 735615, 225974, 644270, 11311, 328284, 735381, 951981, 917889, 903959, 954442, 877515, 158063, 469498, 159083, 34350, 223035, 80631, 246553, 209929, 878161, 690391, 657133, 521267, 118728, 916898, 784910, 389468, 197026, 834009, 683369, 176017, 983471, 997061, 592194, 903176, 949995, 218109, 913331, 311536, 308756, 358523, 293811, 435386, 800850, 220066, 737876, 185105, 794420, 307455, 536095, 901711, 36275, 144059, 92423, 894402, 194031, 890448, 506688, 884645, 153266, 66061, 642475, 474943, 90181, 870639, 781176, 339941, 523257, 133263, 215964, 592683, 980563, 783960, 342400, 699528, 22505, 954785, 641557, 597048, 762510, 225062, 486953, 514484, 932854, 124712, 794986, 64514, 80065, 255362, 862155, 174903, 180236, 449483, 23424, 521153, 439517, 558873, 517776, 850230, 29697, 263643, 657440, 644206, 303496, 582518, 364784, 335511, 703165, 10533, 49442, 989467, 18717, 930687, 435512, 323739, 926063, 479116, 401815, 981397, 586818, 560829, 41380, 652829, 805740, 579016, 302151, 97821, 452890, 782834, 428246, 892244, 692540, 76429, 79605, 346622, 305796, 401349, 8316, 842219, 613332, 214374, 187303, 866400, 20956, 833588, 530611, 188495, 613376, 223728, 369701, 392753, 732960, 854673, 419977, 386794, 226889, 365572, 713187, 721916, 106106, 682828, 455512, 467306, 639043, 302157, 392244, 142222, 931234, 352752, 474927, 490670, 764048, 865277, 607158, 981742, 577829, 992540, 708993, 239461, 542845, 419477, 791683, 487408, 590020, 424731, 214435, 672256, 569242, 976790, 572658, 713891, 619659, 420483, 776561, 110039, 108155, 143630, 952624, 982312, 754305, 281059, 862117, 437053, 792695, 575651, 303194, 500438, 961958, 235423, 683708, 717264, 721116, 458687, 208047, 276083, 761563, 489750, 224881, 514367, 438197, 895637, 816221, 910399, 893798, 635801, 454388, 936194, 867881, 188083, 420071, 98559, 416873, 770673, 874930, 185938, 237161, 31311, 621416, 938123, 34468, 680326, 661281, 824420, 155829, 875920, 529924, 39195, 260786, 164971, 47126, 339773, 257462, 990757, 525259, 924367, 878837, 939875, 597062, 804958, 543714, 47483, 619663, 181720, 244394, 271731, 61037, 38424, 327953, 827733, 328218, 912863, 858618, 846769, 532916, 424553, 674647, 233250, 845205, 864762, 858595, 924429, 816268, 663244, 816569, 110013, 578205, 701104, 933228, 989943, 513174, 574730, 387181, 420809, 482457, 728464, 541872, 431942, 355570, 974773, 278459, 955740, 747899, 631098, 313310, 206655, 799223, 453658, 646157, 914027, 660416, 686750, 807986, 371503, 551177, 723652, 694679, 667980, 394801, 952868, 642010, 534322, 674676, 304913, 386761, 859146, 472263, 768910, 298837, 760459, 61667, 843078, 243859, 26418, 883758, 341401, 640536, 22957, 238474, 457326, 65055, 333202, 263941, 157409, 43486, 861490, 932255, 798505, 432792, 903705, 397832, 171637, 945074, 974188, 174386, 962889, 645488, 87657, 412576, 684777, 661019, 279586, 157459, 28889, 918242, 730072, 425973, 3297, 260131, 45685, 295108, 224144, 12792, 23254, 100585, 643672, 95623, 581053, 652190, 1001, 918393, 188946, 946445, 415619, 946679, 686733, 961473, 537044, 649266, 62688, 542459, 257615, 335108, 155476, 794348, 721545, 155964, 30969, 59870, 917670, 897782, 828723, 536622, 300860, 341859, 506963, 225987, 312339, 557772, 590822, 257203, 903097, 364051, 74393, 660362, 28115, 819250, 766762, 553083, 379493, 210034, 605428, 205874, 527590, 992622, 61680, 660276, 627645, 580728, 956842, 938024, 117176, 490484, 689944, 936944, 863264, 614159, 761574, 855159, 331970, 232206, 50154, 653506, 848906, 634753, 447810, 102275, 418695, 865946, 418510, 700871, 760846, 94784, 120052, 118094, 250195, 963531, 556197, 373641, 840663, 287858, 126345, 375470, 489100, 117260, 158005, 175604, 782162, 728485, 681933, 292731, 734528, 693495, 500335, 988925, 509969, 787631, 937882, 174007, 797277, 761174, 550403, 547847, 589106, 601946, 667078, 191945, 524793, 899247, 237151, 134572, 190836, 414097, 816406, 999394, 647135, 577487, 355561, 899793, 642947, 199237, 395274, 230984, 319777, 381746, 113723, 942605, 608165, 207891, 259588, 998205, 794193, 747033, 744431, 100864, 21360, 733640, 113535, 487184, 542603, 614825, 856154, 106671, 307590, 311892, 761047, 902189, 716189, 613525, 754137, 827753, 984068, 632906, 724606, 41756, 590199, 325616, 326352, 266044, 225418, 77902, 24304, 404225, 404305, 937849, 921664, 206181, 907691, 556288, 201423, 502809, 879704, 969736, 894669, 950657, 765876, 886738, 815849, 268464, 587086, 101673, 626070, 223055, 633105, 588159, 422627, 527327, 8528, 796097, 674980, 792536, 483992, 673673, 610048, 551797, 697468, 818118, 79816, 272849, 240980, 335554, 706247, 630982, 123393, 871061, 706225, 428854, 57124, 280115, 802060, 190221, 491492, 761833, 951586, 555499, 798224, 953995, 17652, 635255, 529193, 186619, 843068, 541075, 933140, 967595, 726402, 579979, 126918, 418240, 643611, 179098, 286992, 750521, 664833, 366962, 668184, 807143, 592662, 700104, 47873, 792937, 689575, 827724, 791161, 984460, 735051, 957597, 509686, 455128, 325840, 350481, 866440, 555743, 632639, 66747, 422350, 972766, 632497, 123594, 775673, 784546, 222167, 45318, 956154, 802432, 661621, 781624, 991641, 12944, 644820, 599494, 486031, 794610, 143244, 895325, 763041, 434991, 735636, 472324, 486988, 128306, 222243, 695855, 98927, 566163, 448631, 829313, 619614, 448241, 538656, 723659, 316163, 334571, 8686, 848882, 964320, 507401, 897679, 910235, 508922, 247270, 945987, 480103, 338182, 369297, 483218, 551660, 65145, 308665, 541033, 373038, 25150, 394389, 701039, 16618, 634172, 550663, 530482, 151584, 9944, 821362, 571344, 157630, 655224, 284592, 987711, 935380, 534994, 855503, 854763, 408460, 208672, 519897, 300747, 754106, 929076, 941830, 847735, 549385, 491562, 742664, 745442, 584481, 408875, 549037, 953725, 563699, 935997, 179246, 225195, 694237, 165304, 128422, 173101, 800782, 49813, 360646, 309848, 748499, 234033, 406362, 309713, 869555, 155646, 674196, 711177, 805991, 154675, 714350, 380217, 576679, 932321, 513658, 290510, 809355, 361861, 371866, 816648, 340275, 613622, 440412, 550214, 787155, 462385, 350640, 748490, 327304, 370765, 502208, 979728, 599616, 444764, 255764, 263309, 880325, 649051, 55371, 654023, 407128, 596483, 994011, 101336, 965352, 925691, 413033, 129147, 950123, 181145, 40333, 288176, 546181, 881392, 869446, 100439, 504862, 509603, 868354, 391539, 935907, 928536, 892283, 411099, 510700, 87325, 627418, 822040, 953418, 325208, 783285, 3387, 575614, 61328, 800503, 852079, 410324, 537178, 321630, 926455, 330231, 416796, 146029, 602705, 79416, 99283, 44365, 696898, 610188, 5818, 665163, 222725, 839777, 798113, 843947, 600374, 519644, 456902, 249679, 93894, 397995, 878648, 863896, 157240, 737512, 943000, 161475, 791529, 401879, 359523, 725297, 90756, 317470, 425890, 685925, 131826, 233398, 43264, 527465, 696322, 424136, 252327, 284841, 817808, 554211, 321, 104308, 936158, 771779, 525219, 903501, 249901, 842702, 335499, 122432, 353935, 957899, 122948, 284077, 23056, 57909, 658740, 919545, 273412, 110186, 318919, 183617, 172279, 599577, 914680, 919313, 387311, 149297, 439921, 497319, 346784, 499548, 972267, 234570, 664959, 994181, 673778, 109154, 342195, 617942, 966646, 264949, 753877, 419569, 187398, 942687, 429174, 503047, 54749, 178990, 194831, 353800, 360184, 244795, 667863, 18454, 646568, 87987, 106861, 555031, 509109, 416662, 86763, 627626, 573713, 746966, 818453, 267921, 844956, 572075, 37836, 347808, 181020, 860290, 897776, 375709, 595284, 939914, 429199, 974069, 385529, 470204, 443869, 869364, 447002, 281957, 528777, 228110, 900444, 720634, 626473, 956673, 834935, 932112, 875622, 299450, 515325, 772623, 777511, 432873, 122388, 509358, 996050, 742354, 179291, 20417, 971808, 841793, 768541, 189836, 485611, 440918, 455099, 480060, 534917, 633806, 757379, 139226, 419110, 366549, 636896, 191417, 100013, 797541, 27364, 97196, 725678, 608319, 594060, 256412, 389641, 811195, 143432, 483672, 691341, 486146, 313652, 143169, 667966, 350308, 530245, 400696, 312198, 312802, 582909, 125644, 990909, 377530, 289722, 571879, 799156, 510967, 269629, 300914, 910313, 927538, 112547, 945913, 735145, 113521, 714097, 61899, 634394, 36949, 706597, 271806, 338730, 24731, 283706, 82805, 723637, 190429, 40438, 330730, 635628, 85845, 91288, 730261, 364210, 761023, 932181, 164923, 937039, 682826, 489064, 600529, 793278, 770447, 560185, 694128, 107823, 132431, 242523, 251593, 624619, 644630, 708884, 303090, 793608, 684755, 465370, 890774, 469651, 290677, 417698, 738596, 40909, 255775, 883139, 270864, 371287, 716538, 185273, 177889, 754726, 801768, 180895, 140856, 825485, 262136, 951606, 427755, 138905, 20111, 414624, 404457, 639478, 117819, 998386, 146055, 898188, 965670, 711704, 219677, 198088, 741444, 941798, 88604, 113221, 196836, 115586, 9613, 39933, 499835, 283096, 65390, 105333, 130154, 795644, 326284, 674797, 398853, 550225, 853532, 232411, 298222, 460070, 583577, 244558, 765417, 929242, 586680, 638358, 535134, 495902, 519207, 379705, 531702, 266470, 697243, 514354, 129461, 130033, 314497, 898033, 542600, 305011, 594580, 483135, 720560, 748773, 18289, 549592, 140543, 460860, 753386, 328497, 381659, 125324, 991630, 815696, 126155, 423080, 953486, 612005, 304209, 978813, 997839, 786918, 536120, 447058, 992527, 88879, 426265, 83752, 233966, 669138, 389360, 384433, 29628, 341369, 992117, 305768, 794517, 466407, 647194, 27387, 84959, 67277, 924363, 545222, 928154, 362836, 825772, 770165, 624328, 94475, 786068, 138729, 942459, 924387, 633637, 174665, 323488, 72643, 691153, 248182, 238765, 773159, 476622, 6068, 339378, 928097, 653788, 385611, 964415, 814198, 257155, 639930, 304869, 244147, 306351, 793687, 258, 825348, 506595, 604186, 378957, 594342, 996917, 526436, 357053, 609082, 360634, 524782, 124944, 812497, 190957, 712767, 458818, 236317, 997013, 371049, 499351, 702420, 737829, 244562, 726050, 185248, 870259, 543462, 696099, 251060, 535239, 871871, 218428, 361597, 473453, 961538, 458114, 783434, 573097, 40890, 225456, 381451, 28139, 604228, 418155, 715116, 526554, 430458, 596688, 863732, 135390, 232543, 988005, 194994, 265325, 319543, 662686, 661816, 548062, 559917, 269227, 261945, 851904, 518423, 135707, 672083, 541633, 112368, 983178, 44115, 874265, 539428, 962426, 18377, 10223, 55159, 587474, 706954, 767928, 371821, 468377, 3933, 105897, 263921, 859228, 18981, 730357, 414615, 957463, 907518, 468777, 252970, 403316, 608933, 421421, 164600, 353602, 290104, 701660, 42565, 1430, 648283, 645367, 460123, 848405, 538227, 428573, 949186, 618307, 507480, 142690, 591872, 344643, 864615, 827023, 165324, 589985, 418770, 891298, 940155, 605635, 977214, 208767, 572178, 219820, 943014, 507036, 908604, 566150, 115780, 785533, 704589, 12470, 580618, 617030, 100067, 566374, 216647, 263895, 915298, 550411, 526404, 415479, 549678, 119141, 480377, 186804, 564317, 1387, 902381, 309375, 227633, 631420, 119654, 971761, 557153, 864632, 97878, 559813, 135550, 802298, 203896, 237497, 512339, 282117, 285542, 14443, 162012, 778564, 893079, 18260, 696971, 849572, 24400, 261018, 197744, 349913, 639384, 385835, 755909, 592553, 524274, 576978, 354650, 680350, 83553, 519082, 102420, 902748, 687827, 682683, 512888, 907450, 806772, 974385, 864271, 213265, 2971, 626575, 963010, 401734, 118748, 451123, 50503, 127532, 407060, 708244, 620649, 11783, 992538, 2668, 582143, 882843, 763060, 862133, 40922, 565948, 720270, 869492, 546837, 408588, 600888, 502317, 739060, 226331, 25283, 960810, 856162, 51175, 365183, 788711, 125018, 948780, 235514, 217419, 127391, 559680, 658099, 893836, 960395, 33886, 382828, 383308, 410126, 772634, 960809, 314802, 842200, 577854, 975777, 27611, 507397, 262819, 667193, 559308, 413492, 504366, 429011, 180846, 431768, 485313, 372461, 54922, 699602, 293597, 820270, 437454, 559990, 84585, 960758, 517150, 929383, 251421, 400651, 718819, 476088, 189487, 300766, 544151, 839482, 720213, 851451, 657488, 203382, 464521, 579221, 570884, 339547, 809544, 270399, 379518, 830617, 320030, 625986, 238875, 780985, 222040, 980435, 388137, 420721, 997922, 654241, 493216, 559967, 753868, 972328, 618514, 182129, 201819, 654719, 448640, 174647, 713655, 31634, 873164, 274072, 296863, 511513, 965813, 407424, 753620, 767110, 614979, 819013, 346332, 215633, 821676, 942705, 770237, 129619, 975670, 925799, 686710, 125872, 758621, 785891, 70014, 84052, 143790, 392441, 364928, 512180, 544611, 128162, 63766, 844852, 457530, 544813, 910483, 65074, 314281, 454836, 157094, 749758, 877339, 745892, 19957, 947507, 716348, 56519, 670469, 816807, 789646, 316848, 448860, 971668, 915362, 158630, 711032, 214287, 779369, 968116, 399969, 651856, 490728, 522688, 69594, 350854, 932626, 875524, 653039, 12126, 497596, 141485, 860038, 307974, 19182, 668573, 893742, 599217, 108257, 978809, 99663, 155327, 398645, 461930, 89507, 162511, 951944, 804428, 590334, 696975, 553381, 314744, 54961, 251823, 590918, 279286, 276766, 681546, 942771, 667305, 914809, 925507, 245429, 441157, 777654, 888330, 877248, 36650, 43314, 662130, 800658, 178778, 812398, 274765, 616556, 922891, 842287, 48360, 863321, 796731, 87850, 324187, 743024, 18588, 27009, 977443, 972184, 374004, 991639, 619013, 286780, 510634, 524095, 700154, 902453, 78638, 304958, 714345, 489296, 803153, 988104, 323552, 88051, 73291, 525256, 518901, 452341, 907158, 661798, 809764, 270901, 316703, 94541, 57575, 700553, 357233, 710856, 593187, 809315, 58064, 462253, 785105, 77996, 226744, 4722, 73512, 409529, 178739, 371417, 626443, 704649, 615602, 968611, 171710, 852108, 677693, 78880, 325817, 479423, 314654, 583153, 672942, 39599, 276377, 731234, 8497, 361294, 635823, 517201, 735768, 825084, 667195, 474088, 111423, 18675, 824257, 964764, 667130, 564191, 142267, 527694, 969588, 596577, 616291, 10643, 741263, 795701, 688930, 776747, 867190, 154305, 520740, 363185, 677681, 366365, 211326, 451198, 559439, 641324, 209781, 850806, 122646, 496725, 599256, 609450, 557292, 747291, 930768, 939106, 874021, 510620, 865851, 295149, 868870, 317704, 981565, 106896, 721959, 834784, 215495, 517735, 749212, 903046, 393050, 162535, 282485, 284730, 483380, 590975, 551900, 924763, 109333, 911734, 65591, 336337, 106968, 636390, 594280, 941300, 746988, 57934, 259685, 695596, 500382, 496499, 920032, 570042, 12589, 243783, 76177, 114166, 643000, 727409, 986157, 444655, 970552, 229012, 242947, 229773, 102498, 773640, 565054, 142211, 75056, 74493, 566431, 567736, 287245, 319007, 118031, 383167, 746106, 391213, 872263, 57083, 210484, 113827, 442324, 349560, 567278, 400305, 981289, 473033, 728250, 274352, 915347, 548609, 996898, 325400, 311374, 582569, 39028, 328550, 646520, 242504, 516047, 156867, 648907, 478489, 322554, 664374, 276219, 526247, 374734, 192474, 281782, 685187, 4096, 865197, 377924, 826270, 454371, 843329, 73740, 391069, 192347, 408195, 870115, 459158, 830873, 432980, 254945, 155818, 737289, 405468, 120184, 306751, 890998, 726465, 666026, 580531, 619536, 934755, 514504, 767477, 158192, 566463, 79902, 915288, 584227, 885026, 363495, 431365, 974248, 891987, 739819, 92624, 275309, 675098, 51688, 762089, 350131, 181460, 869639, 912425, 392434, 273228, 386345, 568933, 987301, 460277, 302927, 411672, 945579, 115918, 667266, 65153, 319888, 810776, 328271, 304433, 193175, 605270, 526216, 438035, 776011, 814815, 563422, 633967, 477596, 904581, 132190, 607173, 261569, 866614, 637223, 239558, 601618, 448364, 578912, 743873, 986473, 792860, 459512, 539774, 931314, 563972, 549608, 743438, 39909, 268997, 850687, 792666, 215400, 417311, 416050, 26288, 957884, 176086, 433931, 829170, 304185, 383369, 66173, 204389, 75498, 365979, 657054, 254192, 32394, 417087, 505085, 820844, 842466, 942560, 921100, 426704, 59106, 15445, 894151, 762155, 648867, 457271, 411726, 536064, 322721, 762377, 606652, 820938, 83796, 677844, 862094, 906945, 750693, 446889, 309902, 442102, 311955, 119851, 935489, 704351, 13843, 913009, 620470, 503907, 909218, 180274, 673397, 337173, 307751, 629404, 313087, 242917, 751757, 799025, 481465, 651336, 24265, 973042, 341175, 845840, 173486, 325003, 687343, 132170, 118958, 424898, 777529, 470159, 677501, 759939, 962478, 664096, 964073, 60104, 419994, 520981, 7570, 68872, 156989, 952298, 724462, 164158, 138327, 304345, 814157, 120062, 498287, 994646, 810346, 719730, 530643, 766629, 988644, 157378, 128555, 228689, 180667, 540618, 46612, 841622, 684040, 243333, 12316, 421151, 106433, 32288, 297065, 904271, 418758, 311482, 650470, 623534, 350374, 763083, 762892, 310214, 577205, 845827, 322760, 451432, 712682, 224029, 111937, 799715, 894671, 48166, 227784, 664154, 409953, 94561, 263640, 154936, 976354, 443125, 312077, 144353, 272118, 312561, 917892, 222851, 103230, 889512, 934189, 853667, 126766, 230196, 70509, 593070, 442143, 490820, 451187, 656148, 787547, 854713, 679275, 552206, 258710, 300235, 175663, 498040, 645308, 864533, 463390, 748089, 29702, 14589, 956437, 26108, 438430, 727836, 65799, 434986, 277607, 814517, 924787, 312625, 179184, 887685, 739651, 67547, 244126, 515210, 136383, 845113, 413095, 790405, 654145, 838335, 514025, 79433, 59042, 53410, 97356, 175692, 356215, 311054, 197027, 266307, 142148, 2936, 340373, 318372, 585618, 150326, 284432, 804266, 806180, 470487, 898857, 52412, 123032, 301750, 778326, 354183, 61297, 734517, 958326, 157066, 343107, 719412, 380773, 565961, 352922, 544603, 38373, 105828, 976642, 750647, 581138, 223121, 151652, 972416, 8956, 900102, 858584, 854173, 309354, 954272, 308399, 117500, 723342, 544405, 317472, 673481, 184918, 102697, 475680, 800743, 805798, 933066, 22627, 639472, 4013, 703028, 53469, 469679, 112559, 823251, 79961, 612579, 336595, 811009, 778257, 131412, 50550, 440883, 259713, 709003, 605176, 789575, 482787, 366731, 649129, 995544, 516032, 509898, 202888, 18826, 274720, 829426, 484004, 34733, 222684, 664857, 422736, 781696, 537059, 272659, 404240, 27416, 199473, 317274, 827454, 558786, 332175, 599069, 283698, 825616, 123675, 951145, 339610, 814449, 692008, 756847, 409144, 199436, 511355, 434682, 676741, 255999, 40894, 22998] \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..91f358e --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[build_ext] + +[bdist_wheel] +universal = 1 + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..980bbba --- /dev/null +++ b/setup.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +# `python setup.py install` should build the C extension if you have Cython + +import os +from setuptools import setup +from setuptools import Extension + +try: + from Cython.Distutils import build_ext + ext_modules = [Extension("bintrees.cython_trees", ["bintrees/ctrees.c", "bintrees/cython_trees.pyx"]), + ] + commands = {'build_ext': build_ext} +except ImportError: + ext_modules = [] + commands = {} + + +def read(fname): + with open(os.path.join(os.path.dirname(__file__), fname)) as f: + return f.read() + +setup( + name='bintrees', + version='2.0.4', + description='Package provides Binary-, RedBlack- and AVL-Trees in Python and Cython.', + author='mozman', + url='http://bitbucket.org/mozman/bintrees', + download_url='http://bitbucket.org/mozman/bintrees/downloads', + author_email='mozman@gmx.at', + cmdclass=commands, + ext_modules=ext_modules, + packages=['bintrees'], + long_description=read('README.rst')+read('NEWS.rst'), + platforms="OS Independent", + license="MIT License", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Cython", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries :: Python Modules", + ], +) diff --git a/testresults.ods b/testresults.ods new file mode 100644 index 0000000000000000000000000000000000000000..6a85b7400293b73b83003cb900a91fb64868f5f8 GIT binary patch literal 21600 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 jDeYbk$V({&HSk|7th!JD9r exists and == 12 + self.assertEqual(value, 12) + value = tree.setdefault(99, 77) + self.assertEqual(value, 77) + + def test_018_keys(self): + tree = self.TREE_CLASS(self.default_values1) + result = list(iter(tree)) + self.assertEqual(result, [12, 16, 34, 35, 45, 57]) + self.assertEqual(result, list(tree.keys())) + + def test_018a_keyslice(self): + tree = self.TREE_CLASS(self.default_values1) + result = list(tree.key_slice(15, 36)) + self.assertEqual(result, [16, 34, 35]) + + def test_018b_keyslice(self): + tree = self.TREE_CLASS(self.default_values1) + result = list(tree.key_slice(15, 35)) + self.assertEqual(result, [16, 34]) + + def test_018c_keyslice_reverse(self): + tree = self.TREE_CLASS(self.default_values1) + result = list(tree.key_slice(15, 36, reverse=True)) + self.assertEqual(list(result), [35, 34, 16]) + + def test_018d_slice_from_start(self): + # values: 1, 2, 3, 4, 8, 9, 10, 11 + tree = self.TREE_CLASS(self.slicetest_data) + result = list(tree[:4]) + self.assertEqual(list(result), [1, 2, 3]) + + def test_018e_slice_til_end(self): + # values: 1, 2, 3, 4, 8, 9, 10, 11 + tree = self.TREE_CLASS(self.slicetest_data) + result = list(tree[8:]) + self.assertEqual(list(result), [8, 9, 10, 11]) + + def test_018f_slice_from_start_til_end(self): + # values: 1, 2, 3, 4, 8, 9, 10, 11 + tree = self.TREE_CLASS(self.slicetest_data) + result = list(tree[:]) + self.assertEqual(list(result), [1, 2, 3, 4, 8, 9, 10, 11]) + + def test_018g_slice_produces_keys(self): + tree = self.TREE_CLASS([(1, 100), (2, 200), (3, 300)]) + result = list(tree[:]) + self.assertEqual(list(result), [1, 2, 3]) + + def test_019_values(self): + tree = self.TREE_CLASS(self.default_values1) + result = list(tree.values()) + self.assertEqual(result, [12, 16, 34, 35, 45, 57]) + self.assertEqual(result, list(tree.values())) + + def test_020_items(self): + tree = self.TREE_CLASS(self.default_values1) + result = list(tree.items()) + self.assertEqual(result, list(sorted(self.default_values1))) + self.assertEqual(result, list(tree.items())) + + def test_020a_items_of_empty_tree(self): + tree = self.TREE_CLASS() + # empty tree also has to return an iterable + result = [item for item in tree.items()] + self.assertEqual(0, len(result)) + + def test_021_keys_reverse(self): + tree = self.TREE_CLASS(self.default_values1) + result = list(tree.keys(reverse=True)) + self.assertEqual(result, list(reversed([12, 16, 34, 35, 45, 57]))) + self.assertEqual(result, list(tree.keys(reverse=True))) + + def test_022_values_reverse(self): + tree = self.TREE_CLASS(self.default_values1) + result = list(tree.values(reverse=True)) + self.assertEqual(result, list(reversed([12, 16, 34, 35, 45, 57]))) + self.assertEqual(result, list(tree.values(reverse=True))) + + def test_023_items_reverse(self): + tree = self.TREE_CLASS(self.default_values1) + result = list(tree.items(reverse=True)) + self.assertEqual(result, list(tree.items(reverse=True))) + + def test_024_get(self): + tree = self.TREE_CLASS(self.default_values1) + self.assertEqual(tree.get(34), 34) + self.assertEqual(tree.get(99), None) + + def test_025_get_default(self): + tree = self.TREE_CLASS(self.default_values1) + self.assertEqual(tree.get(99, -10), -10) # get default value + self.assertEqual(tree.get(34, -10), 34) # key exist + self.assertEqual(tree.get(7, "DEFAULT"), "DEFAULT") + + def test_026_remove_child_1(self): + keys = [50, 25] + tree = self.TREE_CLASS.fromkeys(keys) + remove_key = 25 + del tree[remove_key] + self.assertTrue(check_integrity(keys, remove_key, tree)) + + def test_027_remove_child_2(self): + keys = [50, 25, 12] + tree = self.TREE_CLASS.fromkeys(keys) + remove_key = 25 + del tree[remove_key] + self.assertTrue(check_integrity(keys, remove_key, tree)) + + def test_028_remove_child_3(self): + keys = [50, 25, 12, 33] + tree = self.TREE_CLASS.fromkeys(keys) + remove_key = 25 + del tree[remove_key] + self.assertTrue(check_integrity(keys, remove_key, tree)) + + def test_029_remove_child_4(self): + keys = [50, 25, 12, 33, 40] + tree = self.TREE_CLASS.fromkeys(keys) + remove_key = 25 + del tree[remove_key] + self.assertTrue(check_integrity(keys, remove_key, tree)) + + def test_030_remove_child_5(self): + keys = [50, 25, 12, 33, 40, 37, 43] + tree = self.TREE_CLASS.fromkeys(keys) + remove_key = 25 + del tree[remove_key] + self.assertTrue(check_integrity(keys, remove_key, tree)) + + def test_031_remove_child_6(self): + keys = [50, 75, 100, 150, 60, 65, 64, 80, 66] + tree = self.TREE_CLASS.fromkeys(keys) + remove_key = 75 + del tree[remove_key] + self.assertTrue(check_integrity(keys, remove_key, tree)) + + def test_032_remove_root_1(self): + keys = [50, ] + tree = self.TREE_CLASS.fromkeys(keys) + del tree[50] + self.assertTrue(tree.is_empty) + + def test_033_remove_root_2(self): + keys = [50, 25, 12, 33, 34] + tree = self.TREE_CLASS.fromkeys(keys) + remove_key = 50 + del tree[remove_key] + self.assertTrue(check_integrity(keys, remove_key, tree)) + + def test_034_remove_root_3(self): + keys = [50, 25, 12, 33, 34, 75] + tree = self.TREE_CLASS.fromkeys(keys) + remove_key = 50 + del tree[remove_key] + self.assertTrue(check_integrity(keys, remove_key, tree)) + + def test_035_remove_root_4(self): + keys = [50, 25, 12, 33, 34, 75, 60] + tree = self.TREE_CLASS.fromkeys(keys) + remove_key = 50 + del tree[remove_key] + self.assertTrue(check_integrity(keys, remove_key, tree)) + + def test_036_remove_root_5(self): + keys = [50, 25, 12, 33, 34, 75, 60, 61] + tree = self.TREE_CLASS.fromkeys(keys) + remove_key = 50 + del tree[remove_key] + self.assertTrue(check_integrity(keys, remove_key, tree)) + + def test_037a_discard(self): + keys = [50, 25, 12, 33, 34, 75, 60, 61] + tree = self.TREE_CLASS.fromkeys(keys) + try: + tree.discard(17) + except KeyError: + self.assertTrue(False, "Discard raises KeyError") + + def test_037b_remove_keyerror(self): + keys = [50, 25, 12, 33, 34, 75, 60, 61] + tree = self.TREE_CLASS.fromkeys(keys) + self.assertRaises(KeyError, tree.remove, 17) + + def test_038_remove_shuffeld(self): + keys = [50, 25, 20, 35, 22, 23, 27, 75, 65, 90, 60, 70, 85, 57, 83, 58] + remove_keys = keys[:] + shuffle(remove_keys) + for remove_key in remove_keys: + tree = self.TREE_CLASS.fromkeys(keys) + del tree[remove_key] + self.assertTrue(check_integrity(keys, remove_key, tree)) + + def test_039_remove_random_numbers(self): + try: + fp = open('xtestkey.txt') + keys = eval(fp.read()) + fp.close() + except IOError: + keys = randomkeys(1000) + shuffle(keys) + tree = self.TREE_CLASS.fromkeys(keys) + self.assertEqual(len(tree), len(keys)) + for key in keys: + del tree[key] + self.assertEqual(len(tree), 0) + + def test_040_sort_order(self): + keys = randomkeys(1000) + tree = self.TREE_CLASS.fromkeys(keys) + generator = iter(tree) + a = next(generator) + for b in generator: + self.assertTrue(b > a) + a = b + + def test_041_pop(self): + tree = self.TREE_CLASS(self.default_values2) + data = tree.pop(8) + self.assertEqual(data, 45) + self.assertFalse(8 in tree) + self.assertRaises(KeyError, tree.pop, 8) + self.assertEqual(tree.pop(8, 99), 99) + + def test_042_pop_item(self): + tree = self.TREE_CLASS(self.default_values2) + d = dict() + while not tree.is_empty(): + key, value = tree.pop_item() + d[key] = value + expected = {2: 12, 4: 34, 8: 45, 1: 16, 9: 35, 3: 57} + self.assertEqual(expected, d) + self.assertRaises(KeyError, tree.pop_item) + + def test_043_min_item(self): + tree = self.TREE_CLASS(zip(set3, set3)) + min_item = tree.min_item() + self.assertEqual(min_item[1], 0) + + def test_044_min_item_error(self): + tree = self.TREE_CLASS() + self.assertRaises(ValueError, tree.min_item) + + def test_045_max_item(self): + tree = self.TREE_CLASS(zip(set3, set3)) + max_item = tree.max_item() + self.assertEqual(max_item[1], 999) + + def test_046_max_item_error(self): + tree = self.TREE_CLASS() + self.assertRaises(ValueError, tree.max_item) + + def test_047_min_key(self): + tree = self.TREE_CLASS(zip(set3, set3)) + minkey = tree.min_key() + self.assertEqual(minkey, 0) + self.assertEqual(minkey, min(tree)) + + def test_048_min_key_error(self): + tree = self.TREE_CLASS() + self.assertRaises(ValueError, tree.min_key) + + def test_049_max_key(self): + tree = self.TREE_CLASS(zip(set3, set3)) + maxkey = tree.max_key() + self.assertEqual(maxkey, 999) + self.assertEqual(maxkey, max(tree)) + + def test_050_min_key_error(self): + tree = self.TREE_CLASS() + self.assertRaises(ValueError, tree.max_key) + + def test_051_prev_item(self): + tree = self.TREE_CLASS(zip(set3, set3)) + prev_value = None + for key in tree.keys(): + try: + prev_item = tree.prev_item(key) + except KeyError: # only on first key + self.assertEqual(prev_value, None) + if prev_value is not None: + self.assertEqual(prev_value, prev_item[1]) + prev_value = key + + def test_052_prev_key_extreme(self): + # extreme degenerated binary tree (if unbalanced) + tree = self.TREE_CLASS.fromkeys([1, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2]) + self.assertEqual(tree.prev_key(2), 1) + + def test_053_prev_item_error(self): + tree = self.TREE_CLASS() + tree[0] = 'NULL' + self.assertRaises(KeyError, tree.prev_item, 0) + + def test_054_succ_item(self): + tree = self.TREE_CLASS(zip(set3, set3)) + succ_value = None + for key in tree.keys(reverse=True): + try: + succ_item = tree.succ_item(key) + except KeyError: # only on last key + self.assertEqual(succ_value, None) + if succ_value is not None: + self.assertEqual(succ_value, succ_item[1]) + succ_value = key + + def test_055_succ_key_extreme(self): + # extreme degenerated binary tree (if unbalanced) + tree = self.TREE_CLASS.fromkeys([15, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + self.assertEqual(tree.succ_key(10), 15) + + def test_056_succ_item_error(self): + tree = self.TREE_CLASS() + tree[0] = 'NULL' + self.assertRaises(KeyError, tree.succ_item, 0) + + def test_057_prev_key(self): + tree = self.TREE_CLASS(zip(set3, set3)) + pkey = None + for key in tree.keys(): + try: + prev_key = tree.prev_key(key) + except KeyError: # only on first key + self.assertEqual(pkey, None) + if pkey is not None: + self.assertEqual(pkey, prev_key) + pkey = key + + def test_058_prev_key_error(self): + tree = self.TREE_CLASS() + tree[0] = 'NULL' + self.assertRaises(KeyError, tree.prev_key, 0) + + def test_059_succ_key(self): + tree = self.TREE_CLASS(zip(set3, set3)) + skey = None + for key in tree.keys(reverse=True): + try: + succ_key = tree.succ_key(key) + except KeyError: # only on last key + self.assertEqual(skey, None) + if skey is not None: + self.assertEqual(skey, succ_key) + skey = key + + def test_060_succ_key_error(self): + tree = self.TREE_CLASS() + tree[0] = 'NULL' + self.assertRaises(KeyError, tree.succ_key, 0) + + def test_061_prev_succ_on_empty_trees(self): + tree = self.TREE_CLASS() + self.assertRaises(KeyError, tree.succ_key, 0) + self.assertRaises(KeyError, tree.prev_key, 0) + self.assertRaises(KeyError, tree.succ_item, 0) + self.assertRaises(KeyError, tree.prev_item, 0) + + def test_062_succ_prev_key_random_1000(self): + keys = list(set([randint(0, 10000) for _ in range(1000)])) + shuffle(keys) + tree = self.TREE_CLASS.fromkeys(keys) + + skey = None + for key in tree.keys(reverse=True): + try: + succ_key = tree.succ_key(key) + except KeyError: # only on last key + self.assertEqual(skey, None) + if skey is not None: + self.assertEqual(skey, succ_key) + skey = key + + pkey = None + for key in tree.keys(): + try: + prev_key = tree.prev_key(key) + except KeyError: # only on first key + self.assertEqual(pkey, None) + if pkey is not None: + self.assertEqual(pkey, prev_key) + pkey = key + + def test_063_pop_min(self): + tree = self.TREE_CLASS(zip(set3, set3)) + keys = sorted(set3[:]) + for key in keys: + k, v = tree.pop_min() + self.assertEqual(key, v) + + def test_064_pop_min_error(self): + tree = self.TREE_CLASS() + self.assertRaises(ValueError, tree.pop_min) + + def test_065_pop_max(self): + tree = self.TREE_CLASS(zip(set3, set3)) + keys = sorted(set3[:], reverse=True) + for key in keys: + k, v = tree.pop_max() + self.assertEqual(key, v) + + def test_066_pop_max_error(self): + tree = self.TREE_CLASS() + self.assertRaises(ValueError, tree.pop_max) + + def test_067_nlargest(self): + l = list(range(30)) + shuffle(l) + tree = self.TREE_CLASS(zip(l, l)) + result = tree.nlargest(10) + chk = [(x, x) for x in range(29, 19, -1)] + self.assertEqual(chk, result) + + def test_068_nlargest_gt_len(self): + items = list(zip(range(5), range(5))) + tree = self.TREE_CLASS(items) + result = tree.nlargest(10) + self.assertEqual(result, list(reversed(items))) + + def test_069_nsmallest(self): + l = list(range(30)) + shuffle(l) + tree = self.TREE_CLASS(zip(l, l)) + result = tree.nsmallest(10) + chk = [(x, x) for x in range(0, 10)] + self.assertEqual(chk, result) + + def test_070_nsmallest_gt_len(self): + items = list(zip(range(5), range(5))) + tree = self.TREE_CLASS(items) + result = tree.nsmallest(10) + self.assertEqual(result, items) + + def test_071_reversed(self): + tree = self.TREE_CLASS(zip(set3, set3)) + result = reversed(sorted(set3)) + for key, chk in zip(reversed(tree), result): + self.assertEqual(chk, key) + + def test_077_delslice(self): + T = self.TREE_CLASS.fromkeys([1, 2, 3, 4, 8, 9]) + tree = T.copy() + del tree[:2] + self.assertEqual(list(tree.keys()), [2, 3, 4, 8, 9]) + tree = T.copy() + del tree[1:3] + self.assertEqual(list(tree.keys()), [3, 4, 8, 9]) + tree = T.copy() + del tree[3:] + self.assertEqual(list(tree.keys()), [1, 2]) + tree = T.copy() + del tree[:] + self.assertEqual(list(tree.keys()), []) + + def test_080_intersection(self): + l1 = list(range(30)) + shuffle(l1) + l2 = list(range(15, 45)) + shuffle(l2) + tree1 = self.TREE_CLASS(zip(l1, l1)) + tree2 = self.TREE_CLASS(zip(l2, l2)) + i = tree1 & tree2 + self.assertEqual(len(i), 15) + self.assertEqual(i.min_key(), 15) + self.assertEqual(i.max_key(), 29) + + def test_081_union_keys(self): + l1 = list(range(30)) + shuffle(l1) + l2 = list(range(15, 45)) + shuffle(l2) + tree1 = self.TREE_CLASS(zip(l1, l1)) + tree2 = self.TREE_CLASS(zip(l2, l2)) + i = tree1 | tree2 + self.assertEqual(len(i), 45) + self.assertEqual(i.min_key(), 0) + self.assertEqual(i.max_key(), 44) + + def test_081_union_values(self): + l1 = list(range(30)) + shuffle(l1) + l2 = list(range(15, 45)) + shuffle(l2) + tree1 = self.TREE_CLASS(zip(l1, l1)) + tree2 = self.TREE_CLASS(zip(l2, l2)) + union_tree = tree1 | tree2 + self.assertEqual(union_tree[44], 44) + self.assertEqual(union_tree[1], 1) + + def test_082_difference(self): + l1 = list(range(30)) + shuffle(l1) + l2 = list(range(15, 45)) + shuffle(l2) + + tree1 = self.TREE_CLASS(zip(l1, l1)) + tree2 = self.TREE_CLASS(zip(l2, l2)) + i = tree1 - tree2 + self.assertEqual(len(i), 15) + self.assertEqual(i.min_key(), 0) + self.assertEqual(i.max_key(), 14) + + def test_083_symmetric_difference_keys(self): + l1 = list(range(30)) + shuffle(l1) + l2 = list(range(15, 45)) + shuffle(l2) + + tree1 = self.TREE_CLASS(zip(l1, l1)) + tree2 = self.TREE_CLASS(zip(l2, l2)) + new_tree = tree1 ^ tree2 + self.assertEqual(len(new_tree), 30) + self.assertEqual(new_tree.min_key(), 0) + self.assertEqual(new_tree.max_key(), 44) + self.assertTrue(15 not in new_tree) + self.assertTrue(29 not in new_tree) + + def test_083_symmetric_difference_values(self): + l1 = list(range(30)) + shuffle(l1) + l2 = list(range(15, 45)) + shuffle(l2) + + tree1 = self.TREE_CLASS(zip(l1, l1)) + tree2 = self.TREE_CLASS(zip(l2, l2)) + new_tree = tree1 ^ tree2 + self.assertEqual(new_tree[44], 44) + self.assertEqual(new_tree[1], 1) + + @unittest.skipIf(PYPY, "getrefcount() not supported by pypy.") + def test_084_refcount_get(self): + tree = self.TREE_CLASS(self.default_values1) # key == value + tree[700] = 701 + chk = tree[700] + count = sys.getrefcount(chk) + for _ in range(10): + chk = tree[700] + + self.assertEqual(sys.getrefcount(chk), count) + + @unittest.skipIf(PYPY, "getrefcount() not supported by pypy.") + def test_085_refcount_set(self): + tree = self.TREE_CLASS(self.default_values1) # key == value + chk = 800 + count = sys.getrefcount(chk) + tree[801] = chk + self.assertEqual(sys.getrefcount(chk), count + 1) + + @unittest.skipIf(PYPY, "getrefcount() not supported by pypy.") + def test_086_refcount_del(self): + tree = self.TREE_CLASS(self.default_values1) # key == value + chk = 900 + count = sys.getrefcount(chk) + tree[901] = chk + self.assertEqual(sys.getrefcount(chk), count + 1) + del tree[901] + self.assertEqual(sys.getrefcount(chk), count) + + @unittest.skipIf(PYPY, "getrefcount() not supported by pypy.") + def test_087_refcount_replace(self): + tree = self.TREE_CLASS(self.default_values1) # key == value + chk = 910 + count = sys.getrefcount(chk) + tree[911] = chk + self.assertEqual(sys.getrefcount(chk), count + 1) + tree[911] = 912 # replace 910 with 912 + self.assertEqual(sys.getrefcount(chk), count) + + def test_088_pickle_protocol(self): + tree = self.TREE_CLASS(self.default_values1) # key == value + pickle_str = pickle.dumps(tree, -1) + tree2 = pickle.loads(pickle_str) + self.assertEqual(len(tree), len(tree2)) + self.assertEqual(list(tree.keys()), list(tree2.keys())) + self.assertEqual(list(tree.values()), list(tree2.values())) + + # [12, 34, 45, 16, 35, 57] + def test_089_floor_item(self): + tree = self.TREE_CLASS(self.default_values1) # key == value + self.assertEqual(tree.floor_item(12), (12, 12)) + self.assertEqual(tree.floor_item(13), (12, 12)) + self.assertEqual(tree.floor_item(60), (57, 57)) + + def test_090a_floor_item_key_error(self): + tree = self.TREE_CLASS(self.default_values1) # key == value + with self.assertRaises(KeyError): + tree.floor_item(11) + + def test_090b_floor_item_empty_tree(self): + tree = self.TREE_CLASS() + with self.assertRaises(KeyError): + tree.floor_item(11) + + def test_091_floor_key(self): + tree = self.TREE_CLASS(self.default_values1) # key == value + self.assertEqual(tree.floor_key(12), 12) + self.assertEqual(tree.floor_key(13), 12) + self.assertEqual(tree.floor_key(60), 57) + + def test_092_floor_key_key_error(self): + tree = self.TREE_CLASS(self.default_values1) # key == value + with self.assertRaises(KeyError): + tree.floor_key(11) + + def test_093_ceiling_item(self): + tree = self.TREE_CLASS(self.default_values1) # key == value + self.assertEqual(tree.ceiling_item(57), (57, 57)) + self.assertEqual(tree.ceiling_item(56), (57, 57)) + self.assertEqual(tree.ceiling_item(0), (12, 12)) + + def test_094a_ceiling_item_key_error(self): + tree = self.TREE_CLASS(self.default_values1) # key == value + with self.assertRaises(KeyError): + tree.ceiling_item(60) + + def test_094a_ceiling_item_empty_tree(self): + tree = self.TREE_CLASS() + with self.assertRaises(KeyError): + tree.ceiling_item(60) + + def test_095_ceiling_key(self): + tree = self.TREE_CLASS(self.default_values1) # key == value + self.assertEqual(tree.ceiling_key(57), 57) + self.assertEqual(tree.ceiling_key(56), 57) + self.assertEqual(tree.ceiling_key(0), 12) + + def test_096_ceiling_key_key_error(self): + tree = self.TREE_CLASS(self.default_values1) # key == value + with self.assertRaises(KeyError): + tree.ceiling_key(60) + + def test_097_data_corruption(self): + # Data corruption in FastRBTree in all versions before 1.0.2: + # Error was located in the rb_insert() function in ctrees.c + + tree = self.TREE_CLASS() + 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, + 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] + for key in insert_keys: + tree[key] = "unused_data" + expected_keys = sorted(set(insert_keys)) + self.assertEqual(expected_keys, list(tree.keys()), "Data corruption in %s!" % tree.__class__) + + def test_098_foreach(self): + keys = [] + def collect(key, value): + keys.append(key) + + tree = self.TREE_CLASS(self.default_values1) # key == value + tree.foreach(collect) + self.assertEqual(list(tree.keys()), list(sorted(keys))) + + +class TestBinaryTree(CheckTree, unittest.TestCase): + TREE_CLASS = BinaryTree + + +class TestAVLTree(CheckTree, unittest.TestCase): + TREE_CLASS = AVLTree + + +class TestRBTree(CheckTree, unittest.TestCase): + TREE_CLASS = RBTree + + +class TestFastBinaryTree(CheckTree, unittest.TestCase): + TREE_CLASS = FastBinaryTree + + +class TestFastAVLTree(CheckTree, unittest.TestCase): + TREE_CLASS = FastAVLTree + + +class TestFastRBTree(CheckTree, unittest.TestCase): + TREE_CLASS = FastRBTree + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cython_avltree.py b/tests/test_cython_avltree.py new file mode 100644 index 0000000..b9f80be --- /dev/null +++ b/tests/test_cython_avltree.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Created: 28.04.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + +import sys +PYPY = hasattr(sys, 'pypy_version_info') + +import unittest +from random import randint, shuffle + +if not PYPY: + from bintrees.cython_trees import FastAVLTree + +@unittest.skipIf(PYPY, "Cython implementation not supported for pypy.") +class TestTree(unittest.TestCase): + values = [(2, 12), (4, 34), (8, 45), (1, 16), (9, 35), (3, 57)] + keys = [2, 4, 8, 1, 9, 3] + + def test_create_tree(self): + tree = FastAVLTree() + self.assertEqual(tree.count, 0) + tree.update(self.values) + self.assertEqual(tree.count, 6) + + def test_get_value(self): + tree = FastAVLTree(self.values) + for key in self.keys: + value = tree.get_value(key) + self.assertTrue(value is not None) + + def test_get_value_not(self): + tree = FastAVLTree() + self.assertRaises(KeyError, tree.get_value, 17) + + def test_properties(self): + tree = FastAVLTree(self.values) + self.assertEqual(tree.count, 6) + + def test_clear_tree(self): + tree = FastAVLTree(self.values) + tree.clear() + self.assertEqual(tree.count, 0) + + def test_insert(self): + tree = FastAVLTree() + for key in self.keys: + tree.insert(key, key) + value = tree.get_value(key) + self.assertEqual(value, key) + self.assertEqual(tree.count, 6) + + def test_remove(self): + tree = FastAVLTree(self.values) + for key in self.keys: + tree.remove(key) + self.assertRaises(KeyError, tree.get_value, key) + self.assertEqual(tree.count, 0) + + def test_remove_random_numbers(self): + keys = list(set([randint(0, 10000) for _ in range(500)])) + shuffle(keys) + tree = FastAVLTree(zip(keys, keys)) + self.assertEqual(tree.count, len(keys)) + for key in keys: + tree.remove(key) + self.assertEqual(tree.count, 0) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cython_bintree.py b/tests/test_cython_bintree.py new file mode 100644 index 0000000..fb29051 --- /dev/null +++ b/tests/test_cython_bintree.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Created: 28.04.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + + +import sys +PYPY = hasattr(sys, 'pypy_version_info') + +import unittest +from random import randint, shuffle + +if not PYPY: + from bintrees.cython_trees import FastBinaryTree + +@unittest.skipIf(PYPY, "Cython implementation not supported for pypy.") +class TestTree(unittest.TestCase): + values = [(2, 12), (4, 34), (8, 45), (1, 16), (9, 35), (3, 57)] + keys = [2, 4, 8, 1, 9, 3] + + def test_create_tree(self): + tree = FastBinaryTree() + self.assertEqual(tree.count, 0) + tree.update(self.values) + self.assertEqual(tree.count, 6) + + def test_get_value(self): + tree = FastBinaryTree(self.values) + for key in self.keys: + value = tree.get_value(key) + self.assertTrue(value is not None) + + def test_get_value_not(self): + tree = FastBinaryTree() + self.assertRaises(KeyError, tree.get_value, 17) + + def test_properties(self): + tree = FastBinaryTree(self.values) + self.assertEqual(tree.count, 6) + + def test_clear_tree(self): + tree = FastBinaryTree(self.values) + tree.clear() + self.assertEqual(tree.count, 0) + + def test_insert(self): + tree = FastBinaryTree() + for key in self.keys: + tree.insert(key, key) + value = tree.get_value(key) + self.assertEqual(value, key) + self.assertEqual(tree.count, 6) + + def test_remove(self): + tree = FastBinaryTree(self.values) + for key in self.keys: + tree.remove(key) + self.assertRaises(KeyError, tree.get_value, key) + self.assertEqual(tree.count, 0) + + def test_remove_random_numbers(self): + keys = list(set([randint(0, 10000) for _ in range(500)])) + shuffle(keys) + tree = FastBinaryTree(zip(keys, keys)) + self.assertEqual(tree.count, len(keys)) + for key in keys: + tree.remove(key) + self.assertEqual(tree.count, 0) +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_cython_rbtree.py b/tests/test_cython_rbtree.py new file mode 100644 index 0000000..cd55ec8 --- /dev/null +++ b/tests/test_cython_rbtree.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman +# Purpose: test binary trees +# Created: 28.04.2010 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + + +import sys +PYPY = hasattr(sys, 'pypy_version_info') + +import unittest +from random import randint, shuffle + +if not PYPY: + from bintrees.cython_trees import FastRBTree + +@unittest.skipIf(PYPY, "Cython implementation not supported for pypy.") +class TestTree(unittest.TestCase): + values = [(2, 12), (4, 34), (8, 45), (1, 16), (9, 35), (3, 57)] + keys = [2, 4, 8, 1, 9, 3] + + def test_create_tree(self): + tree = FastRBTree() + self.assertEqual(tree.count, 0) + tree.update(self.values) + self.assertEqual(tree.count, 6) + + def test_properties(self): + tree = FastRBTree(self.values) + self.assertEqual(tree.count, 6) + + def test_clear_tree(self): + tree = FastRBTree(self.values) + tree.clear() + self.assertEqual(tree.count, 0) + + def test_insert(self): + tree = FastRBTree() + for key in self.keys: + tree.insert(key, key) + value = tree.get_value(key) + self.assertEqual(value, key) + self.assertEqual(tree.count, 6) + + def test_remove(self): + tree = FastRBTree(self.values) + for key in self.keys: + tree.remove(key) + self.assertRaises(KeyError, tree.get_value, key) + self.assertEqual(tree.count, 0) + + def test_remove_random_numbers(self): + keys = list(set([randint(0, 10000) for _ in range(500)])) + shuffle(keys) + tree = FastRBTree(zip(keys, keys)) + self.assertEqual(tree.count, len(keys)) + for key in keys: + tree.remove(key) + self.assertEqual(tree.count, 0) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_treeslice.py b/tests/test_treeslice.py new file mode 100644 index 0000000..ebcfa59 --- /dev/null +++ b/tests/test_treeslice.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +#coding:utf-8 +# Author: mozman -- +# Created: 11.04.2011 +# Copyright (c) 2010-2013 by Manfred Moitzi +# License: MIT License + + +import unittest + +from bintrees import RBTree + + +class TestTreeSlice(unittest.TestCase): + def setUp(self): + self.tree = RBTree({1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f'}) + + def test_in_slice_object(self): + treeslice = self.tree[2:6] + self.assertTrue(2 in treeslice) + + def test_not_in_slice_object(self): + treeslice = self.tree[2:6] + self.assertFalse(1 in treeslice) + + def test_in_slice_object_with_no_start(self): + treeslice = self.tree[:6] + self.assertTrue(1 in treeslice) + self.assertFalse(6 in treeslice) + + def test_in_slice_object_with_no_stop(self): + treeslice = self.tree[2:] + self.assertTrue(6 in treeslice) + self.assertFalse(1 in treeslice) + + def test_getitem_from_slice_object(self): + treeslice = self.tree[2:6] + self.assertEqual('b', treeslice[2]) + + def test_error_getitem_for_key_out_of_range(self): + treeslice = self.tree[2:6] + self.assertRaises(KeyError, treeslice.__getitem__, 6) + self.assertRaises(KeyError, treeslice.__getitem__, 1) + + def test_error_getitem_for_key_in_range(self): + treeslice = self.tree[2:6] + self.assertRaises(KeyError, treeslice.__getitem__, 3.5) + + def test_iter_slice_object(self): + treeslice = self.tree[2:6] + self.assertEqual([2, 3, 4, 5], list(treeslice)) + + def test_iter_all(self): + self.assertEqual([1, 2, 3, 4, 5, 6], list(self.tree[:])) + + def test_iter_values_slice_object(self): + treeslice = self.tree[2:6] + self.assertEqual(['b', 'c', 'd', 'e'], list(treeslice.values())) + + def test_iter_items_slice_object(self): + treeslice = self.tree[2:6] + self.assertEqual([(2, 'b'), (3, 'c'), (4, 'd'), (5, 'e')], list(treeslice.items())) + + def test_subslicing(self): + subslice = self.tree[2:6][3:5] + self.assertEqual([3, 4], list(subslice)) + + def test_subslicing_nobounds_1(self): + subslice = self.tree[2:6][:5] + self.assertEqual([2, 3, 4], list(subslice)) + + def test_subslicing_nobounds_2(self): + subslice = self.tree[2:6][3:] + self.assertEqual([3, 4, 5], list(subslice)) + + def test_subslicing_nobounds_3(self): + subslice = self.tree[:4][:3] + self.assertEqual([1, 2], list(subslice)) + + def test_subslicing_nobounds_4(self): + subslice = self.tree[:4][2:] + self.assertEqual([2, 3], list(subslice)) + + def test_subslicing_nobounds_5(self): + subslice = self.tree[2:][1:] + self.assertEqual([2, 3, 4, 5, 6], list(subslice)) + + def test_subslicing_nobounds_6(self): + subslice = self.tree[2:][:5] + self.assertEqual([2, 3, 4], list(subslice)) + + def test_subslicing_nobounds_7(self): + subslice = self.tree[2:][:1] + self.assertEqual([], list(subslice)) + + def test_repr(self): + result = repr(self.tree[2:4]) + self.assertEqual("RBTree({2: 'b', 3: 'c'})", result) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/testkey.txt b/tests/testkey.txt new file mode 100644 index 0000000..51d5803 --- /dev/null +++ b/tests/testkey.txt @@ -0,0 +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] \ No newline at end of file