├── .coveragerc ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── blist ├── __init__.py ├── _blist.c ├── _btuple.py ├── _sorteddict.py ├── _sortedlist.py ├── blist.h └── test │ ├── __init__.py │ ├── btuple_tests.py │ ├── list_tests.py │ ├── mapping_tests.py │ ├── seq_tests.py │ ├── sorteddict_tests.py │ ├── sortedlist_tests.py │ ├── test_list.py │ ├── test_set.py │ ├── test_support.py │ └── unittest.py ├── doc ├── Makefile ├── _templates │ └── layout.html ├── blist.rst ├── btuple.rst ├── conf.py ├── implementation.rst ├── index.rst ├── mymath.txt ├── sorteddict.rst ├── sortedlist.rst ├── sortedset.rst ├── weaksortedlist.rst └── weaksortedset.rst ├── ez_setup.py ├── fuzz.py ├── prototype └── blist.py ├── replot.py ├── setup.py ├── speed_test.py ├── speed_test_native.py ├── summarize.py ├── test.c └── test_blist.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | 4 | [report] 5 | omit: 6 | test/unittest 7 | test/test_support 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[oc] 2 | setuptools-*.egg 3 | build/ 4 | blist.egg-info 5 | blist.dll 6 | blist.pyd 7 | blist.so 8 | distribute-*.tar.gz 9 | .backup 10 | *.so 11 | core 12 | dat/ 13 | fig/ 14 | gnuplot/ 15 | .backup 16 | *~ 17 | *.bak 18 | distribute-*.egg 19 | *.pyd 20 | dist/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2007-2010 Stutzbach Enterprises, LLC 2 | (daniel@stutzbachenterprises.com) 3 | 4 | Copyright 2012 Google, Inc. All Rights Reserved. 5 | (stutzbach@google.com) 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | 1. Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 2. Redistributions in binary form must reproduce the above 14 | copyright notice, this list of conditions and the following 15 | disclaimer in the documentation and/or other materials provided 16 | with the distribution. 17 | 3. The name of the author may not be used to endorse or promote 18 | products derived from this software without specific prior written 19 | permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 22 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 25 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 29 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 30 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include blist/_blist.c 2 | include blist/__init__.py 3 | include blist/_sorteddict.py 4 | include blist/_sortedlist.py 5 | include blist/_btuple.py 6 | include setup.py 7 | include test_blist.py 8 | include blist/test/*.py 9 | include README.rst 10 | include LICENSE 11 | include prototype/blist.py 12 | include ez_setup.py 13 | include speed_test.py 14 | include blist.rst 15 | include blist/blist.h 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #PYTHON=python2.5 2 | #PYPREFIX=/usr 3 | 4 | PYTHON=python2.6 5 | PYPREFIX=/usr/local/stow/python-2.6.1-dbg 6 | 7 | CFLAGS=-Wall -g -fno-strict-aliasing -Wstrict-prototypes $(COPT) -Werror $(INCLUDE) 8 | #INCLUDE=-I/home/agthorr/Python-3.0/Include -I/home/agthorr/Python-3.0/ #-I/home/agthorr/mypython-3.0/Include/ -I/home/agthorr/mypython-3.0/ 9 | INCLUDE=-I$(PYPREFIX)/include/$(PYTHON) 10 | CC=gcc #-L/home/agthorr/Python-3.0 11 | 12 | #COPT=-O3 -DLIMIT=128 -DNDEBUG=1 # For performance 13 | #COPT=-fno-inline -pg -O3 -DLIMIT=128 -DNDEBUG=1# -ftest-coverage -fprofile-arcs # For profiling 14 | COPT=-DLIMIT=8 -DPy_DEBUG=1 # For debug mode 15 | 16 | LDFLAGS=-g -shared $(COPT) 17 | DLLFLAGS=-Wl,--enable-auto-image-base 18 | LD=$(CC) 19 | LOADLIBES=-L$(PYPREFIX)/lib -L$(PYPREFIX)/lib/$(PYTHON)/config/ -l$(PYTHON).dll 20 | 21 | blist.dll: blist.o 22 | $(LD) $(LDFLAGS) $(DLLFLAGS) -o $@ $< $(LOADLIBES) 23 | 24 | blist.so: blist.o 25 | $(LD) $(LDFLAGS) -o $@ $< $(LOADLIBES) 26 | 27 | clean: 28 | rm -f *.o *.so *.dll 29 | 30 | egg: 31 | $(PYTHON) setup.py register 32 | rm -f dist/*.asc 33 | $(PYTHON) setup.py sdist upload -s 34 | rsync -e ssh dist/* stutzbachenterprises.com:stutzbachenterprises/html/blist/ 35 | 36 | bdist_egg: 37 | CFLAGS='-O3 -fno-strict-aliasing' $(PYTHON) setup.py build -f 38 | rm -f dist/*.asc 39 | $(PYTHON) setup.py bdist_egg upload -s 40 | 41 | html: 42 | rst2html README.rst 43 | 44 | speed: 45 | $(PYTHON) speed_test.py 46 | rsync -e ssh -r fig/* stutzbachenterprises.com:stutzbachenterprises/html/fig/ 47 | 48 | test: COPT=-DLIMIT=8 -DPy_DEBUG=1 49 | test: LOADLIBES=-l$(PYTHON) -L/bin 50 | test: clean blist.so 51 | $(PYTHON)-dbg test_blist.py 52 | 53 | testing: test.o blist.o 54 | $(LD) --static -pg test.o blist.o -o testing $(LOADLIBES) 55 | 56 | win: 57 | /cygdrive/c/Python26/python.exe setup.py bdist_wininst 58 | /cygdrive/c/Python27/python.exe setup.py bdist_wininst 59 | /cygdrive/c/Python31/python.exe setup.py bdist_wininst 60 | /cygdrive/c/Python32/python.exe setup.py bdist_wininst 61 | gpg --detach-sign -a dist/blist-$(VERSION).win32-py2.6.exe 62 | gpg --detach-sign -a dist/blist-$(VERSION).win32-py2.7.exe 63 | gpg --detach-sign -a dist/blist-$(VERSION).win32-py3.1.exe 64 | gpg --detach-sign -a dist/blist-$(VERSION).win32-py3.2.exe 65 | 66 | winegg: 67 | /cygdrive/c/Python26/python.exe setup.py bdist_egg upload -s 68 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | blist: a list-like type with better performance 2 | =============================================== 3 | 4 | The ``blist`` is a drop-in replacement for the Python list that provides 5 | better performance when modifying large lists. The blist package also 6 | provides ``sortedlist``, ``sortedset``, ``weaksortedlist``, 7 | ``weaksortedset``, ``sorteddict``, and ``btuple`` types. 8 | 9 | Full documentation is at the link below: 10 | 11 | http://stutzbachenterprises.com/blist-doc/ 12 | 13 | Python's built-in list is a dynamically-sized array; to insert or 14 | remove an item from the beginning or middle of the list, it has to 15 | move most of the list in memory, i.e., O(n) operations. The blist 16 | uses a flexible, hybrid array/tree structure and only needs to move a 17 | small portion of items in memory, specifically using O(log n) 18 | operations. 19 | 20 | For small lists, the blist and the built-in list have virtually 21 | identical performance. 22 | 23 | To use the blist, you simply change code like this: 24 | 25 | >>> items = [5, 6, 2] 26 | >>> more_items = function_that_returns_a_list() 27 | 28 | to: 29 | 30 | >>> from blist import blist 31 | >>> items = blist([5, 6, 2]) 32 | >>> more_items = blist(function_that_returns_a_list()) 33 | 34 | Here are some of the use cases where the blist asymptotically 35 | outperforms the built-in list: 36 | 37 | ========================================== ================ ========= 38 | Use Case blist list 39 | ========================================== ================ ========= 40 | Insertion into or removal from a list O(log n) O(n) 41 | Taking slices of lists O(log n) O(n) 42 | Making shallow copies of lists O(1) O(n) 43 | Changing slices of lists O(log n + log k) O(n+k) 44 | Multiplying a list to make a sparse list O(log k) O(kn) 45 | Maintain a sorted lists with bisect.insort O(log**2 n) O(n) 46 | ========================================== ================ ========= 47 | 48 | So you can see the performance of the blist in more detail, several 49 | performance graphs available at the following link: 50 | http://stutzbachenterprises.com/blist/ 51 | 52 | Example usage: 53 | 54 | >>> from blist import * 55 | >>> x = blist([0]) # x is a blist with one element 56 | >>> x *= 2**29 # x is a blist with > 500 million elements 57 | >>> x.append(5) # append to x 58 | >>> y = x[4:-234234] # Take a 500 million element slice from x 59 | >>> del x[3:1024] # Delete a few thousand elements from x 60 | 61 | Other data structures 62 | --------------------- 63 | 64 | The blist package provides other data structures based on the blist: 65 | 66 | - sortedlist 67 | - sortedset 68 | - weaksortedlist 69 | - weaksortedset 70 | - sorteddict 71 | - btuple 72 | 73 | These additional data structures are only available in Python 2.6 or 74 | higher, as they make use of Abstract Base Classes. 75 | 76 | The sortedlist is a list that's always sorted. It's iterable and 77 | indexable like a Python list, but to modify a sortedlist the same 78 | methods you would use on a Python set (add, discard, or remove). 79 | 80 | >>> from blist import sortedlist 81 | >>> my_list = sortedlist([3,7,2,1]) 82 | >>> my_list 83 | sortedlist([1, 2, 3, 7]) 84 | >>> my_list.add(5) 85 | >>> my_list[3] 86 | 5 87 | >>> 88 | 89 | The sortedlist constructor takes an optional "key" argument, which may 90 | be used to change the sort order just like the sorted() function. 91 | 92 | >>> from blist import sortedlist 93 | >>> my_list = sortedlist([3,7,2,1], key=lambda i: -i) 94 | sortedlist([7, 3, 2, 1] 95 | >>> 96 | 97 | The sortedset is a set that's always sorted. It's iterable and 98 | indexable like a Python list, but modified like a set. Essentially, 99 | it's just like a sortedlist except that duplicates are ignored. 100 | 101 | >>> from blist import sortedset 102 | >>> my_set = sortedset([3,7,2,2]) 103 | sortedset([2, 3, 7] 104 | >>> 105 | 106 | The weaksortedlist and weaksortedset are weakref variations of the 107 | sortedlist and sortedset. 108 | 109 | The sorteddict works just like a regular dict, except the keys are 110 | always sorted. The sorteddict should not be confused with Python 111 | 2.7's OrderedDict type, which remembers the insertion order of the 112 | keys. 113 | 114 | >>> from blist import sorteddict 115 | >>> my_dict = sorteddict({1: 5, 6: 8, -5: 9}) 116 | >>> my_dict.keys() 117 | [-5, 1, 6] 118 | >>> 119 | 120 | The btuple is a drop-in replacement for the built-in tuple. Compared 121 | to the built-in tuple, the btuple offers the following advantages: 122 | 123 | - Constructing a btuple from a blist takes O(1) time. 124 | - Taking a slice of a btuple takes O(n) time, where n is the size of 125 | the original tuple. The size of the slice does not matter. 126 | 127 | >>> from blist import blist, btuple 128 | >>> x = blist([0]) # x is a blist with one element 129 | >>> x *= 2**29 # x is a blist with > 500 million elements 130 | >>> y = btuple(x) # y is a btuple with > 500 million elements 131 | 132 | Installation instructions 133 | ------------------------- 134 | 135 | Python 2.5 or higher is required. If building from the source 136 | distribution, the Python header files are also required. In either 137 | case, just run: 138 | 139 | python setup.py install 140 | 141 | If you're running Linux and see a bunch of compilation errors from 142 | GCC, you probably do not have the Python header files installed. 143 | They're usually located in a package called something like 144 | "python2.6-dev". 145 | 146 | The blist package will be installed in the 'site-packages' directory of 147 | your Python installation. (Unless directed elsewhere; see the 148 | "Installing Python Modules" section of the Python manuals for details 149 | on customizing installation locations, etc.). 150 | 151 | If you downloaded the source distribution and wish to run the 152 | associated test suite, you can also run: 153 | 154 | python setup.py test 155 | 156 | which will verify the correct installation and functioning of the 157 | package. The tests require Python 2.6 or higher. 158 | 159 | Feedback 160 | -------- 161 | 162 | We're eager to hear about your experiences with the blist. You can 163 | email me at daniel@stutzbachenterprises.com. Alternately, bug reports 164 | and feature requests may be reported on our bug tracker at: 165 | http://github.com/DanielStutzbach/blist/issues 166 | 167 | How we test 168 | ----------- 169 | 170 | In addition to the tests include in the source distribution, we 171 | perform the following to add extra rigor to our testing process: 172 | 173 | 1. We use a "fuzzer": a program that randomly generates list 174 | operations, performs them using both the blist and the built-in 175 | list, and compares the results. 176 | 177 | 2. We use a modified Python interpreter where we have replaced the 178 | array-based built-in list with the blist. Then, we run all of 179 | the regular Python unit tests. 180 | -------------------------------------------------------------------------------- /blist/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.3.6' 2 | from blist._blist import * 3 | import collections 4 | if hasattr(collections, 'MutableSet'): # Only supported in Python 2.6+ 5 | from blist._sortedlist import sortedlist, sortedset, weaksortedlist, weaksortedset 6 | from blist._sorteddict import sorteddict 7 | from blist._btuple import btuple 8 | collections.MutableSequence.register(blist) 9 | del _sortedlist, _sorteddict, _btuple 10 | del collections 11 | -------------------------------------------------------------------------------- /blist/_btuple.py: -------------------------------------------------------------------------------- 1 | from blist._blist import blist 2 | from ctypes import c_int 3 | import collections 4 | class btuple(collections.Sequence): 5 | def __init__(self, seq=None): 6 | if isinstance(seq, btuple): 7 | self._blist = seq._blist 8 | elif seq is not None: 9 | self._blist = blist(seq) 10 | else: 11 | self._blist = blist() 12 | self._hash = -1 13 | 14 | def _btuple_or_tuple(self, other, f): 15 | if isinstance(other, btuple): 16 | rv = f(self._blist, other._blist) 17 | elif isinstance(other, tuple): 18 | rv = f(self._blist, blist(other)) 19 | else: 20 | return NotImplemented 21 | if isinstance(rv, blist): 22 | rv = btuple(rv) 23 | return rv 24 | 25 | def __hash__(self): 26 | # Based on tuplehash from tupleobject.c 27 | if self._hash != -1: 28 | return self._hash 29 | 30 | n = len(self) 31 | mult = c_int(1000003) 32 | x = c_int(0x345678) 33 | for ob in self: 34 | n -= 1 35 | y = c_int(hash(ob)) 36 | x = (x ^ y) * mult 37 | mult += c_int(82520) + n + n 38 | x += c_int(97531) 39 | if x == -1: 40 | x = -2; 41 | self._hash = x.value 42 | return self._hash 43 | 44 | def __add__(self, other): 45 | return self._btuple_or_tuple(other, blist.__add__) 46 | def __radd__(self, other): 47 | return self._btuple_or_tuple(other, blist.__radd__) 48 | def __contains__(self, item): 49 | return item in self._blist 50 | def __eq__(self, other): 51 | return self._btuple_or_tuple(other, blist.__eq__) 52 | def __ge__(self, other): 53 | return self._btuple_or_tuple(other, blist.__ge__) 54 | def __gt__(self, other): 55 | return self._btuple_or_tuple(other, blist.__gt__) 56 | def __le__(self, other): 57 | return self._btuple_or_tuple(other, blist.__le__) 58 | def __lt__(self, other): 59 | return self._btuple_or_tuple(other, blist.__lt__) 60 | def __ne__(self, other): 61 | return self._btuple_or_tuple(other, blist.__ne__) 62 | def __iter__(self): 63 | return iter(self._blist) 64 | def __len__(self): 65 | return len(self._blist) 66 | def __getitem__(self, key): 67 | if isinstance(key, slice): 68 | return btuple(self._blist[key]) 69 | return self._blist[key] 70 | def __getslice__(self, i, j): 71 | return btuple(self._blist[i:j]) 72 | def __repr__(self): 73 | return 'btuple((' + repr(self._blist)[7:-2] + '))' 74 | def __str__(self): 75 | return repr(self) 76 | def __mul__(self, i): 77 | return btuple(self._blist * i) 78 | def __rmul__(self, i): 79 | return btuple(i * self._blist) 80 | def count(self, item): 81 | return self._blist.count(item) 82 | def index(self, item): 83 | return self._blist.index(item) 84 | 85 | del c_int 86 | del collections 87 | -------------------------------------------------------------------------------- /blist/_sorteddict.py: -------------------------------------------------------------------------------- 1 | from blist._sortedlist import sortedset, ReprRecursion 2 | import collections, sys 3 | from blist._blist import blist 4 | 5 | class missingdict(dict): 6 | def __missing__(self, key): 7 | return self._missing(key) 8 | 9 | class KeysView(collections.KeysView, collections.Sequence): 10 | def __getitem__(self, index): 11 | return self._mapping._sortedkeys[index] 12 | def __reversed__(self): 13 | return reversed(self._mapping._sortedkeys) 14 | def index(self, key): 15 | return self._mapping._sortedkeys.index(key) 16 | def count(self, key): 17 | return 1 if key in self else 0 18 | def _from_iterable(self, it): 19 | return sortedset(it, key=self._mapping._sortedkeys._key) 20 | def bisect_left(self, key): 21 | return self._mapping._sortedkeys.bisect_left(key) 22 | def bisect_right(self, key): 23 | return self._mapping._sortedkeys.bisect_right(key) 24 | bisect = bisect_right 25 | 26 | class ItemsView(collections.ItemsView, collections.Sequence): 27 | def __getitem__(self, index): 28 | if isinstance(index, slice): 29 | keys = self._mapping._sortedkeys[index] 30 | return self._from_iterable((key, self._mapping[key]) 31 | for key in keys) 32 | key = self._mapping._sortedkeys[index] 33 | return (key, self._mapping[key]) 34 | def index(self, item): 35 | key, value = item 36 | i = self._mapping._sortedkeys.index(key) 37 | if self._mapping[key] == value: 38 | return i 39 | raise ValueError 40 | def count(self, item): 41 | return 1 if item in self else 0 42 | def _from_iterable(self, it): 43 | keyfunc = self._mapping._sortedkeys._key 44 | if keyfunc is None: 45 | return sortedset(it) 46 | else: 47 | return sortedset(it, key=lambda item: keyfunc(item[0])) 48 | 49 | class ValuesView(collections.ValuesView, collections.Sequence): 50 | def __getitem__(self, index): 51 | if isinstance(index, slice): 52 | keys = self._mapping._sortedkeys[index] 53 | return [self._mapping[key] for key in keys] 54 | key = self._mapping._sortedkeys[index] 55 | return self._mapping[key] 56 | 57 | class sorteddict(collections.MutableMapping): 58 | def __init__(self, *args, **kw): 59 | if hasattr(self, '__missing__'): 60 | self._map = missingdict() 61 | self._map._missing = self.__missing__ 62 | else: 63 | self._map = dict() 64 | key = None 65 | if len(args) > 0: 66 | if hasattr(args[0], '__call__'): 67 | key = args[0] 68 | args = args[1:] 69 | elif len(args) > 1: 70 | raise TypeError("'%s' object is not callable" % 71 | args[0].__class__.__name__) 72 | if len(args) > 1: 73 | raise TypeError('sorteddict expected at most 2 arguments, got %d' 74 | % len(args)) 75 | if len(args) == 1 and isinstance(args[0], sorteddict) and key is None: 76 | key = args[0]._sortedkeys._key 77 | self._sortedkeys = sortedset(key=key) 78 | self.update(*args, **kw) 79 | 80 | if sys.version_info[0] < 3: 81 | def keys(self): 82 | return self._sortedkeys.copy() 83 | def items(self): 84 | return blist((key, self[key]) for key in self) 85 | def values(self): 86 | return blist(self[key] for key in self) 87 | def viewkeys(self): 88 | return KeysView(self) 89 | def viewitems(self): 90 | return ItemsView(self) 91 | def viewvalues(self): 92 | return ValuesView(self) 93 | else: 94 | def keys(self): 95 | return KeysView(self) 96 | def items(self): 97 | return ItemsView(self) 98 | def values(self): 99 | return ValuesView(self) 100 | 101 | def __setitem__(self, key, value): 102 | try: 103 | if key not in self._map: 104 | self._sortedkeys.add(key) 105 | self._map[key] = value 106 | except: 107 | if key not in self._map: 108 | self._sortedkeys.discard(key) 109 | raise 110 | 111 | def __delitem__(self, key): 112 | self._sortedkeys.discard(key) 113 | del self._map[key] 114 | 115 | def __getitem__(self, key): 116 | return self._map[key] 117 | 118 | def __iter__(self): 119 | return iter(self._sortedkeys) 120 | 121 | def __len__(self): 122 | return len(self._sortedkeys) 123 | 124 | def copy(self): 125 | return sorteddict(self) 126 | 127 | @classmethod 128 | def fromkeys(cls, keys, value=None, key=None): 129 | if key is not None: 130 | rv = cls(key) 131 | else: 132 | rv = cls() 133 | for key in keys: 134 | rv[key] = value 135 | return rv 136 | 137 | def __repr__(self): 138 | with ReprRecursion(self) as r: 139 | if r: 140 | return 'sorteddict({...})' 141 | return ('sorteddict({%s})' % 142 | ', '.join('%r: %r' % (k, self._map[k]) for k in self)) 143 | 144 | def __eq__(self, other): 145 | if not isinstance(other, sorteddict): 146 | return False 147 | return self._map == other._map 148 | -------------------------------------------------------------------------------- /blist/blist.h: -------------------------------------------------------------------------------- 1 | 2 | /* List object interface */ 3 | 4 | /* 5 | Another generally useful object type is an list of object pointers. 6 | This is a mutable type: the list items can be changed, and items can be 7 | added or removed. Out-of-range indices or non-list objects are ignored. 8 | 9 | *** WARNING *** PyList_SetItem does not increment the new item's reference 10 | count, but does decrement the reference count of the item it replaces, 11 | if not nil. It does *decrement* the reference count if it is *not* 12 | inserted in the list. Similarly, PyList_GetItem does not increment the 13 | returned item's reference count. 14 | */ 15 | 16 | /********************************************************************** 17 | * * 18 | * PLEASE READ blist.rst BEFORE MODIFYING THIS CODE * 19 | * * 20 | **********************************************************************/ 21 | 22 | #ifndef Py_BLISTOBJECT_H 23 | #define Py_BLISTOBJECT_H 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | #if 0 29 | #define BLIST_IN_PYTHON /* Define if building BList into Python */ 30 | #endif 31 | 32 | /* pyport.h includes similar defines, but they're broken and never use 33 | * "inline" except on Windows :-( */ 34 | #if defined(_MSC_VER) 35 | /* ignore warnings if the compiler decides not to inline a function */ 36 | #pragma warning(disable: 4710) 37 | /* fastest possible local call under MSVC */ 38 | #define BLIST_LOCAL(type) static type __fastcall 39 | #define BLIST_LOCAL_INLINE(type) static __inline type __fastcall 40 | #elif defined(__GNUC__) 41 | #if defined(__i386__) 42 | #define BLIST_LOCAL(type) static type __attribute__((fastcall)) 43 | #define BLIST_LOCAL_INLINE(type) static inline __attribute__((fastcall)) type 44 | #else 45 | #define BLIST_LOCAL(type) static type 46 | #define BLIST_LOCAL_INLINE(type) static inline type 47 | #endif 48 | #else 49 | #define BLIST_LOCAL(type) static type 50 | #define BLIST_LOCAL_INLINE(type) static type 51 | #endif 52 | 53 | #ifndef LIMIT 54 | #define LIMIT (128) /* Good performance value */ 55 | #if 0 56 | #define LIMIT (8) /* Maximum size, currently low (for test purposes) */ 57 | #endif 58 | #endif 59 | #define HALF (LIMIT/2) /* Minimum size */ 60 | #define MAX_HEIGHT (16) /* ceil(log(PY_SSIZE_T_MAX)/log(HALF)); */ 61 | #if LIMIT & 1 62 | #error LIMIT must be divisible by 2 63 | #endif 64 | #if LIMIT < 8 65 | #error LIMIT must be at least 8 66 | #endif 67 | #define INDEX_FACTOR (HALF) 68 | 69 | typedef struct PyBList { 70 | PyObject_HEAD 71 | Py_ssize_t n; /* Total # of user-object descendents */ 72 | int num_children; /* Number of immediate children */ 73 | int leaf; /* Boolean value */ 74 | PyObject **children; /* Immediate children */ 75 | } PyBList; 76 | 77 | typedef struct PyBListRoot { 78 | PyObject_HEAD 79 | #define BLIST_FIRST_FIELD n 80 | Py_ssize_t n; /* Total # of user-object descendents */ 81 | int num_children; /* Number of immediate children */ 82 | int leaf; /* Boolean value */ 83 | PyObject **children; /* Immediate children */ 84 | 85 | PyBList **index_list; 86 | Py_ssize_t *offset_list; 87 | unsigned *setclean_list; /* contains index_allocated _bits_ */ 88 | Py_ssize_t index_allocated; 89 | Py_ssize_t *dirty; 90 | Py_ssize_t dirty_length; 91 | Py_ssize_t dirty_root; 92 | Py_ssize_t free_root; 93 | 94 | #ifdef Py_DEBUG 95 | Py_ssize_t last_n; /* For debug */ 96 | #endif 97 | } PyBListRoot; 98 | 99 | #define PyBList_GET_ITEM(op, i) (((PyBList *)(op))->leaf ? (((PyBList *)(op))->children[(i)]) : _PyBList_GET_ITEM_FAST2((PyBListRoot*) (op), (i))) 100 | 101 | /************************************************************************ 102 | * Code used when building BList into the interpreter 103 | */ 104 | 105 | #ifdef BLIST_IN_PYTHON 106 | int PyList_Init1(void); 107 | int PyList_Init2(void); 108 | typedef PyBListRoot PyListObject; 109 | 110 | //PyAPI_DATA(PyTypeObject) PyList_Type; 111 | 112 | PyAPI_DATA(PyTypeObject) PyBList_Type; 113 | PyAPI_DATA(PyTypeObject) PyRootBList_Type; 114 | #define PyList_Type PyRootBList_Type 115 | 116 | #define PyList_Check(op) \ 117 | PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LIST_SUBCLASS) 118 | #define PyList_CheckExact(op) ((op)->ob_type == &PyRootBList_Type) 119 | 120 | PyAPI_FUNC(PyObject *) PyList_New(Py_ssize_t size); 121 | PyAPI_FUNC(Py_ssize_t) PyList_Size(PyObject *); 122 | PyAPI_FUNC(PyObject *) PyList_GetItem(PyObject *, Py_ssize_t); 123 | PyAPI_FUNC(int) PyList_SetItem(PyObject *, Py_ssize_t, PyObject *); 124 | PyAPI_FUNC(int) PyList_Insert(PyObject *, Py_ssize_t, PyObject *); 125 | PyAPI_FUNC(int) PyList_Append(PyObject *, PyObject *); 126 | PyAPI_FUNC(PyObject *) PyList_GetSlice(PyObject *, Py_ssize_t, Py_ssize_t); 127 | PyAPI_FUNC(int) PyList_SetSlice(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); 128 | PyAPI_FUNC(int) PyList_Sort(PyObject *); 129 | PyAPI_FUNC(int) PyList_Reverse(PyObject *); 130 | PyAPI_FUNC(PyObject *) PyList_AsTuple(PyObject *); 131 | PyAPI_FUNC(PyObject *) _PyList_Extend(PyBListRoot *, PyObject *); 132 | 133 | PyAPI_FUNC(void) _PyList_SetItemFast(PyObject *, Py_ssize_t, PyObject *); 134 | 135 | /* Macro, trading safety for speed */ 136 | #define PyList_GET_ITEM(op, i) (PyBList_GET_ITEM((op), (i))) 137 | //#define PyList_SET_ITEM(op, i, v) (((PyBList *)(op))->leaf ? (void) (((PyBList *)(op))->children[(i)] = (v)) : (void) _PyList_SetItemFast((PyObject *) (op), (i), (v))) 138 | #define PyList_SET_ITEM(self, i, v) (((PyBList *)self)->leaf ? (void) (((PyBList*)self)->children[(i)] = (v)) : (void) blist_ass_item_return2((PyBListRoot*)(self), (i), (v))) 139 | 140 | //#define PyList_GET_ITEM(op, i) PyList_GetItem((PyObject*) (op), (i)) 141 | //#define PyList_SET_ITEM(op, i, v) _PyList_SetItemFast((PyObject *) (op), (i), (v)) 142 | #define PyList_GET_SIZE(op) ({ assert(PyList_Check(op)); (((PyBList *)(op))->n); }) 143 | 144 | #define PyList_IS_LEAF(op) ({ assert(PyList_Check(op)); (((PyBList *) (op))->leaf); }) 145 | 146 | PyAPI_FUNC(PyObject *) _PyBList_GetItemFast3(PyBListRoot *, Py_ssize_t); 147 | 148 | PyAPI_FUNC(PyObject *) blist_ass_item_return_slow(PyBListRoot *root, Py_ssize_t i, PyObject *v); 149 | PyAPI_FUNC(PyObject *) ext_make_clean_set(PyBListRoot *root, Py_ssize_t i, PyObject *v); 150 | #else 151 | PyObject *_PyBList_GetItemFast3(PyBListRoot *, Py_ssize_t); 152 | PyObject *blist_ass_item_return_slow(PyBListRoot *root, Py_ssize_t i, PyObject *v); 153 | PyObject *ext_make_clean_set(PyBListRoot *root, Py_ssize_t i, PyObject *v); 154 | #endif 155 | 156 | #define INDEX_FACTOR (HALF) 157 | 158 | /* This should only be called if we know the root is not a leaf */ 159 | /* inlining a common case for speed */ 160 | BLIST_LOCAL_INLINE(PyObject *) 161 | _PyBList_GET_ITEM_FAST2(PyBListRoot *root, Py_ssize_t i) 162 | { 163 | Py_ssize_t ioffset; 164 | Py_ssize_t offset; 165 | PyBList *p; 166 | 167 | assert(!root->leaf); 168 | assert(i >= 0); 169 | assert(i < root->n); 170 | 171 | if (root->dirty_root >= -1 /* DIRTY */) 172 | return _PyBList_GetItemFast3(root, i); 173 | 174 | ioffset = i / INDEX_FACTOR; 175 | offset = root->offset_list[ioffset]; 176 | p = root->index_list[ioffset]; 177 | 178 | if (i < offset + p->n) 179 | return p->children[i - offset]; 180 | ioffset++; 181 | offset = root->offset_list[ioffset]; 182 | p = root->index_list[ioffset]; 183 | return p->children[i - offset]; 184 | } 185 | 186 | #define SETCLEAN_LEN(index_allocated) ((((index_allocated)-1) >> SETCLEAN_SHIFT)+1) 187 | #if SIZEOF_INT == 4 188 | #define SETCLEAN_SHIFT (5u) 189 | #define SETCLEAN_MASK (0x1fu) 190 | #elif SIZEOF_INT == 8 191 | #define SETCLEAN_SHIFT (6u) 192 | #define SETCLEAN_MASK (0x3fu) 193 | #else 194 | #error Unknown sizeof(unsigned) 195 | #endif 196 | 197 | #define SET_BIT(setclean_list, i) (setclean_list[(i) >> SETCLEAN_SHIFT] |= (1u << ((i) & SETCLEAN_MASK))) 198 | #define CLEAR_BIT(setclean_list, i) (setclean_list[(i) >> SETCLEAN_SHIFT] &= ~(1u << ((i) & SETCLEAN_MASK))) 199 | #define GET_BIT(setclean_list, i) (setclean_list[(i) >> SETCLEAN_SHIFT] & (1u << ((i) & SETCLEAN_MASK))) 200 | 201 | BLIST_LOCAL_INLINE(PyObject *) 202 | blist_ass_item_return2(PyBListRoot *root, Py_ssize_t i, PyObject *v) 203 | { 204 | PyObject *rv; 205 | Py_ssize_t offset; 206 | PyBList *p; 207 | Py_ssize_t ioffset = i / INDEX_FACTOR; 208 | 209 | assert(i >= 0); 210 | assert(i < root->n); 211 | assert(!root->leaf); 212 | 213 | if (root->dirty_root >= -1 /* DIRTY */ 214 | || !GET_BIT(root->setclean_list, ioffset)) 215 | return blist_ass_item_return_slow(root, i, v); 216 | 217 | offset = root->offset_list[ioffset]; 218 | p = root->index_list[ioffset]; 219 | assert(i >= offset); 220 | assert(p); 221 | assert(p->leaf); 222 | if (i < offset + p->n) { 223 | good: 224 | /* Py_REFCNT(p) == 1, generally, but see comment in 225 | * blist_ass_item_return_slow for caveats */ 226 | rv = p->children[i - offset]; 227 | p->children[i - offset] = v; 228 | } else if (!GET_BIT(root->setclean_list, ioffset+1)) { 229 | return ext_make_clean_set(root, i, v); 230 | } else { 231 | ioffset++; 232 | assert(ioffset < root->index_allocated); 233 | offset = root->offset_list[ioffset]; 234 | p = root->index_list[ioffset]; 235 | assert(p); 236 | assert(p->leaf); 237 | assert(i < offset + p->n); 238 | 239 | goto good; 240 | } 241 | 242 | return rv; 243 | } 244 | 245 | #ifdef __cplusplus 246 | } 247 | #endif 248 | #endif /* !Py_BLISTOBJECT_H */ 249 | -------------------------------------------------------------------------------- /blist/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielStutzbach/blist/0ed541a5f5fce18b3a8535d32ec64eef78485044/blist/test/__init__.py -------------------------------------------------------------------------------- /blist/test/btuple_tests.py: -------------------------------------------------------------------------------- 1 | # Based on Python's tuple_tests.py, licensed under the Python License 2 | # Agreement 3 | 4 | import sys 5 | import random 6 | import gc 7 | from blist import btuple 8 | from blist.test import unittest 9 | from blist.test import seq_tests 10 | 11 | class bTupleTest(seq_tests.CommonTest): 12 | type2test = btuple 13 | 14 | def test_constructors(self): 15 | super(bTupleTest, self).test_len() 16 | # calling built-in types without argument must return empty 17 | self.assertEqual(tuple(), ()) 18 | t0_3 = (0, 1, 2, 3) 19 | t0_3_bis = tuple(t0_3) 20 | self.assert_(t0_3 is t0_3_bis) 21 | self.assertEqual(tuple([]), ()) 22 | self.assertEqual(tuple([0, 1, 2, 3]), (0, 1, 2, 3)) 23 | self.assertEqual(tuple(''), ()) 24 | self.assertEqual(tuple('spam'), ('s', 'p', 'a', 'm')) 25 | 26 | def test_truth(self): 27 | super(bTupleTest, self).test_truth() 28 | self.assert_(not ()) 29 | self.assert_((42, )) 30 | 31 | def test_len(self): 32 | super(bTupleTest, self).test_len() 33 | self.assertEqual(len(()), 0) 34 | self.assertEqual(len((0,)), 1) 35 | self.assertEqual(len((0, 1, 2)), 3) 36 | 37 | def test_iadd(self): 38 | super(bTupleTest, self).test_iadd() 39 | u = (0, 1) 40 | u2 = u 41 | u += (2, 3) 42 | self.assert_(u is not u2) 43 | 44 | def test_imul(self): 45 | super(bTupleTest, self).test_imul() 46 | u = (0, 1) 47 | u2 = u 48 | u *= 3 49 | self.assert_(u is not u2) 50 | 51 | def test_tupleresizebug(self): 52 | # Check that a specific bug in _PyTuple_Resize() is squashed. 53 | def f(): 54 | for i in range(1000): 55 | yield i 56 | self.assertEqual(list(tuple(f())), list(range(1000))) 57 | 58 | def test_hash(self): 59 | # See SF bug 942952: Weakness in tuple hash 60 | # The hash should: 61 | # be non-commutative 62 | # should spread-out closely spaced values 63 | # should not exhibit cancellation in tuples like (x,(x,y)) 64 | # should be distinct from element hashes: hash(x)!=hash((x,)) 65 | # This test exercises those cases. 66 | # For a pure random hash and N=50, the expected number of occupied 67 | # buckets when tossing 252,600 balls into 2**32 buckets 68 | # is 252,592.6, or about 7.4 expected collisions. The 69 | # standard deviation is 2.73. On a box with 64-bit hash 70 | # codes, no collisions are expected. Here we accept no 71 | # more than 15 collisions. Any worse and the hash function 72 | # is sorely suspect. 73 | 74 | N=50 75 | base = list(range(N)) 76 | xp = [(i, j) for i in base for j in base] 77 | inps = base + [(i, j) for i in base for j in xp] + \ 78 | [(i, j) for i in xp for j in base] + xp + list(zip(base)) 79 | collisions = len(inps) - len(set(map(hash, inps))) 80 | self.assert_(collisions <= 15) 81 | 82 | def test_repr(self): 83 | l0 = btuple() 84 | l2 = btuple((0, 1, 2)) 85 | a0 = self.type2test(l0) 86 | a2 = self.type2test(l2) 87 | 88 | self.assertEqual(str(a0), repr(l0)) 89 | self.assertEqual(str(a2), repr(l2)) 90 | self.assertEqual(repr(a0), "btuple(())") 91 | self.assertEqual(repr(a2), "btuple((0, 1, 2))") 92 | 93 | def _not_tracked(self, t): 94 | if sys.version_info[0] < 3: 95 | return 96 | else: # pragma: no cover 97 | # Nested tuples can take several collections to untrack 98 | gc.collect() 99 | gc.collect() 100 | self.assertFalse(gc.is_tracked(t), t) 101 | 102 | def _tracked(self, t): 103 | if sys.version_info[0] < 3: 104 | return 105 | else: # pragma: no cover 106 | self.assertTrue(gc.is_tracked(t), t) 107 | gc.collect() 108 | gc.collect() 109 | self.assertTrue(gc.is_tracked(t), t) 110 | 111 | def test_track_literals(self): 112 | # Test GC-optimization of tuple literals 113 | x, y, z = 1.5, "a", [] 114 | 115 | self._not_tracked(()) 116 | self._not_tracked((1,)) 117 | self._not_tracked((1, 2)) 118 | self._not_tracked((1, 2, "a")) 119 | self._not_tracked((1, 2, (None, True, False, ()), int)) 120 | self._not_tracked((object(),)) 121 | self._not_tracked(((1, x), y, (2, 3))) 122 | 123 | # Tuples with mutable elements are always tracked, even if those 124 | # elements are not tracked right now. 125 | self._tracked(([],)) 126 | self._tracked(([1],)) 127 | self._tracked(({},)) 128 | self._tracked((set(),)) 129 | self._tracked((x, y, z)) 130 | 131 | def check_track_dynamic(self, tp, always_track): 132 | x, y, z = 1.5, "a", [] 133 | 134 | check = self._tracked if always_track else self._not_tracked 135 | check(tp()) 136 | check(tp([])) 137 | check(tp(set())) 138 | check(tp([1, x, y])) 139 | check(tp(obj for obj in [1, x, y])) 140 | check(tp(set([1, x, y]))) 141 | check(tp(tuple([obj]) for obj in [1, x, y])) 142 | check(tuple(tp([obj]) for obj in [1, x, y])) 143 | 144 | self._tracked(tp([z])) 145 | self._tracked(tp([[x, y]])) 146 | self._tracked(tp([{x: y}])) 147 | self._tracked(tp(obj for obj in [x, y, z])) 148 | self._tracked(tp(tuple([obj]) for obj in [x, y, z])) 149 | self._tracked(tuple(tp([obj]) for obj in [x, y, z])) 150 | 151 | def test_track_dynamic(self): 152 | # Test GC-optimization of dynamically constructed tuples. 153 | self.check_track_dynamic(tuple, False) 154 | 155 | def test_track_subtypes(self): 156 | # Tuple subtypes must always be tracked 157 | class MyTuple(tuple): 158 | pass 159 | self.check_track_dynamic(MyTuple, True) 160 | -------------------------------------------------------------------------------- /blist/test/seq_tests.py: -------------------------------------------------------------------------------- 1 | # This file taken from Python, licensed under the Python License Agreement 2 | 3 | from __future__ import print_function 4 | """ 5 | Tests common to tuple, list and UserList.UserList 6 | """ 7 | 8 | from blist.test import test_support 9 | from blist.test import unittest 10 | import sys 11 | 12 | # Various iterables 13 | # This is used for checking the constructor (here and in test_deque.py) 14 | def iterfunc(seqn): 15 | 'Regular generator' 16 | for i in seqn: 17 | yield i 18 | 19 | class Sequence: 20 | 'Sequence using __getitem__' 21 | def __init__(self, seqn): 22 | self.seqn = seqn 23 | def __getitem__(self, i): 24 | return self.seqn[i] 25 | 26 | class IterFunc: 27 | 'Sequence using iterator protocol' 28 | def __init__(self, seqn): 29 | self.seqn = seqn 30 | self.i = 0 31 | def __iter__(self): 32 | return self 33 | def __next__(self): 34 | if self.i >= len(self.seqn): raise StopIteration 35 | v = self.seqn[self.i] 36 | self.i += 1 37 | return v 38 | next = __next__ 39 | 40 | class IterGen: 41 | 'Sequence using iterator protocol defined with a generator' 42 | def __init__(self, seqn): 43 | self.seqn = seqn 44 | self.i = 0 45 | def __iter__(self): 46 | for val in self.seqn: 47 | yield val 48 | 49 | class IterNextOnly: 50 | 'Missing __getitem__ and __iter__' 51 | def __init__(self, seqn): 52 | self.seqn = seqn 53 | self.i = 0 54 | def __next__(self): # pragma: no cover 55 | if self.i >= len(self.seqn): raise StopIteration 56 | v = self.seqn[self.i] 57 | self.i += 1 58 | return v 59 | next = __next__ 60 | 61 | class IterNoNext: 62 | 'Iterator missing next()' 63 | def __init__(self, seqn): 64 | self.seqn = seqn 65 | self.i = 0 66 | def __iter__(self): 67 | return self 68 | 69 | class IterGenExc: 70 | 'Test propagation of exceptions' 71 | def __init__(self, seqn): 72 | self.seqn = seqn 73 | self.i = 0 74 | def __iter__(self): 75 | return self 76 | def __next__(self): 77 | 3 // 0 78 | next = __next__ 79 | 80 | class IterFuncStop: 81 | 'Test immediate stop' 82 | def __init__(self, seqn): 83 | pass 84 | def __iter__(self): 85 | return self 86 | def __next__(self): 87 | raise StopIteration 88 | next = __next__ 89 | 90 | from itertools import chain 91 | def itermulti(seqn): 92 | 'Test multiple tiers of iterators' 93 | return chain(map(lambda x:x, iterfunc(IterGen(Sequence(seqn))))) 94 | 95 | class CommonTest(unittest.TestCase): 96 | # The type to be tested 97 | type2test = None 98 | 99 | def test_constructors(self): 100 | l0 = [] 101 | l1 = [0] 102 | l2 = [0, 1] 103 | 104 | u = self.type2test() 105 | u0 = self.type2test(l0) 106 | u1 = self.type2test(l1) 107 | u2 = self.type2test(l2) 108 | 109 | uu = self.type2test(u) 110 | uu0 = self.type2test(u0) 111 | uu1 = self.type2test(u1) 112 | uu2 = self.type2test(u2) 113 | 114 | v = self.type2test(tuple(u)) 115 | class OtherSeq: 116 | def __init__(self, initseq): 117 | self.__data = initseq 118 | def __len__(self): 119 | return len(self.__data) 120 | def __getitem__(self, i): 121 | return self.__data[i] 122 | s = OtherSeq(u0) 123 | v0 = self.type2test(s) 124 | self.assertEqual(len(v0), len(s)) 125 | 126 | s = "this is also a sequence" 127 | vv = self.type2test(s) 128 | self.assertEqual(len(vv), len(s)) 129 | 130 | # Create from various iteratables 131 | for s in ("123", "", list(range(1000)), ('do', 1.2), range(2000,2200,5)): 132 | for g in (Sequence, IterFunc, IterGen, 133 | itermulti, iterfunc): 134 | self.assertEqual(self.type2test(g(s)), self.type2test(s)) 135 | self.assertEqual(self.type2test(IterFuncStop(s)), self.type2test()) 136 | self.assertEqual(self.type2test(c for c in "123"), self.type2test("123")) 137 | self.assertRaises(TypeError, self.type2test, IterNextOnly(s)) 138 | self.assertRaises(TypeError, self.type2test, IterNoNext(s)) 139 | self.assertRaises(ZeroDivisionError, self.type2test, IterGenExc(s)) 140 | 141 | def test_truth(self): 142 | self.assert_(not self.type2test()) 143 | self.assert_(self.type2test([42])) 144 | 145 | def test_getitem(self): 146 | u = self.type2test([0, 1, 2, 3, 4]) 147 | for i in range(len(u)): 148 | self.assertEqual(u[i], i) 149 | self.assertEqual(u[int(i)], i) 150 | for i in range(-len(u), -1): 151 | self.assertEqual(u[i], len(u)+i) 152 | self.assertEqual(u[int(i)], len(u)+i) 153 | self.assertRaises(IndexError, u.__getitem__, -len(u)-1) 154 | self.assertRaises(IndexError, u.__getitem__, len(u)) 155 | self.assertRaises(ValueError, u.__getitem__, slice(0,10,0)) 156 | 157 | u = self.type2test() 158 | self.assertRaises(IndexError, u.__getitem__, 0) 159 | self.assertRaises(IndexError, u.__getitem__, -1) 160 | 161 | self.assertRaises(TypeError, u.__getitem__) 162 | 163 | a = self.type2test([10, 11]) 164 | self.assertEqual(a[0], 10) 165 | self.assertEqual(a[1], 11) 166 | self.assertEqual(a[-2], 10) 167 | self.assertEqual(a[-1], 11) 168 | self.assertRaises(IndexError, a.__getitem__, -3) 169 | self.assertRaises(IndexError, a.__getitem__, 3) 170 | 171 | def test_getslice(self): 172 | l = [0, 1, 2, 3, 4] 173 | u = self.type2test(l) 174 | 175 | self.assertEqual(u[0:0], self.type2test()) 176 | self.assertEqual(u[1:2], self.type2test([1])) 177 | self.assertEqual(u[-2:-1], self.type2test([3])) 178 | self.assertEqual(u[-1000:1000], u) 179 | self.assertEqual(u[1000:-1000], self.type2test([])) 180 | self.assertEqual(u[:], u) 181 | self.assertEqual(u[1:None], self.type2test([1, 2, 3, 4])) 182 | self.assertEqual(u[None:3], self.type2test([0, 1, 2])) 183 | 184 | # Extended slices 185 | self.assertEqual(u[::], u) 186 | self.assertEqual(u[::2], self.type2test([0, 2, 4])) 187 | self.assertEqual(u[1::2], self.type2test([1, 3])) 188 | self.assertEqual(u[::-1], self.type2test([4, 3, 2, 1, 0])) 189 | self.assertEqual(u[::-2], self.type2test([4, 2, 0])) 190 | self.assertEqual(u[3::-2], self.type2test([3, 1])) 191 | self.assertEqual(u[3:3:-2], self.type2test([])) 192 | self.assertEqual(u[3:2:-2], self.type2test([3])) 193 | self.assertEqual(u[3:1:-2], self.type2test([3])) 194 | self.assertEqual(u[3:0:-2], self.type2test([3, 1])) 195 | self.assertEqual(u[::-100], self.type2test([4])) 196 | self.assertEqual(u[100:-100:], self.type2test([])) 197 | self.assertEqual(u[-100:100:], u) 198 | self.assertEqual(u[100:-100:-1], u[::-1]) 199 | self.assertEqual(u[-100:100:-1], self.type2test([])) 200 | self.assertEqual(u[-100:100:2], self.type2test([0, 2, 4])) 201 | 202 | # Test extreme cases with long ints 203 | a = self.type2test([0,1,2,3,4]) 204 | self.assertEqual(a[ -pow(2,128): 3 ], self.type2test([0,1,2])) 205 | self.assertEqual(a[ 3: pow(2,145) ], self.type2test([3,4])) 206 | 207 | if sys.version_info[0] < 3: 208 | self.assertRaises(TypeError, u.__getslice__) 209 | 210 | def test_contains(self): 211 | u = self.type2test([0, 1, 2]) 212 | for i in u: 213 | self.assert_(i in u) 214 | for i in min(u)-1, max(u)+1: 215 | self.assert_(i not in u) 216 | 217 | self.assertRaises(TypeError, u.__contains__) 218 | 219 | def test_contains_fake(self): 220 | class AllEq: 221 | # Sequences must use rich comparison against each item 222 | # (unless "is" is true, or an earlier item answered) 223 | # So instances of AllEq must be found in all non-empty sequences. 224 | def __eq__(self, other): 225 | return True 226 | def __hash__(self): # pragma: no cover 227 | raise NotImplemented 228 | self.assert_(AllEq() not in self.type2test([])) 229 | self.assert_(AllEq() in self.type2test([1])) 230 | 231 | def test_contains_order(self): 232 | # Sequences must test in-order. If a rich comparison has side 233 | # effects, these will be visible to tests against later members. 234 | # In this test, the "side effect" is a short-circuiting raise. 235 | class DoNotTestEq(Exception): 236 | pass 237 | class StopCompares: 238 | def __eq__(self, other): 239 | raise DoNotTestEq 240 | 241 | checkfirst = self.type2test([1, StopCompares()]) 242 | self.assert_(1 in checkfirst) 243 | checklast = self.type2test([StopCompares(), 1]) 244 | self.assertRaises(DoNotTestEq, checklast.__contains__, 1) 245 | 246 | def test_len(self): 247 | self.assertEqual(len(self.type2test()), 0) 248 | self.assertEqual(len(self.type2test([])), 0) 249 | self.assertEqual(len(self.type2test([0])), 1) 250 | self.assertEqual(len(self.type2test([0, 1, 2])), 3) 251 | 252 | def test_minmax(self): 253 | u = self.type2test([0, 1, 2]) 254 | self.assertEqual(min(u), 0) 255 | self.assertEqual(max(u), 2) 256 | 257 | def test_addmul(self): 258 | u1 = self.type2test([0]) 259 | u2 = self.type2test([0, 1]) 260 | self.assertEqual(u1, u1 + self.type2test()) 261 | self.assertEqual(u1, self.type2test() + u1) 262 | self.assertEqual(u1 + self.type2test([1]), u2) 263 | self.assertEqual(self.type2test([-1]) + u1, self.type2test([-1, 0])) 264 | self.assertEqual(self.type2test(), u2*0) 265 | self.assertEqual(self.type2test(), 0*u2) 266 | self.assertEqual(self.type2test(), u2*0) 267 | self.assertEqual(self.type2test(), 0*u2) 268 | self.assertEqual(u2, u2*1) 269 | self.assertEqual(u2, 1*u2) 270 | self.assertEqual(u2, u2*1) 271 | self.assertEqual(u2, 1*u2) 272 | self.assertEqual(u2+u2, u2*2) 273 | self.assertEqual(u2+u2, 2*u2) 274 | self.assertEqual(u2+u2, u2*2) 275 | self.assertEqual(u2+u2, 2*u2) 276 | self.assertEqual(u2+u2+u2, u2*3) 277 | self.assertEqual(u2+u2+u2, 3*u2) 278 | 279 | class subclass(self.type2test): 280 | pass 281 | u3 = subclass([0, 1]) 282 | self.assertEqual(u3, u3*1) 283 | self.assert_(u3 is not u3*1) 284 | 285 | def test_iadd(self): 286 | u = self.type2test([0, 1]) 287 | u += self.type2test() 288 | self.assertEqual(u, self.type2test([0, 1])) 289 | u += self.type2test([2, 3]) 290 | self.assertEqual(u, self.type2test([0, 1, 2, 3])) 291 | u += self.type2test([4, 5]) 292 | self.assertEqual(u, self.type2test([0, 1, 2, 3, 4, 5])) 293 | 294 | u = self.type2test("spam") 295 | u += self.type2test("eggs") 296 | self.assertEqual(u, self.type2test("spameggs")) 297 | 298 | def test_imul(self): 299 | u = self.type2test([0, 1]) 300 | u *= 3 301 | self.assertEqual(u, self.type2test([0, 1, 0, 1, 0, 1])) 302 | 303 | def test_getitemoverwriteiter(self): 304 | # Verify that __getitem__ overrides are not recognized by __iter__ 305 | class T(self.type2test): 306 | def __getitem__(self, key): # pragma: no cover 307 | return str(key) + '!!!' 308 | self.assertEqual(next(iter(T((1,2)))), 1) 309 | 310 | def test_repeat(self): 311 | for m in range(4): 312 | s = tuple(range(m)) 313 | for n in range(-3, 5): 314 | self.assertEqual(self.type2test(s*n), self.type2test(s)*n) 315 | self.assertEqual(self.type2test(s)*(-4), self.type2test([])) 316 | self.assertEqual(id(s), id(s*1)) 317 | 318 | def test_subscript(self): 319 | a = self.type2test([10, 11]) 320 | self.assertEqual(a.__getitem__(0), 10) 321 | self.assertEqual(a.__getitem__(1), 11) 322 | self.assertEqual(a.__getitem__(-2), 10) 323 | self.assertEqual(a.__getitem__(-1), 11) 324 | self.assertRaises(IndexError, a.__getitem__, -3) 325 | self.assertRaises(IndexError, a.__getitem__, 3) 326 | self.assertEqual(a.__getitem__(slice(0,1)), self.type2test([10])) 327 | self.assertEqual(a.__getitem__(slice(1,2)), self.type2test([11])) 328 | self.assertEqual(a.__getitem__(slice(0,2)), self.type2test([10, 11])) 329 | self.assertEqual(a.__getitem__(slice(0,3)), self.type2test([10, 11])) 330 | self.assertEqual(a.__getitem__(slice(3,5)), self.type2test([])) 331 | self.assertRaises(ValueError, a.__getitem__, slice(0, 10, 0)) 332 | self.assertRaises(TypeError, a.__getitem__, 'x') 333 | -------------------------------------------------------------------------------- /blist/test/sorteddict_tests.py: -------------------------------------------------------------------------------- 1 | import bisect 2 | import sys 3 | import blist 4 | from blist.test import mapping_tests 5 | 6 | def CmpToKey(mycmp): 7 | 'Convert a cmp= function into a key= function' 8 | class K(object): 9 | def __init__(self, obj): 10 | self.obj = obj 11 | def __lt__(self, other): 12 | return mycmp(self.obj, other.obj) == -1 13 | return K 14 | 15 | class sorteddict_test(mapping_tests.TestHashMappingProtocol): 16 | type2test = blist.sorteddict 17 | 18 | def _reference(self): 19 | """Return a dictionary of values which are invariant by storage 20 | in the object under test.""" 21 | return {1:2, 3:4, 5:6} 22 | 23 | class Collider(object): 24 | def __init__(self, x): 25 | self.x = x 26 | def __repr__(self): 27 | return 'Collider(%r)' % self.x 28 | def __eq__(self, other): 29 | return self.__class__ == other.__class__ and self.x == other.x 30 | def __lt__(self, other): 31 | if type(self) != type(other): 32 | return NotImplemented 33 | return self.x < other.x 34 | def __hash__(self): 35 | return 42 36 | 37 | def test_repr(self): 38 | d = self._empty_mapping() 39 | self.assertEqual(repr(d), 'sorteddict({})') 40 | d[1] = 2 41 | self.assertEqual(repr(d), 'sorteddict({1: 2})') 42 | d = self._empty_mapping() 43 | d[1] = d 44 | self.assertEqual(repr(d), 'sorteddict({1: sorteddict({...})})') 45 | d = self._empty_mapping() 46 | d[self.Collider(1)] = 1 47 | d[self.Collider(2)] = 2 48 | self.assertEqual(repr(d), 49 | 'sorteddict({Collider(1): 1, Collider(2): 2})') 50 | d = self._empty_mapping() 51 | d[self.Collider(2)] = 2 52 | d[self.Collider(1)] = 1 53 | self.assertEqual(repr(d), 54 | 'sorteddict({Collider(1): 1, Collider(2): 2})') 55 | 56 | class Exc(Exception): pass 57 | 58 | class BadRepr(object): 59 | def __repr__(self): 60 | raise Exc() 61 | 62 | d = self._full_mapping({1: BadRepr()}) 63 | self.assertRaises(Exc, repr, d) 64 | 65 | def test_mutatingiteration(self): 66 | pass 67 | 68 | def test_sort(self): 69 | u = self.type2test.fromkeys([1, 0]) 70 | self.assertEqual(list(u.keys()), [0, 1]) 71 | 72 | u = self.type2test.fromkeys([2,1,0,-1,-2]) 73 | self.assertEqual(u, self.type2test.fromkeys([-2,-1,0,1,2])) 74 | self.assertEqual(list(u.keys()), [-2,-1,0,1,2]) 75 | 76 | a = self.type2test.fromkeys(reversed(list(range(512)))) 77 | self.assertEqual(list(a.keys()), list(range(512))) 78 | 79 | def revcmp(a, b): # pragma: no cover 80 | if a == b: 81 | return 0 82 | elif a < b: 83 | return 1 84 | else: # a > b 85 | return -1 86 | u = self.type2test.fromkeys([2,1,0,-1,-2], key=CmpToKey(revcmp)) 87 | self.assertEqual(list(u.keys()), [2,1,0,-1,-2]) 88 | 89 | # The following dumps core in unpatched Python 1.5: 90 | def myComparison(x,y): 91 | xmod, ymod = x%3, y%7 92 | if xmod == ymod: 93 | return 0 94 | elif xmod < ymod: 95 | return -1 96 | else: # xmod > ymod 97 | return 1 98 | 99 | self.type2test.fromkeys(list(range(12)), key=CmpToKey(myComparison)) 100 | 101 | #def selfmodifyingComparison(x,y): 102 | # z[x+y] = None 103 | # return cmp(x, y) 104 | #z = self.type2test(CmpToKey(selfmodifyingComparison)) 105 | #self.assertRaises(ValueError, z.update, [(i,i) for i in range(12)]) 106 | 107 | self.assertRaises(TypeError, self.type2test.fromkeys, 42, 42, 42, 42) 108 | 109 | def test_view_indexing_without_key(self): 110 | self._test_view_indexing(key=None) 111 | 112 | def test_view_indexing_with_key(self): 113 | self._test_view_indexing(key=lambda x: -x) 114 | 115 | def _test_view_indexing(self, key): 116 | expected_items = [(3, "first"), (7, "second")] 117 | if key is not None: 118 | u = self.type2test(key, expected_items) 119 | expected_items.sort(key=lambda item: key(item[0])) 120 | else: 121 | u = self.type2test(expected_items) 122 | expected_items.sort() 123 | expected_keys, expected_values = list(zip(*expected_items)) 124 | 125 | if sys.version_info[0] < 3: 126 | keys = u.viewkeys() 127 | values = u.viewvalues() 128 | items = u.viewitems() 129 | else: 130 | keys = u.keys() 131 | values = u.values() 132 | items = u.items() 133 | 134 | for i in range(len(expected_items)): 135 | self.assertEqual(keys[i], expected_keys[i]) 136 | self.assertEqual(values[i], expected_values[i]) 137 | self.assertEqual(items[i], expected_items[i]) 138 | 139 | for i in range(-1, len(expected_items)+1): 140 | for j in range(-1, len(expected_items)+1): 141 | self.assertEqual(keys[i:j], blist.sortedset(expected_keys[i:j])) 142 | self.assertEqual(values[i:j], list(expected_values[i:j])) 143 | self.assertEqual(items[i:j], blist.sortedset(expected_items[i:j])) 144 | 145 | self.assertEqual(list(reversed(keys)), list(reversed(expected_keys))) 146 | for i, key in enumerate(expected_keys): 147 | self.assertEqual(keys.index(key), expected_keys.index(key)) 148 | self.assertEqual(keys.count(key), 1) 149 | self.assertEqual(keys.bisect_left(key), i) 150 | self.assertEqual(keys.bisect_right(key), i+1) 151 | self.assertEqual(keys.count(object()), 0) 152 | self.assertRaises(ValueError, keys.index, object()) 153 | 154 | for item in expected_items: 155 | self.assertEqual(items.index(item), expected_items.index(item)) 156 | self.assertEqual(items.count(item), 1) 157 | self.assertEqual(items.count((7, "foo")), 0) 158 | self.assertEqual(items.count((object(), object())), 0) 159 | self.assertRaises(ValueError, items.index, (7, "foo")) 160 | self.assertRaises(ValueError, items.index, (object(), object())) 161 | -------------------------------------------------------------------------------- /blist/test/test_list.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from blist.test import unittest 3 | from blist.test import test_support, list_tests 4 | 5 | class ListTest(list_tests.CommonTest): 6 | type2test = list 7 | 8 | def test_truth(self): 9 | super(ListTest, self).test_truth() 10 | self.assert_(not []) 11 | self.assert_([42]) 12 | 13 | def test_identity(self): 14 | self.assert_([] is not []) 15 | 16 | def test_len(self): 17 | super(ListTest, self).test_len() 18 | self.assertEqual(len([]), 0) 19 | self.assertEqual(len([0]), 1) 20 | self.assertEqual(len([0, 1, 2]), 3) 21 | 22 | def test_main(verbose=None): 23 | test_support.run_unittest(ListTest) 24 | 25 | # verify reference counting 26 | import sys 27 | if verbose: 28 | import gc 29 | counts = [None] * 5 30 | for i in range(len(counts)): 31 | test_support.run_unittest(ListTest) 32 | gc.set_debug(gc.DEBUG_STATS) 33 | gc.collect() 34 | #counts[i] = sys.gettotalrefcount() 35 | print(counts) 36 | 37 | 38 | if __name__ == "__main__": 39 | test_main(verbose=False) 40 | -------------------------------------------------------------------------------- /blist/test/test_support.py: -------------------------------------------------------------------------------- 1 | # This file taken from Python, licensed under the Python License Agreement 2 | 3 | from __future__ import print_function 4 | """Supporting definitions for the Python regression tests.""" 5 | 6 | if __name__ != 'blist.test.test_support': 7 | raise ImportError('test_support must be imported from the test package') 8 | 9 | import sys 10 | 11 | class Error(Exception): 12 | """Base class for regression test exceptions.""" 13 | 14 | class TestFailed(Error): 15 | """Test failed.""" 16 | 17 | class TestSkipped(Error): 18 | """Test skipped. 19 | 20 | This can be raised to indicate that a test was deliberatly 21 | skipped, but not because a feature wasn't available. For 22 | example, if some resource can't be used, such as the network 23 | appears to be unavailable, this should be raised instead of 24 | TestFailed. 25 | """ 26 | 27 | class ResourceDenied(TestSkipped): 28 | """Test skipped because it requested a disallowed resource. 29 | 30 | This is raised when a test calls requires() for a resource that 31 | has not be enabled. It is used to distinguish between expected 32 | and unexpected skips. 33 | """ 34 | 35 | verbose = 1 # Flag set to 0 by regrtest.py 36 | use_resources = None # Flag set to [] by regrtest.py 37 | max_memuse = 0 # Disable bigmem tests (they will still be run with 38 | # small sizes, to make sure they work.) 39 | 40 | # _original_stdout is meant to hold stdout at the time regrtest began. 41 | # This may be "the real" stdout, or IDLE's emulation of stdout, or whatever. 42 | # The point is to have some flavor of stdout the user can actually see. 43 | _original_stdout = None 44 | def record_original_stdout(stdout): 45 | global _original_stdout 46 | _original_stdout = stdout 47 | 48 | def get_original_stdout(): 49 | return _original_stdout or sys.stdout 50 | 51 | def unload(name): 52 | try: 53 | del sys.modules[name] 54 | except KeyError: 55 | pass 56 | 57 | def unlink(filename): 58 | import os 59 | try: 60 | os.unlink(filename) 61 | except OSError: 62 | pass 63 | 64 | def forget(modname): 65 | '''"Forget" a module was ever imported by removing it from sys.modules and 66 | deleting any .pyc and .pyo files.''' 67 | unload(modname) 68 | import os 69 | for dirname in sys.path: 70 | unlink(os.path.join(dirname, modname + os.extsep + 'pyc')) 71 | # Deleting the .pyo file cannot be within the 'try' for the .pyc since 72 | # the chance exists that there is no .pyc (and thus the 'try' statement 73 | # is exited) but there is a .pyo file. 74 | unlink(os.path.join(dirname, modname + os.extsep + 'pyo')) 75 | 76 | def is_resource_enabled(resource): 77 | """Test whether a resource is enabled. Known resources are set by 78 | regrtest.py.""" 79 | return use_resources is not None and resource in use_resources 80 | 81 | def requires(resource, msg=None): 82 | """Raise ResourceDenied if the specified resource is not available. 83 | 84 | If the caller's module is __main__ then automatically return True. The 85 | possibility of False being returned occurs when regrtest.py is executing.""" 86 | # see if the caller's module is __main__ - if so, treat as if 87 | # the resource was set 88 | if sys._getframe().f_back.f_globals.get("__name__") == "__main__": 89 | return 90 | if not is_resource_enabled(resource): 91 | if msg is None: 92 | msg = "Use of the `%s' resource not enabled" % resource 93 | raise ResourceDenied(msg) 94 | 95 | FUZZ = 1e-6 96 | 97 | def fcmp(x, y): # fuzzy comparison function 98 | if type(x) == type(0.0) or type(y) == type(0.0): 99 | try: 100 | x, y = coerce(x, y) 101 | fuzz = (abs(x) + abs(y)) * FUZZ 102 | if abs(x-y) <= fuzz: 103 | return 0 104 | except: 105 | pass 106 | elif type(x) == type(y) and type(x) in (type(()), type([])): 107 | for i in range(min(len(x), len(y))): 108 | outcome = fcmp(x[i], y[i]) 109 | if outcome != 0: 110 | return outcome 111 | return cmp(len(x), len(y)) 112 | return cmp(x, y) 113 | 114 | try: 115 | str 116 | have_unicode = 1 117 | except NameError: 118 | have_unicode = 0 119 | 120 | is_jython = sys.platform.startswith('java') 121 | 122 | import os 123 | # Filename used for testing 124 | if os.name == 'java': 125 | # Jython disallows @ in module names 126 | TESTFN = '$test' 127 | elif os.name == 'riscos': 128 | TESTFN = 'testfile' 129 | else: 130 | TESTFN = '@test' 131 | # Unicode name only used if TEST_FN_ENCODING exists for the platform. 132 | if have_unicode: 133 | # Assuming sys.getfilesystemencoding()!=sys.getdefaultencoding() 134 | # TESTFN_UNICODE is a filename that can be encoded using the 135 | # file system encoding, but *not* with the default (ascii) encoding 136 | if isinstance('', str): 137 | # python -U 138 | # XXX perhaps unicode() should accept Unicode strings? 139 | TESTFN_UNICODE = "@test-\xe0\xf2" 140 | else: 141 | # 2 latin characters. 142 | TESTFN_UNICODE = str("@test-\xe0\xf2", "latin-1") 143 | TESTFN_ENCODING = sys.getfilesystemencoding() 144 | 145 | # Make sure we can write to TESTFN, try in /tmp if we can't 146 | fp = None 147 | try: 148 | fp = open(TESTFN, 'w+') 149 | except IOError: 150 | TMP_TESTFN = os.path.join('/tmp', TESTFN) 151 | try: 152 | fp = open(TMP_TESTFN, 'w+') 153 | TESTFN = TMP_TESTFN 154 | del TMP_TESTFN 155 | except IOError: 156 | print(('WARNING: tests will fail, unable to write to: %s or %s' % 157 | (TESTFN, TMP_TESTFN))) 158 | if fp is not None: 159 | fp.close() 160 | unlink(TESTFN) 161 | del os, fp 162 | 163 | def findfile(file, here=__file__): 164 | """Try to find a file on sys.path and the working directory. If it is not 165 | found the argument passed to the function is returned (this does not 166 | necessarily signal failure; could still be the legitimate path).""" 167 | import os 168 | if os.path.isabs(file): 169 | return file 170 | path = sys.path 171 | path = [os.path.dirname(here)] + path 172 | for dn in path: 173 | fn = os.path.join(dn, file) 174 | if os.path.exists(fn): return fn 175 | return file 176 | 177 | def verify(condition, reason='test failed'): 178 | """Verify that condition is true. If not, raise TestFailed. 179 | 180 | The optional argument reason can be given to provide 181 | a better error text. 182 | """ 183 | 184 | if not condition: 185 | raise TestFailed(reason) 186 | 187 | def vereq(a, b): 188 | """Raise TestFailed if a == b is false. 189 | 190 | This is better than verify(a == b) because, in case of failure, the 191 | error message incorporates repr(a) and repr(b) so you can see the 192 | inputs. 193 | 194 | Note that "not (a == b)" isn't necessarily the same as "a != b"; the 195 | former is tested. 196 | """ 197 | 198 | if not (a == b): 199 | raise TestFailed("%r == %r" % (a, b)) 200 | 201 | def sortdict(dict): 202 | "Like repr(dict), but in sorted order." 203 | items = list(dict.items()) 204 | items.sort() 205 | reprpairs = ["%r: %r" % pair for pair in items] 206 | withcommas = ", ".join(reprpairs) 207 | return "{%s}" % withcommas 208 | 209 | def check_syntax(statement): 210 | try: 211 | compile(statement, '', 'exec') 212 | except SyntaxError: 213 | pass 214 | else: 215 | print('Missing SyntaxError: "%s"' % statement) 216 | 217 | def open_urlresource(url): 218 | import urllib.request, urllib.parse, urllib.error, urllib.parse 219 | import os.path 220 | 221 | filename = urllib.parse.urlparse(url)[2].split('/')[-1] # '/': it's URL! 222 | 223 | for path in [os.path.curdir, os.path.pardir]: 224 | fn = os.path.join(path, filename) 225 | if os.path.exists(fn): 226 | return open(fn) 227 | 228 | requires('urlfetch') 229 | print('\tfetching %s ...' % url, file=get_original_stdout()) 230 | fn, _ = urllib.request.urlretrieve(url, filename) 231 | return open(fn) 232 | 233 | #======================================================================= 234 | # Decorator for running a function in a different locale, correctly resetting 235 | # it afterwards. 236 | 237 | def run_with_locale(catstr, *locales): 238 | def decorator(func): 239 | def inner(*args, **kwds): 240 | try: 241 | import locale 242 | category = getattr(locale, catstr) 243 | orig_locale = locale.setlocale(category) 244 | except AttributeError: 245 | # if the test author gives us an invalid category string 246 | raise 247 | except: 248 | # cannot retrieve original locale, so do nothing 249 | locale = orig_locale = None 250 | else: 251 | for loc in locales: 252 | try: 253 | locale.setlocale(category, loc) 254 | break 255 | except: 256 | pass 257 | 258 | # now run the function, resetting the locale on exceptions 259 | try: 260 | return func(*args, **kwds) 261 | finally: 262 | if locale and orig_locale: 263 | locale.setlocale(category, orig_locale) 264 | inner.__name__ = func.__name__ 265 | inner.__doc__ = func.__doc__ 266 | return inner 267 | return decorator 268 | 269 | #======================================================================= 270 | # Big-memory-test support. Separate from 'resources' because memory use should be configurable. 271 | 272 | # Some handy shorthands. Note that these are used for byte-limits as well 273 | # as size-limits, in the various bigmem tests 274 | _1M = 1024*1024 275 | _1G = 1024 * _1M 276 | _2G = 2 * _1G 277 | 278 | def set_memlimit(limit): 279 | import re 280 | global max_memuse 281 | sizes = { 282 | 'k': 1024, 283 | 'm': _1M, 284 | 'g': _1G, 285 | 't': 1024*_1G, 286 | } 287 | m = re.match(r'(\d+(\.\d+)?) (K|M|G|T)b?$', limit, 288 | re.IGNORECASE | re.VERBOSE) 289 | if m is None: 290 | raise ValueError('Invalid memory limit %r' % (limit,)) 291 | memlimit = int(float(m.group(1)) * sizes[m.group(3).lower()]) 292 | if memlimit < 2.5*_1G: 293 | raise ValueError('Memory limit %r too low to be useful' % (limit,)) 294 | max_memuse = memlimit 295 | 296 | def bigmemtest(minsize, memuse, overhead=5*_1M): 297 | """Decorator for bigmem tests. 298 | 299 | 'minsize' is the minimum useful size for the test (in arbitrary, 300 | test-interpreted units.) 'memuse' is the number of 'bytes per size' for 301 | the test, or a good estimate of it. 'overhead' specifies fixed overhead, 302 | independant of the testsize, and defaults to 5Mb. 303 | 304 | The decorator tries to guess a good value for 'size' and passes it to 305 | the decorated test function. If minsize * memuse is more than the 306 | allowed memory use (as defined by max_memuse), the test is skipped. 307 | Otherwise, minsize is adjusted upward to use up to max_memuse. 308 | """ 309 | def decorator(f): 310 | def wrapper(self): 311 | if not max_memuse: 312 | # If max_memuse is 0 (the default), 313 | # we still want to run the tests with size set to a few kb, 314 | # to make sure they work. We still want to avoid using 315 | # too much memory, though, but we do that noisily. 316 | maxsize = 5147 317 | self.failIf(maxsize * memuse + overhead > 20 * _1M) 318 | else: 319 | maxsize = int((max_memuse - overhead) / memuse) 320 | if maxsize < minsize: 321 | # Really ought to print 'test skipped' or something 322 | if verbose: 323 | sys.stderr.write("Skipping %s because of memory " 324 | "constraint\n" % (f.__name__,)) 325 | return 326 | # Try to keep some breathing room in memory use 327 | maxsize = max(maxsize - 50 * _1M, minsize) 328 | return f(self, maxsize) 329 | wrapper.minsize = minsize 330 | wrapper.memuse = memuse 331 | wrapper.overhead = overhead 332 | return wrapper 333 | return decorator 334 | 335 | #======================================================================= 336 | # Preliminary PyUNIT integration. 337 | 338 | from . import unittest 339 | 340 | 341 | class BasicTestRunner: 342 | def run(self, test): 343 | result = unittest.TestResult() 344 | test(result) 345 | return result 346 | 347 | def run_suite(suite, testclass=None): 348 | """Run tests from a unittest.TestSuite-derived class.""" 349 | if verbose: 350 | runner = unittest.TextTestRunner(sys.stdout, verbosity=2) 351 | else: 352 | runner = BasicTestRunner() 353 | 354 | result = runner.run(suite) 355 | if not result.wasSuccessful(): 356 | if len(result.errors) == 1 and not result.failures: 357 | err = result.errors[0][1] 358 | elif len(result.failures) == 1 and not result.errors: 359 | err = result.failures[0][1] 360 | else: 361 | if testclass is None: 362 | msg = "errors occurred; run in verbose mode for details" 363 | else: 364 | msg = "errors occurred in %s.%s" \ 365 | % (testclass.__module__, testclass.__name__) 366 | raise TestFailed(msg) 367 | raise TestFailed(err) 368 | 369 | 370 | def run_unittest(*classes): 371 | """Run tests from unittest.TestCase-derived classes.""" 372 | suite = unittest.TestSuite() 373 | for cls in classes: 374 | if isinstance(cls, (unittest.TestSuite, unittest.TestCase)): 375 | suite.addTest(cls) 376 | else: 377 | suite.addTest(unittest.makeSuite(cls)) 378 | if len(classes)==1: 379 | testclass = classes[0] 380 | else: 381 | testclass = None 382 | run_suite(suite, testclass) 383 | 384 | 385 | #======================================================================= 386 | # doctest driver. 387 | 388 | def run_doctest(module, verbosity=None): 389 | """Run doctest on the given module. Return (#failures, #tests). 390 | 391 | If optional argument verbosity is not specified (or is None), pass 392 | test_support's belief about verbosity on to doctest. Else doctest's 393 | usual behavior is used (it searches sys.argv for -v). 394 | """ 395 | 396 | import doctest 397 | 398 | if verbosity is None: 399 | verbosity = verbose 400 | else: 401 | verbosity = None 402 | 403 | # Direct doctest output (normally just errors) to real stdout; doctest 404 | # output shouldn't be compared by regrtest. 405 | save_stdout = sys.stdout 406 | sys.stdout = get_original_stdout() 407 | try: 408 | f, t = doctest.testmod(module, verbose=verbosity) 409 | if f: 410 | raise TestFailed("%d of %d doctests failed" % (f, t)) 411 | finally: 412 | sys.stdout = save_stdout 413 | if verbose: 414 | print('doctest (%s) ... %d tests with zero failures' % (module.__name__, t)) 415 | return f, t 416 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " pickle to make pickle files" 22 | @echo " json to make JSON files" 23 | @echo " htmlhelp to make HTML files and a HTML help project" 24 | @echo " qthelp to make HTML files and a qthelp project" 25 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 26 | @echo " changes to make an overview of all changed/added/deprecated items" 27 | @echo " linkcheck to check all external links for integrity" 28 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 29 | 30 | clean: 31 | -rm -rf $(BUILDDIR)/* 32 | 33 | html: 34 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 35 | @echo 36 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 37 | 38 | coverage: 39 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 40 | @echo 41 | @echo "Build finished. The coverage pages are in $(BUILDDIR)/coverage." 42 | 43 | dirhtml: 44 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 45 | @echo 46 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 47 | 48 | pickle: 49 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 50 | @echo 51 | @echo "Build finished; now you can process the pickle files." 52 | 53 | json: 54 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 55 | @echo 56 | @echo "Build finished; now you can process the JSON files." 57 | 58 | htmlhelp: 59 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 60 | @echo 61 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 62 | ".hhp project file in $(BUILDDIR)/htmlhelp." 63 | 64 | qthelp: 65 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 66 | @echo 67 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 68 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 69 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/blist.qhcp" 70 | @echo "To view the help file:" 71 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/blist.qhc" 72 | 73 | latex: 74 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 75 | @echo 76 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 77 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 78 | "run these through (pdf)latex." 79 | 80 | changes: 81 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 82 | @echo 83 | @echo "The overview file is in $(BUILDDIR)/changes." 84 | 85 | linkcheck: 86 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 87 | @echo 88 | @echo "Link check complete; look for any errors in the above output " \ 89 | "or in $(BUILDDIR)/linkcheck/output.txt." 90 | 91 | doctest: 92 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 93 | @echo "Testing of doctests in the sources finished, look at the " \ 94 | "results in $(BUILDDIR)/doctest/output.txt." 95 | -------------------------------------------------------------------------------- /doc/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | {% block rootrellink %} 3 |
  • Stutzbach Enterpises 4 | »
  • 5 | {{ super() }} 6 | {% endblock %} 7 | 8 | {% block footer %} 9 | 15 | 22 | 29 | 30 | {% endblock %} 31 | 32 | {% block extrahead %} 33 | 93 | {% endblock %} 94 | 95 | {% block sidebartoc %} 96 |

    Table of contents

    97 | 116 | 117 |

    blist

    118 | 128 | 129 |

    heapdict

    130 | 137 | 138 |

    winreg_unicode

    139 | 146 | 147 |

    Poker Sleuth

    148 | 157 |

    ion-sampler

    158 | 167 | 168 | {% endblock %} 169 | 170 | {% block sidebarrel %} {% endblock %} 171 | -------------------------------------------------------------------------------- /doc/blist.rst: -------------------------------------------------------------------------------- 1 | .. include:: mymath.txt 2 | 3 | blist 4 | ===== 5 | 6 | .. currentmodule:: blist 7 | 8 | .. class:: blist(iterable) 9 | 10 | The :class:`blist` is a drop-in replacement for the Python 11 | :class:`list` that provides better performance when modifying large 12 | lists. For small lists, the :class:`blist` and the built-in 13 | :class:`list` have virtually identical performance. 14 | 15 | To use the :class:`blist`, you simply change code like this: 16 | 17 | >>> items = [5, 6, 2] 18 | >>> more_items = function_that_returns_a_list() 19 | 20 | to: 21 | 22 | >>> from blist import blist 23 | >>> items = blist([5, 6, 2]) 24 | >>> more_items = blist(function_that_returns_a_list()) 25 | 26 | Python's built-in :class:`list` is a dynamically-sized array; to 27 | insert or remove an item from the beginning or middle of the 28 | list, it has to move most of the list in memory, i.e., |theta(n)| 29 | operations. The :class:`blist` uses a flexible, hybrid array/tree 30 | structure and only needs to move a small portion of items in 31 | memory, specifically using |theta(log n)| operations. 32 | 33 | Creating a :class:`blist` from another :class:`blist` requires 34 | |theta(1)| operations. Creating a :class:`blist` from any other 35 | iterable requires |theta(n)| operations. 36 | 37 | .. method:: L + L2, L2 + L 38 | 39 | :type L: blist 40 | :type L2: list or blist 41 | 42 | Returns a new blist by concatenating two lists. 43 | 44 | If the other list is also a blist, requires |theta(log m + log 45 | n)| operations. If it's a built-in :class:`list`, requires 46 | |theta(m + log n)| operations, where *m* is the size of the other 47 | list and *n* is the size of *L*. 48 | 49 | :rtype: :class:`blist` 50 | 51 | .. method:: x in L 52 | 53 | Returns True if and only if x is an element in the list. 54 | 55 | Requires |theta(n)| operations in the worst case. 56 | 57 | :rtype: :class:`bool` 58 | 59 | .. method:: del L[i] 60 | 61 | Removes the element located at index *i* from the list. 62 | 63 | Requires |theta(log n)| operations. 64 | 65 | .. method:: del L[i:j] 66 | 67 | Removes the elements from *i* to *j* from the list. 68 | 69 | Requires |theta(log n)| operations. 70 | 71 | .. method:: L == L2, L != L2, L < L2, L <= L2, L > L2, L >= L2 72 | 73 | Compares two lists. For full details see `Comparisons 74 | `_ in 75 | the Python language reference. 76 | 77 | Requires |theta(n)| operations in the worst case. 78 | 79 | :rtype: :class:`bool` 80 | 81 | .. method:: L[i] 82 | 83 | Returns the element at position *i*. 84 | 85 | Requires |theta(log n)| operations in the worst case but only 86 | |theta(1)| operations if the list's size has not been changed 87 | recently. 88 | 89 | :rtype: item 90 | 91 | .. method:: L[i:j] 92 | 93 | Returns a new blist containing the elements from *i* to *j*. 94 | 95 | Requires |theta(log n)| operations. 96 | 97 | :rtype: :class:`blist` 98 | 99 | .. method:: L += iterable 100 | 101 | The same as ``L.extend(iterable)``. 102 | 103 | .. method:: L *= k 104 | 105 | Increase the length of the list by a factor of *k*, by appending 106 | *k-1* shallow copies of the list. 107 | 108 | Requires |theta(log k)| operations. 109 | 110 | .. method:: iter(L) 111 | 112 | Creates an iterator over the list. 113 | 114 | Requires |theta(log n)| operations to create the iterator. Each 115 | element from the iterator requires |theta(1)| operations to 116 | retrieve, or |theta(n)| operations to iterate over the entire 117 | list. 118 | 119 | :rtype: iterator 120 | 121 | .. method: len(L) 122 | 123 | Returns the number of elements in the list. 124 | 125 | Requires |theta(1)| operations. 126 | 127 | :rtype: :class:`int` 128 | 129 | .. method: L * k or k * L 130 | 131 | Returns a new blist contained k shallow copies of L concatenated. 132 | 133 | Requires |theta(log k)| operations. 134 | 135 | :rtype: :class:`blist` 136 | 137 | .. method: reversed(L) 138 | 139 | Creates an iterator to traverse the list in reverse. 140 | 141 | Requires |theta(log n)| operations to create the iterator. Each 142 | element from the iterator requires |theta(1)| operations to 143 | retrieve, or |theta(n)| operations to iterate over the entire 144 | list. 145 | 146 | :rtype: iterator 147 | 148 | .. method: L[i] = value 149 | 150 | Replace the item at index *i* with *value*. 151 | 152 | Requires |theta(log n)| operations in the worst case but only 153 | |theta(1)| operations if the list's size has not been changed 154 | recently. 155 | 156 | .. method: L[i:j] = iterable 157 | 158 | Replaces the items at indices *i* through *j* with the items 159 | from *iterable*. 160 | 161 | If *iterable* is a :class:`blist`, requires |theta(log m + log 162 | n)| operations. Otherwise, requires |theta(m + log n)| 163 | operations, where *k* is the length of *iterable* and *n* is the 164 | initial length of *L* 165 | 166 | .. _blist.append: 167 | .. method:: L.append(object) 168 | 169 | Append object to the end of the list. 170 | 171 | Requires amortized |theta(1)| operations. 172 | 173 | .. method:: L.count(value) 174 | 175 | Returns the number of occurrences of *value* in the list. 176 | 177 | Requires |theta(n)| operations in the worst case. 178 | 179 | :rtype: :class:`int` 180 | 181 | .. method:: L.extend(iterable) 182 | 183 | Extend the list by appending all elements from the iterable. 184 | 185 | If iterable is a blist, requires |theta(log m + log n)| 186 | operations. Otherwise, requires |theta(m + log n)| operations, 187 | where *m* is the size of the iterable and *n* is the size of the 188 | list initially. 189 | 190 | .. method:: L.index(value, [start, [stop]]) 191 | 192 | Returns the smallest *k* such that :math:`s[k] == x` and 193 | :math:`i <= k < j`. Raises ValueError if *value* is not 194 | present. *stop* defaults to the end of the list. *start* 195 | defaults to the beginning. Negative indexes are supported, as 196 | for slice indices. 197 | 198 | Requires |theta(stop-start)| operations in the worst case. 199 | 200 | :rtype: :class:`int` 201 | 202 | .. _blist.insert: 203 | .. method:: L.insert(index, object) 204 | 205 | Inserts object before index. The same as s[i:i] = [x]. Negative 206 | indexes are supported, as for slice indices. 207 | 208 | Requires |theta(log n)| operations. 209 | 210 | .. method:: L.pop([index]) 211 | 212 | Removes and return item at index (default last). Raises 213 | IndexError if list is empty or index is out of range. Negative 214 | indexes are supported, as for slice indices. 215 | 216 | Requires |theta(log n)| operations. 217 | 218 | :rtype: item 219 | 220 | .. method:: L.remove(value) 221 | 222 | Removes the first occurrence of *value*. Raises ValueError if 223 | *value* is not present. 224 | 225 | Requires |theta(n)| operations in the worst case. 226 | 227 | .. method:: L.reverse() 228 | 229 | Reverse the list *in place*. 230 | 231 | Requires |theta(n)| operations. 232 | 233 | .. method:: L.sort(cmp=None, key=None, reverse=False) 234 | 235 | Stable sort *in place*. 236 | 237 | *cmp* is suppoted in Python 2 for compatibility with Python 238 | 2's list.sort. All users are encouraged to migrate to using the 239 | `key` parameter, which is more efficient. 240 | 241 | *key* specifies a function of one argument that is used to 242 | extract a comparison key from each list element: 243 | ``key=str.lower``. The default value is ``None`` (compare the 244 | elements directly). 245 | 246 | *reverse* is a boolean value. If set to ``True``, then the list 247 | elements are sorted as if each comparison were reversed. 248 | 249 | Requires |theta(n log n)| operations in the worst and average 250 | case and |theta(n)| operation in the best case. 251 | -------------------------------------------------------------------------------- /doc/btuple.rst: -------------------------------------------------------------------------------- 1 | .. include:: mymath.txt 2 | 3 | btuple 4 | ====== 5 | 6 | .. currentmodule:: blist 7 | 8 | .. class:: btuple(iterable) 9 | 10 | The :class:`btuple` is a drop-in replacement for the Python 11 | :class:`tuple` that provides better performance for some 12 | operations on large tuples. Operations such as concatenation, 13 | taking a slice, and converting from a :class:`blist` are 14 | inexpensive. 15 | 16 | The current implementation of the :class:`btuple` is a Python 17 | wrapper around the :class:`blist`. Consequently, for small 18 | tuples the built-in :class:`tuple` always provides better 19 | performance. In a future version, the :class:`btuple` may be 20 | implemented in C; it would then have nearly identical performance 21 | to the built-in :class:`tuple` for small tuples. 22 | 23 | To use the :class:`btuple`, you simply change code like this: 24 | 25 | >>> items = (5, 6, 2) 26 | >>> more_items = function_that_returns_a_tuple() 27 | 28 | to: 29 | 30 | >>> from btuple import btuple 31 | >>> items = btuple((5, 6, 2)) 32 | >>> more_items = btuple(function_that_returns_a_tuple()) 33 | 34 | Creating a :class:`btuple` from another :class:`btuple` or a 35 | :class:`blist` requires |theta(1)| operations. Creating a 36 | :class:`btuple` from any other iterable requires |theta(n)| operations. 37 | 38 | .. method:: L + L2, L2 + L 39 | 40 | :type L: btuple 41 | :type L2: tuple or btuple 42 | 43 | Returns a new btuple by concatenating two tuples. 44 | 45 | If the other tuple is also a btuple, requires |theta(log m + log 46 | n)| operations. If it's a built-in :class:`tuple`, requires 47 | |theta(m + log n)| operations, where *m* is the size of the other 48 | tuple and *n* is the size of *L*. 49 | 50 | :rtype: :class:`btuple` 51 | 52 | .. method:: x in L 53 | 54 | Returns True if and only if x is an element in the tuple. 55 | 56 | Requires |theta(n)| operations in the worst case. 57 | 58 | :rtype: :class:`bool` 59 | 60 | .. method:: L == L2, L != L2, L < L2, L <= L2, L > L2, L >= L2 61 | 62 | Compares two tuples. For full details see `Comparisons 63 | `_ in 64 | the Python language reference. 65 | 66 | Requires |theta(n)| operations in the worst case. 67 | 68 | :rtype: :class:`bool` 69 | 70 | .. method:: L[i] 71 | 72 | Returns the element at position *i*. 73 | 74 | Requires |theta(1)| operations in the amortized worst case. 75 | 76 | :rtype: item 77 | 78 | .. method:: L[i:j] 79 | 80 | Returns a new btuple containing the elements from *i* to *j*. 81 | 82 | Requires |theta(log n)| operations. 83 | 84 | :rtype: :class:`btuple` 85 | 86 | .. method:: iter(L) 87 | 88 | Creates an iterator over the tuple. 89 | 90 | Requires |theta(log n)| operations to create the iterator. Each 91 | element from the iterator requires |theta(1)| operations to 92 | retrieve, or |theta(n)| operations to iterate over the entire 93 | tuple. 94 | 95 | :rtype: iterator 96 | 97 | .. method: len(L) 98 | 99 | Returns the number of elements in the tuple. 100 | 101 | Requires |theta(1)| operations. 102 | 103 | :rtype: :class:`int` 104 | 105 | .. method: L * k or k * L 106 | 107 | Returns a new btuple contained k shallow copies of L concatenated. 108 | 109 | Requires |theta(log k)| operations. 110 | 111 | :rtype: :class:`btuple` 112 | 113 | .. method: reversed(L) 114 | 115 | Creates an iterator to traverse the tuple in reverse. 116 | 117 | Requires |theta(log n)| operations to create the iterator. Each 118 | element from the iterator requires |theta(1)| operations to 119 | retrieve, or |theta(n)| operations to iterate over the entire 120 | tuple. 121 | 122 | :rtype: iterator 123 | 124 | .. method:: L.count(value) 125 | 126 | Returns the number of occurrences of *value* in the tuple. 127 | 128 | Requires |theta(n)| operations in the worst case. 129 | 130 | :rtype: :class:`int` 131 | 132 | .. method:: L.index(value, [start, [stop]]) 133 | 134 | Returns the smallest *k* such that :math:`s[k] == x` and 135 | :math:`i <= k < j`. Raises ValueError if *value* is not 136 | present. *stop* defaults to the end of the tuple. *start* 137 | defaults to the beginning. Negative indexes are supported, as 138 | for slice indices. 139 | 140 | Requires |theta(stop-start)| operations in the worst case. 141 | 142 | :rtype: :class:`int` 143 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # blist documentation build configuration file, created by 4 | # sphinx-quickstart on Sat May 15 18:18:54 2010. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.append(os.path.abspath('.')) 20 | sys.path.insert(0, os.path.abspath('..')) 21 | 22 | # -- General configuration ----------------------------------------------------- 23 | 24 | # Add any Sphinx extension module names here, as strings. They can be extensions 25 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 26 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.coverage', 'sphinx.ext.pngmath'] 27 | 28 | # Add any paths that contain templates here, relative to this directory. 29 | templates_path = ['_templates'] 30 | 31 | # The suffix of source filenames. 32 | source_suffix = '.rst' 33 | 34 | # The encoding of source files. 35 | #source_encoding = 'utf-8' 36 | 37 | # The master toctree document. 38 | master_doc = 'index' 39 | 40 | # General information about the project. 41 | project = u'blist' 42 | copyright = u'2010, Stutzbach Enterprises, LLC' 43 | 44 | # The version info for the project you're documenting, acts as replacement for 45 | # |version| and |release|, also used in various other places throughout the 46 | # built documents. 47 | # 48 | # The short X.Y version. 49 | version = '1.2' 50 | # The full version, including alpha/beta/rc tags. 51 | release = '1.2.0' 52 | 53 | # The language for content autogenerated by Sphinx. Refer to documentation 54 | # for a list of supported languages. 55 | #language = None 56 | 57 | # There are two options for replacing |today|: either, you set today to some 58 | # non-false value, then it is used: 59 | #today = '' 60 | # Else, today_fmt is used as the format for a strftime call. 61 | #today_fmt = '%B %d, %Y' 62 | 63 | # List of documents that shouldn't be included in the build. 64 | #unused_docs = [] 65 | 66 | # List of directories, relative to source directory, that shouldn't be searched 67 | # for source files. 68 | exclude_trees = ['_build'] 69 | 70 | # The reST default role (used for this markup: `text`) to use for all documents. 71 | #default_role = None 72 | 73 | # If true, '()' will be appended to :func: etc. cross-reference text. 74 | #add_function_parentheses = True 75 | 76 | # If true, the current module name will be prepended to all description 77 | # unit titles (such as .. function::). 78 | #add_module_names = True 79 | 80 | # If true, sectionauthor and moduleauthor directives will be shown in the 81 | # output. They are ignored by default. 82 | #show_authors = False 83 | 84 | # The name of the Pygments (syntax highlighting) style to use. 85 | pygments_style = 'sphinx' 86 | 87 | # A list of ignored prefixes for module index sorting. 88 | #modindex_common_prefix = [] 89 | 90 | 91 | # -- Options for HTML output --------------------------------------------------- 92 | 93 | # The theme to use for HTML and HTML Help pages. Major themes that come with 94 | # Sphinx are currently 'default' and 'sphinxdoc'. 95 | html_theme = 'default' 96 | 97 | # Theme options are theme-specific and customize the look and feel of a theme 98 | # further. For a list of options available for each theme, see the 99 | # documentation. 100 | #html_theme_options = {} 101 | 102 | # Add any paths that contain custom themes here, relative to this directory. 103 | #html_theme_path = [] 104 | 105 | # The name for this set of Sphinx documents. If None, it defaults to 106 | # " v documentation". 107 | #html_title = None 108 | 109 | # A shorter title for the navigation bar. Default is the same as html_title. 110 | #html_short_title = None 111 | 112 | # The name of an image file (relative to this directory) to place at the top 113 | # of the sidebar. 114 | #html_logo = None 115 | 116 | # The name of an image file (within the static path) to use as favicon of the 117 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 118 | # pixels large. 119 | #html_favicon = None 120 | 121 | # Add any paths that contain custom static files (such as style sheets) here, 122 | # relative to this directory. They are copied after the builtin static files, 123 | # so a file named "default.css" will overwrite the builtin "default.css". 124 | html_static_path = ['_static'] 125 | 126 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 127 | # using the given strftime format. 128 | #html_last_updated_fmt = '%b %d, %Y' 129 | 130 | # If true, SmartyPants will be used to convert quotes and dashes to 131 | # typographically correct entities. 132 | html_use_smartypants = True 133 | 134 | # Custom sidebar templates, maps document names to template names. 135 | #html_sidebars = {} 136 | 137 | # Additional templates that should be rendered to pages, maps page names to 138 | # template names. 139 | #html_additional_pages = {} 140 | 141 | # If false, no module index is generated. 142 | #html_use_modindex = True 143 | 144 | # If false, no index is generated. 145 | #html_use_index = True 146 | 147 | # If true, the index is split into individual pages for each letter. 148 | #html_split_index = False 149 | 150 | # If true, links to the reST sources are added to the pages. 151 | html_show_sourcelink = False 152 | 153 | # If true, an OpenSearch description file will be output, and all pages will 154 | # contain a tag referring to it. The value of this option must be the 155 | # base URL from which the finished HTML is served. 156 | #html_use_opensearch = '' 157 | 158 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 159 | #html_file_suffix = '' 160 | 161 | # Output file base name for HTML help builder. 162 | htmlhelp_basename = 'blistdoc' 163 | 164 | 165 | # -- Options for LaTeX output -------------------------------------------------- 166 | 167 | # The paper size ('letter' or 'a4'). 168 | #latex_paper_size = 'letter' 169 | 170 | # The font size ('10pt', '11pt' or '12pt'). 171 | #latex_font_size = '10pt' 172 | 173 | # Grouping the document tree into LaTeX files. List of tuples 174 | # (source start file, target name, title, author, documentclass [howto/manual]). 175 | latex_documents = [ 176 | ('index', 'blist.tex', u'blist Documentation', 177 | u'Stutzbach Enterprises, LLC', 'manual'), 178 | ] 179 | 180 | # The name of an image file (relative to this directory) to place at the top of 181 | # the title page. 182 | #latex_logo = None 183 | 184 | # For "manual" documents, if this is true, then toplevel headings are parts, 185 | # not chapters. 186 | #latex_use_parts = False 187 | 188 | # Additional stuff for the LaTeX preamble. 189 | #latex_preamble = '' 190 | 191 | # Documents to append as an appendix to all manuals. 192 | #latex_appendices = [] 193 | 194 | # If false, no module index is generated. 195 | #latex_use_modindex = True 196 | 197 | 198 | # Example configuration for intersphinx: refer to the Python standard library. 199 | intersphinx_mapping = {'http://docs.python.org/': None} 200 | 201 | pngmath_use_preview = True 202 | pngmath_dvipng_args = ['-gamma 1.5', '-D 110', '-bg Transparent'] 203 | -------------------------------------------------------------------------------- /doc/implementation.rst: -------------------------------------------------------------------------------- 1 | Implementation Details 2 | ====================== 3 | 4 | BLists are based on B+Trees, which are a dictionary data structure 5 | where each element is a (key, value) pair and the keys are kept in 6 | sorted order. B+Trees internally use a tree representation. All data 7 | is stored in the leaf nodes of the tree, and all leaf nodes are at the 8 | same level. Unlike binary trees, each node has a large number of 9 | children, stored as an array of references within the node. The 10 | B+Tree operations ensure that each node always has between "limit/2" 11 | and "limit" children (except the root which may have between 0 and 12 | "limit" children). When a B+Tree has fewer than "limit/2" elements, 13 | they will all be contained in a single node (the root). 14 | 15 | Wikipedia has a diagram that may be helpful for understanding the 16 | basic structure of a B+Tree: 17 | 18 | http://en.wikipedia.org/wiki/B+_tree 19 | 20 | Of course, we don't want a dictionary. We want a list. 21 | 22 | In BLists, the "key" is implicit: it's the in-order location of the value. 23 | Instead of keys, each BList node maintains a count of the total number 24 | of data elements beneath it in the tree. This allows walking the tree 25 | efficiently by keeping track of how far we've moved when passing by a 26 | child node. The tree structure gives us O(log n) for most operations, 27 | asymptotically. 28 | 29 | When the BList has fewer than "limit/2" data elements, they are all 30 | stored in the root node. In other words, for small lists a BList 31 | essentially reduces to an array. It should have almost identical 32 | performance to a regular Python list, as only one or two extra if() 33 | statements will be needed per method call. 34 | 35 | Adding elements 36 | --------------- 37 | 38 | Elements are inserted recursively. Each node determines which child 39 | node contains the insertion point, and calls the insertion routine of 40 | that child. 41 | 42 | When we add elements to a BList node, the node may overflow (i.e., 43 | have more than "limit" elements). Instead of overflowing, the node 44 | creates a new BList node and gives half of its elements to the new 45 | node. When the inserting function returns, the function informs its 46 | parent about the new sibling. This causes the parent to add the new 47 | node as a child. If this causes the parent to overflow, it creates a 48 | sibling of its own, notifies its parent, and so on. 49 | 50 | When the root of the tree overflows, it must increase the depth of the 51 | tree. The root creates two new children and splits all of its former 52 | references between these two children (i.e., all former children are now 53 | grandchildren). 54 | 55 | Removing an element 56 | ------------------- 57 | 58 | Removing an element is also done recursively. Each node determines 59 | which child node contains the element to be removed, and calls the 60 | removal routine of that child. 61 | 62 | Removing an element may cause an underflow (i.e., fewer than "limit/2" 63 | elements). It's the parent's job to check if a child has underflowed 64 | after any operation that might cause an underflow. The parent must 65 | then repair the child, either by stealing elements from one of the 66 | child's sibling or merging the child with one of its sibling. If the 67 | parent performs a merge, the parent itself may underflow. 68 | 69 | If a node has only one element, the tree collapses. The node replaces 70 | its one child with its grandchildren. When removing a single element, 71 | this can only happen at the root. 72 | 73 | Removing a range 74 | ---------------- 75 | 76 | The __delslice__ method to remove a range of elements is the most 77 | complex operation in the BList implementation. The first step is to 78 | locate the common parent of all the elements to be removed. The 79 | parent deletes any children who will be completely deleted (i.e., they 80 | are entirely within the range to be deleted). The parent also has to 81 | deal with two children who may be partially deleted: they contain the 82 | left and right boundaries of the deletion range. 83 | 84 | The parent calls the deletion operation recursively on these two 85 | children. When the call returns, the children must return a valid 86 | BList, but they may be in an underflow state, and, worse, they may 87 | have needed to collapse the tree. To make life a little easier, the 88 | children return an integer indicating how many levels of the tree 89 | collapsed (if any). The parent now has two adjacent subtrees of 90 | different heights that need to be put back into the main tree (to keep 91 | it balanced). 92 | 93 | To accomplish this goal, we use a merge-tree operation, defined below. 94 | The parent merges the two adjacent subtrees into a single subtree, 95 | then merges the subtree with one of its other children. If it has no 96 | other children, then the parent collapses to become the subtree and 97 | indicates to its parent the total level of collapse. 98 | 99 | Merging subtrees 100 | ---------------- 101 | 102 | The __delslice__ method needs a way to merge two adjacent subtrees of 103 | potentially different heights. Because we only need to merge *adjacent* 104 | subtrees, we don't have to handle inserting a subtree into the middle of 105 | another. There are only two cases: the far-left and the far-right. If 106 | the two subtrees are the same height, this is a pretty simple operation where 107 | we join their roots together. If the trees are different heights, we 108 | merge the smaller into the larger as follows. Let H be the difference 109 | in their heights. Then, recurse through the larger tree by H levels 110 | and insert the smaller subtree there. 111 | 112 | Retrieving a range and copy-on-write 113 | ------------------------------------ 114 | 115 | One of the most powerful features of BLists is the ability to support 116 | copy-on-write. Thus far we have described a BLists as a tree 117 | structure where parents contain references to their children. None of 118 | the basic tree operations require the children to maintain references 119 | to their parents or siblings. Therefore, it is possible for a child 120 | to have *multiple parents*. The parents can happily share the child 121 | as long as they perform read-only operations on it. If a parent wants 122 | to modify a child in any way, it first checks the child's reference 123 | count. If it is 1, the parent has the only reference and can proceed. 124 | Otherwise, the parent must create a copy of the child, and relinquish 125 | its reference to the child. 126 | 127 | Creating a copy of a child doesn't implicitly copy the child's 128 | subtree. It just creates a new node with new references to the 129 | child's children. In other words, the child and the copy are now 130 | joint parents of their children. 131 | 132 | This assumes that no other code will gain references to internal BList 133 | nodes. The internal nodes are never exposed to the user, so this is a 134 | reasonably safe assumption. In the worst case, if the user manages to 135 | gain a reference to an internal BList node (such as through the gc 136 | module), it will just prevent the BList code from modifying that node. 137 | It will create a copy instead. User-visible nodes (i.e., the root of 138 | a tree) have no parents and are never shared children. 139 | 140 | Why is this copy-on-write operation so useful? 141 | 142 | Consider the common idiom of performing an operation on a slice of a 143 | list. Normally, this requires making a copy of that region of the 144 | list, which is expensive if the region is large. With copy-on-write, 145 | __getslice__ takes logarithmic time. 146 | 147 | Structure 148 | --------- 149 | 150 | Each BList node has the following member variables: 151 | 152 | n: 153 | the total number of user data elements below the node, equal to 154 | len(children) for leaf nodes 155 | 156 | num_children: 157 | the number of children immediately below the node 158 | 159 | 160 | leaf: 161 | true if this node is a leaf node (has user data as children), 162 | false if this node is an interior node (has nodes as children) 163 | 164 | children: 165 | an array of references to the node's children 166 | 167 | Global Constants 168 | ---------------- 169 | 170 | LIMIT: 171 | the maximum size of .children, must be even and >= 8 172 | 173 | HALF: 174 | LIMIT//2, the minimum size of .children for a valid node, other 175 | than the root 176 | 177 | Definitions 178 | ----------- 179 | 180 | - The only user-visible node is the root node. 181 | - All leaf nodes are at the same height in the tree. 182 | - If the root node has exactly one child, the root node must be a leaf node. 183 | - Nodes never maintain references to their parents or siblings, only to 184 | their children. 185 | - Users call methods of the user-node, which may call methods of its 186 | children, who may call their children recursively. 187 | - A node's user-visible elements are numbered from 0 to self.n-1. These are 188 | called "positions". 189 | - A node's children are numbered 0 to len(self.children)-1. These are 190 | called "indexes" and should not be confused with positions. 191 | - If a user-visible function does not modify the BList, the BList's 192 | internal structure must not change. This is important for 193 | supporting iterators. 194 | - Functions exposed to the user must ensure these invariants are true 195 | when they return. 196 | - When a recursive function returns, the invariants must be true as if 197 | the child were a root node. 198 | - Completely private functions may temporarily violate these invariants. 199 | 200 | Reference Counters 201 | ------------------ 202 | 203 | In CPython, when Py_DECREF() decrements a reference counter to zero, 204 | completely arbitrary code may be executed by way of the garbage 205 | collector and __del__. For that reason, it's critical that any data 206 | structures are in a coherent state when Py_DECREF() is called. 207 | 208 | Toward that end, the BList implementation contains the helper 209 | functions, decref_later() and xdecref_later(). If an objects 210 | reference counter is greater than 1, decref_later() will decrement 211 | right away. Otherwise, it will append the object to a global list to 212 | be decremented just before control returns to the user. 213 | decref_later() must be used instead of Py_DECREF() any time a 214 | reference counter might be decreased to 0 within a BList function. 215 | 216 | decref_later() appends the reference to a global list. All of the 217 | references in the list are decremented when decref_flush() is called. 218 | decref_flush() is recursion-safe, so everything will work out properly 219 | if a __del__ method fired by decref_flush() calls other BList 220 | routines. 221 | 222 | decref_flush() must all be called by user-callable functions. It must 223 | not be called any function that might be called by other BList 224 | functions, as the caller may not expect the list to mutate in 225 | arbitrary ways via __del__. 226 | 227 | When we can prove that a reference counter is already greater than 1, 228 | use SAFE_DECREF() or SAFE_XDECREF(). When Py_DEBUG is defined, these 229 | macros will verify that the reference counter is greater than 1. 230 | 231 | Debugging 232 | --------- 233 | 234 | The BList implementation contains extensive debugging routines to 235 | verify the invariants, which only operate if Py_DEBUG is defined. 236 | Nearly ever function begins with a call like this: 237 | 238 | invariants(self, flags); 239 | 240 | where "flags" specifies that invariants that the function promises to 241 | maintain. 242 | 243 | When such a function returns, it must hand its return type and value 244 | to the verification routines. For example, a function that returns an 245 | integer would return as follows: 246 | 247 | return _int(some_value); 248 | 249 | The available flags are as follows: 250 | 251 | VALID_RW: 252 | This is a read-write function that may modify the BList. "self" 253 | must be a root node or have exactly one parent, both when the 254 | function starts and when it returns. 255 | 256 | VALID_PARENT: 257 | This is a function meant to be called by a parent on a child. 258 | "self" must maintain all the invariants, both when the function 259 | stars and it when it returns. 260 | 261 | VALID_ROOT: 262 | "self" must be a root node. The function must maintain all 263 | invariants. 264 | 265 | Implies VALID_PARENT. 266 | 267 | VALID_USER: 268 | This is a user-called function. "self" is a root node. "self" 269 | must maintain all the invariants, both when the function starts and 270 | it when it returns. 271 | 272 | Implies VALID_ROOT. 273 | 274 | VALID_OVERFLOW: 275 | The function may cause "self" to overflow. If so, the function 276 | creates a new right-hand sibling for "self" and returns it. If 277 | not, the return value is NULL. 278 | 279 | VALID_COLLAPSE: 280 | The function may cause "self" to underflow. If so, the function 281 | collapses the tree and returns a positive integer indicating the 282 | change in the height of the tree. If not, the function returns 0. 283 | 284 | VALID_DECREF: 285 | The function may call decref_flush(). It must not be called by 286 | any other BList functions. 287 | 288 | Requires VALID_USER. 289 | 290 | Whenever BList code calls a function that might execute arbitrary 291 | code, the call must be surrounded by the macros DANGER_BEGIN and 292 | DANGER END, like this: 293 | 294 | DANGER_BEGIN; 295 | cmp = PyObject_RichCompareBool(item, w->ob_item[i], Py_EQ); 296 | DANGER_END; 297 | 298 | The macros serve as useful visual aid to the programmer to remember 299 | that the code may modify the list in unexpected ways. Additionally, 300 | in debug mode they maintain internal state used by the validation 301 | code. 302 | 303 | Root Node Extensions 304 | -------------------- 305 | 306 | The data structure for BList root nodes contains additional fields to 307 | speed up certain operations. Specifically, the root node contains an 308 | index of the tree's leaf nodes, to speed up __getitem__ and 309 | __setitem__ operations (making them O(1) worst-case amortized time 310 | rather than O(log n)). 311 | 312 | The index is broken into INDEX_FACTOR elements, where INDEX_FACTOR <= 313 | HALF. 314 | 315 | index_list: 316 | An array of pointers to leaf nodes. index_list[i/INDEX_FACTOR] 317 | points to the leaf that contains position i, unless it is dirty. 318 | 319 | offset_list: 320 | An array of integers, corresponding to the entries in the 321 | index_list. offset_list[j] provides the position of the *first* 322 | child of index_list[j]. 323 | 324 | setclean_list: 325 | An array of bits, each bit corresponding to one entry in 326 | index_list. Each bit indicates whether an indexed leaf is ready 327 | for a __setitem__ operation. A leaf is ready iff the leaf and all 328 | of its ancestors are owned exclusively by one BList root (i.e., 329 | they have a reference count of 1). 330 | 331 | index_length: 332 | The size of the memory pointed to by index_list and offset_list. 333 | 334 | dirty: 335 | An array of integers representing a binary tree, indicating which 336 | parts of the index_list are valid and which are dirty. For some 337 | even integer, i, dirty[i] and dirty[i+1] are integers pointing to 338 | the children of node i. A negative values (CLEAN or DIRTY) 339 | indicates that there is no child. 340 | 341 | The binary tree corresponds with the index_list, as if the length 342 | of the index_list were rounded up to the nearest power of two. 343 | For example, if the root of the tree is DIRTY then the whole 344 | index_list is dirty. If the index_list has a length of 8 and the 345 | root points to CLEAN on the left and DIRTY on the right, then the 346 | first 4 indexes are clean and the second 4 are dirty. 347 | 348 | dirty_length: 349 | The size of the memory pointed to by dirty. 350 | 351 | dirty_root: 352 | An integer pointing to the root node of dirty, or a negative value 353 | (CLEAN_RW, CLEAN, or DIRTY). 354 | 355 | free_root: 356 | Another integer pointer into dirty. free_root points to an entry 357 | that is not currently in use to indicate clean/dirty status. 358 | Instead, the entry forms a binary tree of other entries that are 359 | not currently in use. The free list allows entries for the dirty 360 | tree to be allocated quickly without malloc/free. 361 | 362 | last_n: 363 | The length of the BList object when the index was last set to all 364 | dirty. last_n is used only for debugging purposes. 365 | 366 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | blist: an asymptotically faster list-like type for Python 2 | ========================================================= 3 | 4 | .. currentmodule:: blist 5 | 6 | The :class:`blist` is a drop-in replacement for the Python list the 7 | provides better performance when modifying large lists. The blist 8 | package also provides :class:`sortedlist`, :class:`sortedset`, 9 | :class:`weaksortedlist`, :class:`weaksortedset`, :class:`sorteddict`, 10 | and :class:`btuple` types. 11 | 12 | Documentations contents: 13 | 14 | .. toctree:: 15 | :maxdepth: 1 16 | 17 | blist.rst 18 | btuple.rst 19 | sorteddict.rst 20 | sortedlist.rst 21 | sortedset.rst 22 | weaksortedlist.rst 23 | weaksortedset.rst 24 | implementation.rst 25 | 26 | Other resources: 27 | 28 | * `Download `_ 29 | * `Browse source code `_ 30 | * `Issue tracker `_ 31 | * `Performance analysis `_ 32 | 33 | Indices and tables 34 | ================== 35 | 36 | * :ref:`genindex` 37 | * :ref:`search` 38 | 39 | -------------------------------------------------------------------------------- /doc/mymath.txt: -------------------------------------------------------------------------------- 1 | .. include:: 2 | 3 | .. |theta(n)| replace:: :math:`\Theta\left(n\right)` 4 | .. |theta(log n)| replace:: :math:`\Theta\left(\log n\right)` 5 | .. |theta(log m)| replace:: :math:`\Theta\left(\log m\right)` 6 | .. |theta(n log n)| replace:: :math:`\Theta\left(n \log n\right)` 7 | .. |theta(log k)| replace:: :math:`\Theta\left(\log k\right)` 8 | .. |theta(1)| replace:: :math:`\Theta\left(1\right)` 9 | .. |theta(log**2 n)| replace:: :math:`\Theta\left(\log^2 n\right)` 10 | .. |theta(log**2 m)| replace:: :math:`\Theta\left(\log^2 m\right)` 11 | .. |theta(m log**2 n)| replace:: :math:`\Theta\left(m \log^2 n\right)` 12 | .. |theta(m log n)| replace:: :math:`\Theta\left(m \log n\right)` 13 | .. |theta(log m + log n)| replace:: :math:`\Theta\left(\log m + \log n\right)` 14 | .. |theta(m + log n)| replace:: :math:`\Theta\left(m + \log n\right)` 15 | .. |theta(stop-start)| replace:: :math:`\Theta\left(\textrm{start}-\textrm{stop}\right)` 16 | .. |theta(n log(k + n))| replace:: :math:`\Theta\left(n \log\left(k + 17 | n\right)\right)` 18 | .. |theta(m log**2(n + m))| replace:: :math:`\Theta\left(m \log^2 19 | \left(n + m\right)\right)` 20 | .. |theta(m log(n + m))| replace:: :math:`\Theta\left(m \log\left(n + m\right)\right)` 21 | .. |theta(j - i + log n)| replace:: :math:`\Theta\left(j - i + \log n\right)` 22 | -------------------------------------------------------------------------------- /doc/sorteddict.rst: -------------------------------------------------------------------------------- 1 | .. include:: mymath.txt 2 | 3 | sorteddict 4 | ========== 5 | 6 | .. currentmodule:: blist 7 | 8 | .. class:: sorteddict([key,] [arg,] **kw) 9 | 10 | A :class:`sorteddict` provides the same methods as a :class:`dict`. 11 | Additionally, a :class:`sorteddict` efficiently maintains its keys 12 | in sorted order. Consequently, the :ref:`keys ` 13 | method will return the keys in sorted order, the :ref:`popitem 14 | ` method will remove the item with the lowest 15 | key, etc. 16 | 17 | *key* specifies a function of one argument that is used to extract 18 | a comparison sort key from each dict key, 19 | e.g. ``sorteddict(str.lower)``. If no function is specified, the 20 | default compares the elements directly. The *key* function must 21 | always return the same key for an item or the results are 22 | unpredictable. The *key* argument must be provided as a positional 23 | argument; if provided as a keyword argument, it will be added to 24 | the dictionary instead. 25 | 26 | An optional *iterable* provides an initial series of items to 27 | populate the :class:`sorteddict`. Each item in the series must 28 | itself contain to items. The first is used as a key in the new 29 | dictionary, and the second as the key's value. If a given key is 30 | seen more than once, the last value associated with it is retained 31 | in the new dictionary. 32 | 33 | If keyword arguments are given, the keywords themselves with their 34 | associated values are added as items to the dictionary. If a key is 35 | specified both in the positional argument and as a keyword argument, 36 | the value associated with the keyword is retained in the 37 | dictionary. For example, these all return a dictionary equal to 38 | ``{"one": 2, "two": 3}``: 39 | 40 | * ``sorteddict(one=2, two=3)`` 41 | * ``sorteddict({'one': 2, 'two': 3})`` 42 | * ``sorteddict(zip(('one', 'two'), (2, 3)))`` 43 | * ``sorteddict([['two', 3], ['one', 2]])`` 44 | 45 | The first example only works for keys that are valid Python 46 | identifiers; the others work with any valid keys. 47 | 48 | .. method:: x in d 49 | 50 | Returns True if and only if *x* is a key in the dictionary. 51 | 52 | Requires |theta(1)| operations in the average case. 53 | 54 | :rtype: :class:`bool` 55 | 56 | .. _sorteddict.delitem: 57 | .. method:: del d[key] 58 | 59 | Remove ``d[key]`` from *d*. Raises a :exc:`KeyError` if *key* 60 | is not in the dictionary. 61 | 62 | Requires |theta(log n)| comparisons. 63 | 64 | .. method:: d[key] 65 | 66 | Return the item of *d* with key *key*. Raises a :exc:`KeyError` 67 | if *key* is not in the dictionary. 68 | 69 | If a subclass of dict defines a method :meth:`__missing__`, if 70 | the key *key* is not present, the ``d[key]`` operation calls 71 | that method with the key *key* as argument. The ``d[key]`` 72 | operation then returns or raises whatever is returned or raised 73 | by the ``__missing__(key)`` call if the key is not present. No 74 | other operations or methods invoke :meth:`__missing__`. If 75 | :meth:`__missing__` is not defined, :exc:`KeyError` is raised. 76 | :meth:`__missing__` must be a method; it cannot be an instance 77 | variable. 78 | 79 | Requires |theta(1)| operations in the average case. 80 | 81 | :rtype: value 82 | 83 | .. method:: L == L2, L != L2 84 | 85 | Test two dictionaries for equality (or inequality). 86 | Dictionaries compare equal if and only if they have the same 87 | length, if all of the keys of *L* may be found in *L2*, and all 88 | of the corresponding values compare equal. 89 | 90 | In the worst case, requires |theta(n)| operations and |theta(n)| 91 | comparisons. 92 | 93 | :rtype: :class:`bool` 94 | 95 | .. method:: iter(d) 96 | 97 | Creates an iterator over the sorted keys of the dictionary. 98 | 99 | Requires |theta(log n)| operations to create the iterator. Each 100 | element from the iterator requires |theta(1)| operations to 101 | retrieve, or |theta(n)| operations to iterate over the entire 102 | dictionary. 103 | 104 | :rtype: iterator 105 | 106 | .. method:: len(d) 107 | 108 | Returns the number of (key, value) pairs in the dictionary. 109 | 110 | Requires |theta(1)| operations. 111 | 112 | :rtype: :class:`int` 113 | 114 | .. method:: d[key] = value 115 | 116 | Sets `d[key]` to *value*. 117 | 118 | In the average case, requires |theta(log**2 n)| operations and 119 | |theta(log n)| comparisons. 120 | 121 | .. method:: d.clear() 122 | 123 | Remove all elements from the dictionary. 124 | 125 | Requires |theta(n)| total operations in the worst case. 126 | 127 | .. method:: d.copy() 128 | 129 | Creates a shallow copy of the dictionary. 130 | 131 | Requires |theta(n)| total operations and no comparisons. 132 | 133 | :rtype: :class:`sorteddict` 134 | 135 | .. method:: d.fromkeys(seq[, value[, key]]) 136 | 137 | Create a new dictionary with keys from *seq* and values set to 138 | *value*. *key* specifies a function of one argument that is 139 | used to extract a comparison sort key from each dict key. 140 | 141 | :meth:`fromkeys` is a class method that returns a new 142 | dictionary. *value* defaults to ``None``. 143 | 144 | :rtype: :class:`sorteddict` 145 | 146 | .. method:: d.get(key[, default]) 147 | 148 | Return the value for *key* if *key* is in the dictionary, else 149 | *default*. If *default* is not given, it defaults to ``None``, 150 | so that this method never raises a :exc:`KeyError`. 151 | 152 | Requires |theta(1)| total operations in the average case. 153 | 154 | :rtype: value 155 | 156 | .. method:: d.items() 157 | 158 | In Python 2, returns a blist of the dictionary's items (``(key, 159 | value)`` pairs). 160 | 161 | In Python 3, returns a new :class:`ItemsView` of the dictionary's 162 | items. In addition to the methods provided by the built-in `view 163 | `_, 164 | the :class:`ItemsView` is indexable (e.g., ``d.items()[5]``). 165 | 166 | Requires |theta(n)| operations. 167 | 168 | :rtype: :class:`blist` or :class:`ItemsView` 169 | 170 | .. _sorteddict.keys: 171 | .. method:: d.keys() 172 | 173 | In Python 2, returns a :class:`sortedset` of the dictionary's keys. 174 | 175 | In Python 3, returns a new :class:`KeysView` of the dictionary's 176 | keys. In addition to the methods provided by the built-in `view 177 | `_, 178 | the :class:`KeysView` is indexable (e.g., ``d.keys()[5]``). 179 | 180 | Requires |theta(1)| operations. 181 | 182 | :rtype: :class:`sortedset` or :class:`KeysView` 183 | 184 | .. method:: d.pop(key[, default]) 185 | 186 | If *key* is in the dictionary, remove it and return its value, 187 | else return *default*. If *default* is not given and *key* is not in 188 | the dictionary, a :exc:`KeyError` is raised. 189 | 190 | Requires |theta(1)| operations if *key* is not in the 191 | dictionary. Otherwise, requires |theta(log n)| comparisons. 192 | 193 | :rtype: value 194 | 195 | .. _sorteddict.popitem: 196 | .. method:: d.popitem() 197 | 198 | Remove and return the ``(key, value)`` pair with the least *key* 199 | from the dictionary. 200 | 201 | If the dictionary is empty, calling :meth:`popitem` raises a 202 | :exc:`KeyError`. 203 | 204 | Requires |theta(log n)| operations and no comparisons. 205 | 206 | :rtype: ``(key, value)`` tuple 207 | 208 | .. method:: d.setdefault(key[, default]) 209 | 210 | If *key* is in the dictionary, return its value. If not, 211 | insert *key* with a value of *default* and return 212 | *default*. *default* defaults to ``None``. 213 | 214 | Requires |theta(1)| operations if *key* is already in the 215 | dictionary. Otherwise, requires |theta(log n)| comparisons. 216 | 217 | .. method:: d.update(other, ...) 218 | 219 | Update the dictionary with the key/value pairs from *other*, 220 | overwriting existing keys. 221 | 222 | :meth:`update` accepts either another dictionary object or an 223 | iterable of key/value pairs (as a tuple or other iterable of 224 | length two). If keyword arguments are specified, the dictionary 225 | is then updated with those key/value pairs: ``d.update(red=1, 226 | blue=2)``. 227 | 228 | In the average case, requires |theta(m log**2(n + m))| operations 229 | and |theta(m log(n + m))| comparisons, where *m* is the combined 230 | size of all the other sets and *n* is the initial size of *d*. 231 | 232 | .. method:: d.values() 233 | 234 | In Python 2, returns a blist of the dictionary's values. 235 | 236 | In Python 3, returns a new :class:`ValuesView` of the dictionary's 237 | values. In addition to the methods provided by the built-in `view 238 | `_, 239 | the :class:`ValuesView` is indexable (e.g., ``d.values()[5]``). 240 | 241 | Requires |theta(n)| operations. 242 | 243 | :rtype: :class:`blist` or :class:`ValuesView` 244 | 245 | .. class:: KeysView 246 | 247 | A KeysView object is a dynamic view of the dictionary's keys, which 248 | means that when the dictionary's keys change, the view reflects 249 | those changes. 250 | 251 | The KeysView class implements the Set_ and 252 | Sequence_ Abstract Base Classes. 253 | 254 | .. method:: len(keysview) 255 | 256 | Returns the number of entries in the dictionary. 257 | 258 | Requires |theta(1)| operations. 259 | 260 | :rtype: :class:`int` 261 | 262 | .. method:: iter(keysview) 263 | 264 | Returns an iterator over the keys in the dictionary. Keys are 265 | iterated over in their sorted order. 266 | 267 | Iterating views while adding or deleting entries in the dictionary 268 | may raise a :exc:`RuntimeError` or fail to iterate over all 269 | entries. 270 | 271 | Requires |theta(log n)| operations to create the iterator. Each 272 | element from the iterator requires |theta(1)| operations to 273 | retrieve, or |theta(n)| operations to iterate over the entire 274 | dictionary. 275 | 276 | :rtype: iterator 277 | 278 | .. method:: x in keysview 279 | 280 | Returns ``True`` iff *x* is one of the underlying dictionary's 281 | keys. 282 | 283 | Requires |theta(1)| operations. 284 | 285 | :rtype: :class:`bool` 286 | 287 | .. method:: keysview[i] 288 | 289 | Returns the key at position *i*. 290 | 291 | Requires |theta(1)| operations if the dictionary's size has not 292 | been changed recently. Otherwise, requires |theta(log n)| 293 | operations. 294 | 295 | :rtype: value 296 | 297 | .. method:: keysview & other 298 | 299 | Returns the intersection of the keys and the other object as 300 | a new set. 301 | 302 | Requires |theta(m log**2(n + m))| operations and |theta(m 303 | log(n + m))| comparisons, where *m* is the size of *other* and 304 | *n* is the size of *keysview*. 305 | 306 | :rtype: :class:`sortedset` 307 | 308 | .. method:: keysview | other 309 | 310 | Returns the union of the keys and the other object as a new set. 311 | 312 | Requires |theta(m log**2(n + m))| operations or |theta(m log(n + 313 | m))| comparisons, where *m* is the size of *other* 314 | and *n* is the size of *S*. 315 | 316 | :rtype: :class:`sortedset` 317 | 318 | .. describe:: keysview - other 319 | 320 | Return the difference between the keys and the other object (all 321 | keys that aren't in *other*) as a new set. 322 | 323 | Requires |theta(m log**2(n + m))| operations and |theta(m 324 | log(n + m))| comparisons, where *m* is the size of *other* and 325 | *n* is the size of *keysview*. 326 | 327 | :rtype: :class:`sortedset` 328 | 329 | .. describe:: keysview ^ other 330 | 331 | Return the symmetric difference (all elements either in the keys 332 | or *other*, but not in both) of the keys and the other object as 333 | a new set. 334 | 335 | Requires |theta(m log**2(n + m))| operations and |theta(m 336 | log(n + m))| comparisons, where *m* is the size of *other* and 337 | *n* is the size of *keysview*. 338 | 339 | :rtype: :class:`sortedset` 340 | 341 | .. _sortedlist.bisect_left: 342 | .. method:: L.bisect_left(value) 343 | 344 | Similarly to the ``bisect`` module in the standard library, this 345 | returns an appropriate index to insert *value* in *L*. If *value* is 346 | already present in *L*, the insertion point will be before (to the 347 | left of) any existing entries. 348 | 349 | Requires |theta(log**2 n)| total operations or |theta(log n)| 350 | comparisons. 351 | 352 | .. method:: L.bisect(value) 353 | 354 | Same as :ref:`bisect_left `. 355 | 356 | .. method:: L.bisect_right(value) 357 | 358 | Same thing as :ref:`bisect_left `, but if 359 | *value* is already present in *L*, the insertion point will be after 360 | (to the right of) any existing entries. 361 | 362 | .. method:: keysview.count(key) 363 | 364 | Returns the number of occurrences of *key* in the set. 365 | 366 | Requires |theta(1)| operations. 367 | 368 | :rtype: :class:`int` 369 | 370 | .. method:: keysview.index(key, [start, [stop]]) 371 | 372 | Returns the smallest *k* such that :math:`keysview[k] == x` and 373 | :math:`i <= k < j`. Raises :exc:`KeyError` if *key* is not 374 | present. *stop* defaults to the end of the set. *start* 375 | defaults to the beginning. Negative indexes are supported, as 376 | for slice indices. 377 | 378 | Requires |theta(1)| operations. 379 | 380 | :rtype: :class:`int` 381 | 382 | .. class:: ValuesView 383 | 384 | A ValuesView object is a dynamic view of the dictionary's values, 385 | which means that when the dictionary's values change, the view 386 | reflects those changes. 387 | 388 | The ValuesView class implements the Sequence_ Abstract Base 389 | Class. 390 | 391 | .. method:: len(valuesview) 392 | 393 | Returns the number of entries in the dictionary. 394 | 395 | Requires |theta(1)| operations. 396 | 397 | :rtype: :class:`int` 398 | 399 | .. method:: iter(valuesview) 400 | 401 | Returns an iterator over the values in the dictionary. Values are 402 | iterated over in sorted order of the keys. 403 | 404 | Iterating views while adding or deleting entries in the dictionary 405 | may raise a :exc:`RuntimeError` or fail to iterate over all 406 | entries. 407 | 408 | Requires |theta(log n)| operations to create the iterator. Each 409 | element from the iterator requires |theta(1)| operations to 410 | retrieve, or |theta(n)| operations to iterate over the entire 411 | dictionary. 412 | 413 | :rtype: iterator 414 | 415 | .. method:: x in valuesview 416 | 417 | Returns ``True`` iff *x* is one of the underlying dictionary's 418 | values. 419 | 420 | Requires |theta(n)| operations. 421 | 422 | :rtype: :class:`bool` 423 | 424 | .. method:: valuesview[i] 425 | 426 | Returns the value at position *i*. 427 | 428 | Requires |theta(1)| operations if the dictionary's size has not 429 | been changed recently. Otherwise, requires |theta(log n)| 430 | operations. 431 | 432 | :rtype: value 433 | 434 | .. method:: valuesview.count(value) 435 | 436 | Returns the number of occurrences of *value* in the set. 437 | 438 | Requires |theta(n)| operations and |theta(n)| comparisons in the 439 | worst case. 440 | 441 | :rtype: :class:`int` 442 | 443 | .. method:: valuesview.index(value, [start, [stop]]) 444 | 445 | Returns the smallest *k* such that :math:`valuesview[k] == x` 446 | and :math:`i <= k < j`. Raises :exc:`KeyError` if *value* is not 447 | present. *stop* defaults to the end of the set. *start* 448 | defaults to the beginning. Negative indexes are supported, as 449 | for slice indices. 450 | 451 | In the worst case, requires |theta(stop-start)| operations and 452 | |theta(stop-start)| comparisons. 453 | 454 | :rtype: :class:`int` 455 | 456 | .. class:: ItemsView 457 | 458 | A ItemsView object is a dynamic view of the dictionary's ``(key, 459 | value)`` pairs, which means that when the dictionary changes, the 460 | view reflects those changes. 461 | 462 | The ItemsView class implements the Set_ and 463 | Sequence_ Abstract Base Classes. However, the set-like 464 | operations (``&``, ``|``, ``-``, ``^``) will only operate correctly 465 | if all of the dictionary's values are hashable. 466 | 467 | .. method:: len(itemsview) 468 | 469 | Returns the number of entries in the dictionary. 470 | 471 | Requires |theta(1)| operations. 472 | 473 | :rtype: :class:`int` 474 | 475 | .. method:: iter(itemsview) 476 | 477 | Returns an iterator over the items in the dictionary. Items are 478 | iterated over in sorted order of the keys. 479 | 480 | Iterating views while adding or deleting entries in the dictionary 481 | may raise a :exc:`RuntimeError` or fail to iterate over all 482 | entries. 483 | 484 | Requires |theta(log n)| operations to create the iterator. Each 485 | element from the iterator requires |theta(1)| operations to 486 | retrieve, or |theta(n)| operations to iterate over the entire 487 | dictionary. 488 | 489 | :rtype: iterator 490 | 491 | .. method:: x in itemsview 492 | 493 | Returns ``True`` iff *x* is one of the underlying dictionary's 494 | items. 495 | 496 | Requires |theta(1)| operations. 497 | 498 | :rtype: :class:`bool` 499 | 500 | .. method:: itemsview[i] 501 | 502 | Returns the ``(key, value)`` pair at position *i*. 503 | 504 | Requires |theta(1)| operations if the dictionary's size has not 505 | been changed recently. Otherwise, requires |theta(log n)| 506 | operations. 507 | 508 | :rtype: item 509 | 510 | .. method:: itemsview & other 511 | 512 | Returns the intersection of the items and the other object as 513 | a new set. 514 | 515 | Requires |theta(m log**2(n + m))| operations and |theta(m 516 | log(n + m))| comparisons, where *m* is the size of *other* and 517 | *n* is the size of *itemsview*. 518 | 519 | :rtype: :class:`sortedset` 520 | 521 | .. method:: itemsview | other 522 | 523 | Returns the union of the items and the other object as a new set. 524 | 525 | Requires |theta(m log**2(n + m))| operations or |theta(m log(n + 526 | m))| comparisons, where *m* is the size of *other* 527 | and *n* is the size of *S*. 528 | 529 | :rtype: :class:`sortedset` 530 | 531 | .. describe:: itemsview - other 532 | 533 | Return the difference between the items and the other object (all 534 | items that aren't in *other*) as a new set. 535 | 536 | Requires |theta(m log**2(n + m))| operations and |theta(m 537 | log(n + m))| comparisons, where *m* is the size of *other* and 538 | *n* is the size of *itemsview*. 539 | 540 | :rtype: :class:`sortedset` 541 | 542 | .. describe:: itemsview ^ other 543 | 544 | Return the symmetric difference (all elements either in the items 545 | or *other*, but not in both) of the items and the other object as 546 | a new set. 547 | 548 | Requires |theta(m log**2(n + m))| operations and |theta(m 549 | log(n + m))| comparisons, where *m* is the size of *other* and 550 | *n* is the size of *itemsview*. 551 | 552 | :rtype: :class:`sortedset` 553 | 554 | .. method:: itemsview.count(item) 555 | 556 | Returns the number of occurrences of *item* in the set. 557 | 558 | Requires |theta(1)| operations. 559 | 560 | :rtype: :class:`int` 561 | 562 | .. method:: itemsview.index(item, [start, [stop]]) 563 | 564 | Returns the smallest *k* such that :math:`itemsview[k] == x` and 565 | :math:`i <= k < j`. Raises :exc:`KeyError` if *item* is not 566 | present. *stop* defaults to the end of the set. *start* 567 | defaults to the beginning. Negative indexes are supported, as 568 | for slice indices. 569 | 570 | Requires |theta(1)| operations. 571 | 572 | :rtype: :class:`int` 573 | 574 | .. _Set: http://docs.python.org/release/3.1/library/collections.html#abcs-abstract-base-classes 575 | .. _Sequence: http://docs.python.org/release/3.1/library/collections.html#abcs-abstract-base-classes 576 | -------------------------------------------------------------------------------- /doc/sortedlist.rst: -------------------------------------------------------------------------------- 1 | .. include:: mymath.txt 2 | 3 | sortedlist 4 | ========== 5 | 6 | .. currentmodule:: blist 7 | 8 | .. class:: sortedlist(iterable=(), key=None) 9 | 10 | A :class:`sortedlist` provides most of the same methods as a 11 | :class:`blist` (and, hence, a :class:`list`), but keeps the items 12 | in sorted order. Methods that would insert an item at a particular 13 | location are not included (e.g., :ref:`append `, 14 | :ref:`insert `). To add an element to the sortedlist, use 15 | :ref:`add `. To add several elements, use 16 | :ref:`update `. To remove an element, use 17 | :ref:`discard `, :ref:`remove `, or 18 | :ref:`del L[i] `. 19 | 20 | An optional *iterable* provides an initial series of items to 21 | populate the :class:`sortedlist`. 22 | 23 | *key* specifies a function of one argument that is used to extract 24 | a comparison key from each list element: ``key=str.lower``. The 25 | default value is ``None`` (compare the elements directly). The 26 | *key* function must always return the same key for an item or the 27 | results are unpredictable. 28 | 29 | A :class:`sortedlist` can be used as an order statistic tree 30 | (Cormen *et al.*, *Introduction to Algorithms*, ch. 14) 31 | that allows duplicate keys. 32 | 33 | .. method:: x in L 34 | 35 | Returns True if and only if *x* is an element in the list. 36 | 37 | Requires |theta(log**2 n)| total operations or |theta(log n)| 38 | comparisons. 39 | 40 | :rtype: :class:`bool` 41 | 42 | .. _sortedlist.delitem: 43 | .. method:: del L[i] 44 | 45 | Removes the element located at index *i* from the list. 46 | 47 | Requires |theta(log n)| operations and no comparisons. 48 | 49 | .. method:: del L[i:j] 50 | 51 | Removes the elements from *i* to *j* from the list. 52 | 53 | Requires |theta(log n)| operations and no comparisons. 54 | 55 | .. method:: L == L2, L != L2, L < L2, L <= L2, L > L2, L >= L2 56 | 57 | Compares two lists. For full details see `Comparisons 58 | `_ in 59 | the Python language reference. 60 | 61 | In the worst case, requires |theta(n)| operations and |theta(n)| 62 | comparisons. 63 | 64 | :rtype: :class:`bool` 65 | 66 | .. method:: L[i] 67 | 68 | Returns the element at position *i*. 69 | 70 | Requires |theta(log n)| operations in the worst case but only 71 | |theta(1)| operations if the list's size has not been changed 72 | recently. Requires no comparisons in any case. 73 | 74 | (Cormen *et al.* call this operation "SELECT".) 75 | 76 | :rtype: item 77 | 78 | .. method:: L[i:j] 79 | 80 | Returns a new :class:`sortedlist` containing the elements from 81 | *i* to *j*. 82 | 83 | Requires |theta(log n)| operations and no comparisons. 84 | 85 | :rtype: :class:`sortedlist` 86 | 87 | .. method:: L *= k 88 | 89 | Increase the length of the list by a factor of *k*, by inserting 90 | *k-1* additional shallow copies of each item in the list. 91 | 92 | Requires |theta(n log(k + n))| operations and no comparisons. 93 | 94 | .. method:: iter(L) 95 | 96 | Creates an iterator over the list. 97 | 98 | Requires |theta(log n)| operations to create the iterator. Each 99 | element from the iterator requires |theta(1)| operations to 100 | retrieve, or |theta(n)| operations to iterate over the entire 101 | list. 102 | 103 | :rtype: iterator 104 | 105 | .. method:: len(L) 106 | 107 | Returns the number of elements in the list. 108 | 109 | Requires |theta(1)| operations. 110 | 111 | :rtype: :class:`int` 112 | 113 | .. method:: L * k or k * L 114 | 115 | Returns a new sorted list containing *k* shallow copies of each 116 | item in L. 117 | 118 | Requires |theta(n log(k + n))| operations and no comparisons. 119 | 120 | :rtype: :class:`sortedlist` 121 | 122 | .. method:: reversed(L) 123 | 124 | Creates an iterator to traverse the list in reverse. 125 | 126 | Requires |theta(log n)| operations to create the iterator. Each 127 | element from the iterator requires |theta(1)| operations to 128 | retrieve, or |theta(n)| operations to iterate over the entire 129 | list. Requires no comparisons in any case. 130 | 131 | :rtype: iterator 132 | 133 | .. method:: L[i] = x 134 | 135 | Replace the item at position *i* of *L* with *x*. 136 | 137 | Requires |theta(log n)| operations in the worst case but only 138 | |theta(1)| operations if the list's size has not been changed 139 | recently. Requires no comparisons in any case. 140 | 141 | .. method:: L[i:j] = iterable 142 | 143 | Replace the items at positions *i* through *j* with the contents 144 | of *iterable*. 145 | 146 | Requires |theta(j - i + log n)| operations and no comparisons. 147 | 148 | .. _sortedlist.add: 149 | .. method:: L.add(value) 150 | 151 | Add the element *value* to the list. 152 | 153 | Requires |theta(log**2 n)| total operations or |theta(log n)| 154 | comparisons. 155 | 156 | .. _sortedlist.bisect_left: 157 | .. method:: L.bisect_left(value) 158 | 159 | Similarly to the ``bisect`` module in the standard library, this 160 | returns an appropriate index to insert *value* in *L*. If *value* is 161 | already present in *L*, the insertion point will be before (to the 162 | left of) any existing entries. 163 | 164 | Requires |theta(log**2 n)| total operations or |theta(log n)| 165 | comparisons. 166 | 167 | .. method:: L.bisect(value) 168 | 169 | Same as :ref:`bisect_left `. 170 | 171 | .. _sortedlist.bisect_right: 172 | .. method:: L.bisect_right(value) 173 | 174 | Same thing as :ref:`bisect_left `, but if 175 | *value* is already present in *L*, the insertion point will be after 176 | (to the right of) any existing entries. 177 | 178 | .. method:: L.count(value) 179 | 180 | Returns the number of occurrences of *value* in the list. 181 | 182 | Requires |theta(n)| operations and |theta(n)| comparisons in the 183 | worst case. 184 | 185 | :rtype: :class:`int` 186 | 187 | .. _sortedlist.discard: 188 | .. method:: L.discard(value) 189 | 190 | Removes the first occurrence of *value*. If *value* is not a 191 | member, does nothing. 192 | 193 | In the worst case, requires |theta(log**2 n)| operations and 194 | |theta(log n)| comparisons. 195 | 196 | .. method:: L.index(value, [start, [stop]]) 197 | 198 | Returns the smallest *k* such that :math:`L[k] == x` and 199 | :math:`i <= k < j`. Raises ValueError if *value* is not 200 | present. *stop* defaults to the end of the list. *start* 201 | defaults to the beginning. Negative indexes are supported, as 202 | for slice indices. 203 | 204 | In the worst case, requires |theta(log**2 m)| operations and 205 | |theta(log m)| comparisons, where *m* is *stop* - *start*. 206 | 207 | (Cormen *et al.* call this operation "RANK".) 208 | 209 | :rtype: :class:`int` 210 | 211 | .. method:: L.pop([index]) 212 | 213 | Removes and return item at index (default last). Raises 214 | IndexError if list is empty or index is out of range. Negative 215 | indexes are supported, as for slice indices. 216 | 217 | Requires |theta(log n)| operations and no comparisons. 218 | 219 | :rtype: item 220 | 221 | .. _sortedlist.remove: 222 | .. method:: L.remove(value) 223 | 224 | Remove first occurrence of *value*. Raises ValueError if 225 | *value* is not present. 226 | 227 | In the worst case, requires |theta(log**2 n)| operations and 228 | |theta(log n)| comparisons. 229 | 230 | .. _sortedlist.update: 231 | .. method:: L.update(iterable) 232 | 233 | Grow the list by inserting all elements from the iterable. 234 | 235 | Requires |theta(m log**2(n + m))| operations and |theta(m log(n 236 | + m))| comparisons, where *m* is the size of the iterable and *n* is 237 | the size of the list initially. 238 | -------------------------------------------------------------------------------- /doc/sortedset.rst: -------------------------------------------------------------------------------- 1 | .. include:: mymath.txt 2 | 3 | sortedset 4 | ========== 5 | 6 | .. currentmodule:: blist 7 | 8 | .. class:: sortedset(iterable=(), key=None) 9 | 10 | A :class:`sortedset` provides the same methods as a :class:`set`. 11 | Additionally, a :class:`sortedset:` maintains its items in sorted 12 | order, allowing the :class:`sortedset` to be indexed. 13 | 14 | An optional *iterable* provides an initial series of items to 15 | populate the :class:`sortedset`. 16 | 17 | *key* specifies a function of one argument that is used to extract 18 | a comparison key from each set element: ``key=str.lower``. The 19 | default value is ``None`` (compare the elements directly). The 20 | *key* function must always return the same key for an item or the 21 | results are unpredictable. 22 | 23 | Unlike a :class:`set`, a :class:`sortedset` does not require items 24 | to be hashable. 25 | 26 | A :class:`sortedset` can be used as an order statistic tree 27 | (Cormen *et al.*, *Introduction to Algorithms*, ch. 14). 28 | 29 | .. method:: x in S 30 | 31 | Returns True if and only if *x* is an element in the set. 32 | 33 | Requires |theta(log**2 n)| total operations or |theta(log n)| 34 | comparisons. 35 | 36 | :rtype: :class:`bool` 37 | 38 | .. _sortedset.delitem: 39 | .. method:: del S[i] 40 | 41 | Removes the element located at index *i* from the set. 42 | 43 | Requires |theta(log n)| operations and no comparisons. 44 | 45 | .. method:: del S[i:j] 46 | 47 | Removes the elements from *i* to *j* from the set. 48 | 49 | Requires |theta(log n)| operations and no comparisons. 50 | 51 | .. method:: S < S2 52 | 53 | Test whether the set is a proper subset of *S2*, that is, ``S <= S2 54 | and S != other``. 55 | 56 | In the worst case, requires |theta(n)| operations multiplied by 57 | the cost of *S2*'s `in` operation. 58 | 59 | :rtype: :class:`bool` 60 | 61 | .. method:: S > S2 62 | 63 | Test whether the set is a proper superset of *S2*, that is, ``S 64 | >= S2 and S != S2``. 65 | 66 | In the worst case, requires |theta(m log**2 n)| operations or 67 | |theta(m log n)| comparisons, where *m* is the size of *S2* and 68 | *n* is the size of *S*. 69 | 70 | :rtype: :class:`bool` 71 | 72 | .. method:: S[i] 73 | 74 | Returns the element at position *i*. 75 | 76 | Requires |theta(log n)| operations in the worst case but only 77 | |theta(1)| operations if the set's size has not been changed 78 | recently. Requires no comparisons in any case. 79 | 80 | (Cormen *et al.* call this operation "SELECT".) 81 | 82 | :rtype: item 83 | 84 | .. method:: S[i:j] 85 | 86 | Returns a new sortedset containing the elements from *i* to *j*. 87 | 88 | Requires |theta(log n)| operations and no comparisons. 89 | 90 | :rtype: :class:`sortedset` 91 | 92 | .. method:: S *= k 93 | 94 | Increase the length of the set by a factor of *k*, by inserting 95 | *k-1* additional shallow copies of each item in the set. 96 | 97 | Requires |theta(n log(k + n))| operations and no comparisons. 98 | 99 | .. method:: iter(S) 100 | 101 | Creates an iterator over the set. 102 | 103 | Requires |theta(log n)| operations to create the iterator. Each 104 | element from the iterator requires |theta(1)| operations to 105 | retrieve, or |theta(n)| operations to iterate over the entire 106 | set. 107 | 108 | :rtype: iterator 109 | 110 | .. method:: len(S) 111 | 112 | Returns the number of elements in the set. 113 | 114 | Requires |theta(1)| operations. 115 | 116 | :rtype: :class:`int` 117 | 118 | .. method:: S * k or k * S 119 | 120 | Returns a new sorted set containing *k* shallow copies of each 121 | item in S. 122 | 123 | Requires |theta(n log(k + n))| operations and no comparisons. 124 | 125 | :rtype: :class:`sortedset` 126 | 127 | .. method:: reversed(S) 128 | 129 | Creates an iterator to traverse the set in reverse. 130 | 131 | Requires |theta(log n)| operations to create the iterator. Each 132 | element from the iterator requires |theta(1)| operations to 133 | retrieve, or |theta(n)| operations to iterate over the entire 134 | set. Requires no comparisons in any case. 135 | 136 | :rtype: iterator 137 | 138 | .. _sortedset.add: 139 | .. method:: S.add(value) 140 | 141 | Add the element *value* to the set. 142 | 143 | Requires |theta(log**2 n)| total operations or |theta(log n)| 144 | comparisons. 145 | 146 | .. _sortedlist.bisect_left: 147 | .. method:: L.bisect_left(value) 148 | 149 | Similarly to the ``bisect`` module in the standard library, this 150 | returns an appropriate index to insert *value* in *L*. If *value* is 151 | already present in *L*, the insertion point will be before (to the 152 | left of) any existing entries. 153 | 154 | Requires |theta(log**2 n)| total operations or |theta(log n)| 155 | comparisons. 156 | 157 | .. method:: L.bisect(value) 158 | 159 | Same as :ref:`bisect_left `. 160 | 161 | .. method:: L.bisect_right(value) 162 | 163 | Same thing as :ref:`bisect_left `, but if 164 | *value* is already present in *L*, the insertion point will be after 165 | (to the right of) any existing entries. 166 | 167 | .. method:: S.clear() 168 | 169 | Remove all elements from the set. 170 | 171 | Requires |theta(n)| total operations and no comparisons in the 172 | worst case. 173 | 174 | .. method:: S.copy() 175 | 176 | Creates a shallow copy of the set. 177 | 178 | Requires |theta(1)| total operations and no comparisons. 179 | 180 | :rtype: :class:`sortedset` 181 | 182 | .. method:: S.count(value) 183 | 184 | Returns the number of occurrences of *value* in the set. 185 | 186 | Requires |theta(n)| operations and |theta(n)| comparisons in the 187 | worst case. 188 | 189 | :rtype: :class:`int` 190 | 191 | .. method:: S.difference(S2, ...) 192 | S - S2 - ... 193 | 194 | Return a new set with elements in the set that are not in the others. 195 | 196 | In the worst case, requires |theta(m log**2(n + m))| operations 197 | and |theta(m log(n + m))| comparisons, where *m* is the combined 198 | size of all the other sets and *n* is the size of *S*. 199 | 200 | :rtype: :class:`sortedset` 201 | 202 | .. method:: S.difference_update(S2, ...) 203 | S -= S2 | ... 204 | 205 | Update the set, removing elements found in keeping only elements 206 | found in any of the others. 207 | 208 | In the worst case, requires |theta(m log**2(n + m))| operations 209 | and |theta(m log(n + m))| comparisons, where *m* is the combined 210 | size of all the other sets and *n* is the initial size of *S*. 211 | 212 | .. _sortedset.discard: 213 | .. method:: S.discard(value) 214 | 215 | Removes the first occurrence of *value*. If *value* is not a 216 | member, does nothing. 217 | 218 | In the worst case, requires |theta(log**2 n)| operations and 219 | |theta(log n)| comparisons. 220 | 221 | .. method:: S.index(value, [start, [stop]]) 222 | 223 | Returns the smallest *k* such that :math:`S[k] == x` and 224 | :math:`i <= k < j`. Raises ValueError if *value* is not 225 | present. *stop* defaults to the end of the set. *start* 226 | defaults to the beginning. Negative indexes are supported, as 227 | for slice indices. 228 | 229 | In the worst case, requires |theta(log**2 m)| operations and 230 | |theta(log m)| comparisons, where *m* is *stop* - *start*. 231 | 232 | (Cormen *et al.* call this operation "RANK".) 233 | 234 | :rtype: :class:`int` 235 | 236 | .. method:: S.intersection(S2, ...) 237 | S & S2 & ... 238 | 239 | Return a new set with elements common to the set and all others. 240 | 241 | In the worst case, requires |theta(m log**2(n + m))| operations 242 | and |theta(m log(n + m))| comparisons, where *m* is the combined 243 | size of all the other sets and *n* is the size of *S*. 244 | 245 | :rtype: :class:`sortedset` 246 | 247 | .. method:: S.intersection_update(S2, ...) 248 | S &= S2 & ... 249 | 250 | Update the set, keeping only elements found in it and all 251 | others. 252 | 253 | In the worst case, requires |theta(m log**2(n + m))| operations 254 | and |theta(m log(n + m))| comparisons, where *m* is the combined 255 | size of all the other sets and *n* is the initial size of *S*. 256 | 257 | .. method:: S.isdisjoint(S2) 258 | 259 | Return True if the set has no elements in common with *S2*. 260 | Sets are disjoint if and only if their intersection is the empty 261 | set. 262 | 263 | :rtype: :class:`bool` 264 | 265 | .. method:: S.issubset(S2) 266 | S <= S2 267 | 268 | Test whether every element in the set is in *S2* 269 | 270 | In the worst case, requires |theta(n)| operations multiplied by 271 | the cost of *S2*'s `in` operation. 272 | 273 | :rtype: :class:`bool` 274 | 275 | .. method:: S.issuperset(S2) 276 | S >= S2 277 | 278 | Test whether every element in *S2* is in the set. 279 | 280 | In the worst case, requires |theta(m log**2 n)| operations or 281 | |theta(m log n)| comparisons, where *m* is the size of *S2* and 282 | *n* is the size of *S*. 283 | 284 | :rtype: :class:`bool` 285 | 286 | .. method:: S.symmetric_difference(S2) 287 | S ^ S2 288 | 289 | Return a new set with element in either set but not both. 290 | 291 | In the worst case, requires |theta(m log**2(n + m))| operations 292 | and |theta(m log(n + m))| comparisons, where *m* is the size of 293 | *S2* and *n* is the size of *S*. 294 | 295 | :rtype: :class:`sortedset` 296 | 297 | .. method:: S.symmetric_difference_update(S2) 298 | S ^= S2 299 | 300 | Update the set, keeping only elements found in either set, but 301 | not in both. 302 | 303 | In the worst case, requires |theta(m log**2(n + m))| operations 304 | and |theta(m log(n + m))| comparisons, where *m* is the size of 305 | *S2* and *n* is the initial size of *S*. 306 | 307 | .. method:: S.pop([index]) 308 | 309 | Removes and return item at index (default last). Raises 310 | IndexError if set is empty or index is out of range. Negative 311 | indexes are supported, as for slice indices. 312 | 313 | Requires |theta(log n)| operations and no comparisons. 314 | 315 | :rtype: item 316 | 317 | .. _sortedset.remove: 318 | .. method:: S.remove(value) 319 | 320 | Remove first occurrence of *value*. Raises ValueError if 321 | *value* is not present. 322 | 323 | In the worst case, requires |theta(log**2 n)| operations and 324 | |theta(log n)| comparisons. 325 | 326 | .. method:: S.union(S2, ...) 327 | S | S2 | ... 328 | 329 | Return a new sortedset with elements from the set and all 330 | others. The new sortedset will be sorted according to the key 331 | of the leftmost set. 332 | 333 | Requires |theta(m log**2(n + m))| operations or |theta(m log(n + 334 | m))| comparisons, where *m* is the total size of the other sets 335 | and *n* is the size of *S*. 336 | 337 | :rtype: :class:`sortedset` 338 | 339 | .. method:: S.update(S2, ...) 340 | S |= S2 | ... 341 | 342 | Update the set, adding elements from all others. 343 | 344 | In the worst case, requires |theta(m log**2(n + m))| operations 345 | and |theta(m log(n + m))| comparisons, where *m* is the combined 346 | size of all the other sets and *n* is the initial size of *S*. 347 | -------------------------------------------------------------------------------- /doc/weaksortedlist.rst: -------------------------------------------------------------------------------- 1 | weaksortedlist 2 | ============== 3 | 4 | .. currentmodule:: blist 5 | 6 | .. class:: weaksortedlist(iterable=(), key=None) 7 | 8 | A :class:`weaksortedlist` provides exactly the same methods and has 9 | the same performance characteristics as a :class:`sortedlist`. 10 | However, it keeps a `weak reference 11 | `_ to its members 12 | instead of a strong reference. After an item has no more strong 13 | references to it, the item will be removed from the list. 14 | 15 | -------------------------------------------------------------------------------- /doc/weaksortedset.rst: -------------------------------------------------------------------------------- 1 | weaksortedset 2 | ============= 3 | 4 | .. currentmodule:: blist 5 | 6 | .. class:: weaksortedset(iterable=(), key=None) 7 | 8 | A :class:`weaksortedset` provides exactly the same methods and has 9 | the same performance characteristics as a :class:`sortedset`. 10 | However, it keeps a `weak reference 11 | `_ to its members 12 | instead of a strong reference. After an item has no more strong 13 | references to it, the item will be removed from the set. 14 | 15 | -------------------------------------------------------------------------------- /ez_setup.py: -------------------------------------------------------------------------------- 1 | #!python 2 | """Bootstrap setuptools installation 3 | 4 | If you want to use setuptools in your package's setup.py, just include this 5 | file in the same directory with it, and add this to the top of your setup.py:: 6 | 7 | from ez_setup import use_setuptools 8 | use_setuptools() 9 | 10 | If you want to require a specific version of setuptools, set a download 11 | mirror, or use an alternate download directory, you can do so by supplying 12 | the appropriate options to ``use_setuptools()``. 13 | 14 | This file can also be run as a script to install or upgrade setuptools. 15 | """ 16 | import os 17 | import shutil 18 | import sys 19 | import tempfile 20 | import tarfile 21 | import optparse 22 | import subprocess 23 | import platform 24 | 25 | from distutils import log 26 | 27 | try: 28 | from site import USER_SITE 29 | except ImportError: 30 | USER_SITE = None 31 | 32 | DEFAULT_VERSION = "1.1.6" 33 | DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" 34 | 35 | def _python_cmd(*args): 36 | args = (sys.executable,) + args 37 | return subprocess.call(args) == 0 38 | 39 | def _check_call_py24(cmd, *args, **kwargs): 40 | res = subprocess.call(cmd, *args, **kwargs) 41 | class CalledProcessError(Exception): 42 | pass 43 | if not res == 0: 44 | msg = "Command '%s' return non-zero exit status %d" % (cmd, res) 45 | raise CalledProcessError(msg) 46 | vars(subprocess).setdefault('check_call', _check_call_py24) 47 | 48 | def _install(tarball, install_args=()): 49 | # extracting the tarball 50 | tmpdir = tempfile.mkdtemp() 51 | log.warn('Extracting in %s', tmpdir) 52 | old_wd = os.getcwd() 53 | try: 54 | os.chdir(tmpdir) 55 | tar = tarfile.open(tarball) 56 | _extractall(tar) 57 | tar.close() 58 | 59 | # going in the directory 60 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) 61 | os.chdir(subdir) 62 | log.warn('Now working in %s', subdir) 63 | 64 | # installing 65 | log.warn('Installing Setuptools') 66 | if not _python_cmd('setup.py', 'install', *install_args): 67 | log.warn('Something went wrong during the installation.') 68 | log.warn('See the error message above.') 69 | # exitcode will be 2 70 | return 2 71 | finally: 72 | os.chdir(old_wd) 73 | shutil.rmtree(tmpdir) 74 | 75 | 76 | def _build_egg(egg, tarball, to_dir): 77 | # extracting the tarball 78 | tmpdir = tempfile.mkdtemp() 79 | log.warn('Extracting in %s', tmpdir) 80 | old_wd = os.getcwd() 81 | try: 82 | os.chdir(tmpdir) 83 | tar = tarfile.open(tarball) 84 | _extractall(tar) 85 | tar.close() 86 | 87 | # going in the directory 88 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) 89 | os.chdir(subdir) 90 | log.warn('Now working in %s', subdir) 91 | 92 | # building an egg 93 | log.warn('Building a Setuptools egg in %s', to_dir) 94 | _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) 95 | 96 | finally: 97 | os.chdir(old_wd) 98 | shutil.rmtree(tmpdir) 99 | # returning the result 100 | log.warn(egg) 101 | if not os.path.exists(egg): 102 | raise IOError('Could not build the egg.') 103 | 104 | 105 | def _do_download(version, download_base, to_dir, download_delay): 106 | egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' 107 | % (version, sys.version_info[0], sys.version_info[1])) 108 | if not os.path.exists(egg): 109 | tarball = download_setuptools(version, download_base, 110 | to_dir, download_delay) 111 | _build_egg(egg, tarball, to_dir) 112 | sys.path.insert(0, egg) 113 | 114 | # Remove previously-imported pkg_resources if present (see 115 | # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). 116 | if 'pkg_resources' in sys.modules: 117 | del sys.modules['pkg_resources'] 118 | 119 | import setuptools 120 | setuptools.bootstrap_install_from = egg 121 | 122 | 123 | def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, 124 | to_dir=os.curdir, download_delay=15): 125 | # making sure we use the absolute path 126 | to_dir = os.path.abspath(to_dir) 127 | was_imported = 'pkg_resources' in sys.modules or \ 128 | 'setuptools' in sys.modules 129 | try: 130 | import pkg_resources 131 | except ImportError: 132 | return _do_download(version, download_base, to_dir, download_delay) 133 | try: 134 | pkg_resources.require("setuptools>=" + version) 135 | return 136 | except pkg_resources.VersionConflict: 137 | e = sys.exc_info()[1] 138 | if was_imported: 139 | sys.stderr.write( 140 | "The required version of setuptools (>=%s) is not available,\n" 141 | "and can't be installed while this script is running. Please\n" 142 | "install a more recent version first, using\n" 143 | "'easy_install -U setuptools'." 144 | "\n\n(Currently using %r)\n" % (version, e.args[0])) 145 | sys.exit(2) 146 | else: 147 | del pkg_resources, sys.modules['pkg_resources'] # reload ok 148 | return _do_download(version, download_base, to_dir, 149 | download_delay) 150 | except pkg_resources.DistributionNotFound: 151 | return _do_download(version, download_base, to_dir, 152 | download_delay) 153 | 154 | def download_file_powershell(url, target): 155 | """ 156 | Download the file at url to target using Powershell (which will validate 157 | trust). Raise an exception if the command cannot complete. 158 | """ 159 | target = os.path.abspath(target) 160 | cmd = [ 161 | 'powershell', 162 | '-Command', 163 | "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars(), 164 | ] 165 | subprocess.check_call(cmd) 166 | 167 | def has_powershell(): 168 | if platform.system() != 'Windows': 169 | return False 170 | cmd = ['powershell', '-Command', 'echo test'] 171 | devnull = open(os.path.devnull, 'wb') 172 | try: 173 | try: 174 | subprocess.check_call(cmd, stdout=devnull, stderr=devnull) 175 | except: 176 | return False 177 | finally: 178 | devnull.close() 179 | return True 180 | 181 | download_file_powershell.viable = has_powershell 182 | 183 | def download_file_curl(url, target): 184 | cmd = ['curl', url, '--silent', '--output', target] 185 | subprocess.check_call(cmd) 186 | 187 | def has_curl(): 188 | cmd = ['curl', '--version'] 189 | devnull = open(os.path.devnull, 'wb') 190 | try: 191 | try: 192 | subprocess.check_call(cmd, stdout=devnull, stderr=devnull) 193 | except: 194 | return False 195 | finally: 196 | devnull.close() 197 | return True 198 | 199 | download_file_curl.viable = has_curl 200 | 201 | def download_file_wget(url, target): 202 | cmd = ['wget', url, '--quiet', '--output-document', target] 203 | subprocess.check_call(cmd) 204 | 205 | def has_wget(): 206 | cmd = ['wget', '--version'] 207 | devnull = open(os.path.devnull, 'wb') 208 | try: 209 | try: 210 | subprocess.check_call(cmd, stdout=devnull, stderr=devnull) 211 | except: 212 | return False 213 | finally: 214 | devnull.close() 215 | return True 216 | 217 | download_file_wget.viable = has_wget 218 | 219 | def download_file_insecure(url, target): 220 | """ 221 | Use Python to download the file, even though it cannot authenticate the 222 | connection. 223 | """ 224 | try: 225 | from urllib.request import urlopen 226 | except ImportError: 227 | from urllib2 import urlopen 228 | src = dst = None 229 | try: 230 | src = urlopen(url) 231 | # Read/write all in one block, so we don't create a corrupt file 232 | # if the download is interrupted. 233 | data = src.read() 234 | dst = open(target, "wb") 235 | dst.write(data) 236 | finally: 237 | if src: 238 | src.close() 239 | if dst: 240 | dst.close() 241 | 242 | download_file_insecure.viable = lambda: True 243 | 244 | def get_best_downloader(): 245 | downloaders = [ 246 | download_file_powershell, 247 | download_file_curl, 248 | download_file_wget, 249 | download_file_insecure, 250 | ] 251 | 252 | for dl in downloaders: 253 | if dl.viable(): 254 | return dl 255 | 256 | def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, 257 | to_dir=os.curdir, delay=15, 258 | downloader_factory=get_best_downloader): 259 | """Download setuptools from a specified location and return its filename 260 | 261 | `version` should be a valid setuptools version number that is available 262 | as an egg for download under the `download_base` URL (which should end 263 | with a '/'). `to_dir` is the directory where the egg will be downloaded. 264 | `delay` is the number of seconds to pause before an actual download 265 | attempt. 266 | 267 | ``downloader_factory`` should be a function taking no arguments and 268 | returning a function for downloading a URL to a target. 269 | """ 270 | # making sure we use the absolute path 271 | to_dir = os.path.abspath(to_dir) 272 | tgz_name = "setuptools-%s.tar.gz" % version 273 | url = download_base + tgz_name 274 | saveto = os.path.join(to_dir, tgz_name) 275 | if not os.path.exists(saveto): # Avoid repeated downloads 276 | log.warn("Downloading %s", url) 277 | downloader = downloader_factory() 278 | downloader(url, saveto) 279 | return os.path.realpath(saveto) 280 | 281 | 282 | def _extractall(self, path=".", members=None): 283 | """Extract all members from the archive to the current working 284 | directory and set owner, modification time and permissions on 285 | directories afterwards. `path' specifies a different directory 286 | to extract to. `members' is optional and must be a subset of the 287 | list returned by getmembers(). 288 | """ 289 | import copy 290 | import operator 291 | from tarfile import ExtractError 292 | directories = [] 293 | 294 | if members is None: 295 | members = self 296 | 297 | for tarinfo in members: 298 | if tarinfo.isdir(): 299 | # Extract directories with a safe mode. 300 | directories.append(tarinfo) 301 | tarinfo = copy.copy(tarinfo) 302 | tarinfo.mode = 448 # decimal for oct 0700 303 | self.extract(tarinfo, path) 304 | 305 | # Reverse sort directories. 306 | if sys.version_info < (2, 4): 307 | def sorter(dir1, dir2): 308 | return cmp(dir1.name, dir2.name) 309 | directories.sort(sorter) 310 | directories.reverse() 311 | else: 312 | directories.sort(key=operator.attrgetter('name'), reverse=True) 313 | 314 | # Set correct owner, mtime and filemode on directories. 315 | for tarinfo in directories: 316 | dirpath = os.path.join(path, tarinfo.name) 317 | try: 318 | self.chown(tarinfo, dirpath) 319 | self.utime(tarinfo, dirpath) 320 | self.chmod(tarinfo, dirpath) 321 | except ExtractError: 322 | e = sys.exc_info()[1] 323 | if self.errorlevel > 1: 324 | raise 325 | else: 326 | self._dbg(1, "tarfile: %s" % e) 327 | 328 | 329 | def _build_install_args(options): 330 | """ 331 | Build the arguments to 'python setup.py install' on the setuptools package 332 | """ 333 | install_args = [] 334 | if options.user_install: 335 | if sys.version_info < (2, 6): 336 | log.warn("--user requires Python 2.6 or later") 337 | raise SystemExit(1) 338 | install_args.append('--user') 339 | return install_args 340 | 341 | def _parse_args(): 342 | """ 343 | Parse the command line for options 344 | """ 345 | parser = optparse.OptionParser() 346 | parser.add_option( 347 | '--user', dest='user_install', action='store_true', default=False, 348 | help='install in user site package (requires Python 2.6 or later)') 349 | parser.add_option( 350 | '--download-base', dest='download_base', metavar="URL", 351 | default=DEFAULT_URL, 352 | help='alternative URL from where to download the setuptools package') 353 | parser.add_option( 354 | '--insecure', dest='downloader_factory', action='store_const', 355 | const=lambda: download_file_insecure, default=get_best_downloader, 356 | help='Use internal, non-validating downloader' 357 | ) 358 | options, args = parser.parse_args() 359 | # positional arguments are ignored 360 | return options 361 | 362 | def main(version=DEFAULT_VERSION): 363 | """Install or upgrade setuptools and EasyInstall""" 364 | options = _parse_args() 365 | tarball = download_setuptools(download_base=options.download_base, 366 | downloader_factory=options.downloader_factory) 367 | return _install(tarball, _build_install_args(options)) 368 | 369 | if __name__ == '__main__': 370 | sys.exit(main()) 371 | -------------------------------------------------------------------------------- /fuzz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | from __future__ import print_function 3 | 4 | from blist import blist 5 | import pprint, random, sys 6 | 7 | if len(sys.argv) > 0: 8 | random.seed(int(sys.argv[1])) 9 | else: 10 | random.seed(3) 11 | 12 | type1 = list 13 | type2 = blist 14 | iterations = 100000 15 | 16 | lobs = [type1(), type1()] 17 | blobs = [type2(), type2()] 18 | 19 | def get_method(self, name): 20 | if name == '__add__': 21 | return lambda x: operator.add(self, x) 22 | return getattr(self, name) 23 | 24 | methods = { 25 | '__add__': 1, 26 | '__contains__': 1, 27 | '__delitem__': 1, 28 | '__delslice__': 2, 29 | '__eq__': 1, 30 | '__ge__': 1, 31 | '__getitem__': 1, 32 | '__getslice__': 2, 33 | '__gt__': 1, 34 | '__hash__': 0, 35 | '__iadd__': 1, 36 | '__imul__': 1, 37 | '__iter__': 1, 38 | '__le__': 1, 39 | '__len__': 0, 40 | '__lt__': 1, 41 | '__mul__': 1, 42 | '__ne__': 1, 43 | '__repr__': 0, 44 | '__reversed__': 1, 45 | '__rmul__': 1, 46 | '__setitem__': 2, 47 | '__setslice__': 3, 48 | '__str__': 0, 49 | 'append': 1, 50 | 'count': 1, 51 | 'extend': 1, 52 | 'index': 1, 53 | 'insert': 2, 54 | 'pop': 1, 55 | 'remove': 1, 56 | 'reverse': 0, 57 | 'sort': 3, 58 | } 59 | 60 | for name in list(name for name in methods if not hasattr([], name)): 61 | del methods[name] 62 | 63 | # In Python 2, list types and blist types may compare differently 64 | # against other types. 65 | if sys.version_info[0] < 3: 66 | for name in ('__ge__', '__le__', '__lt__', '__gt__'): 67 | del methods[name] 68 | 69 | left = None 70 | right = None 71 | 72 | gen_int = lambda: random.randint(-2**10, 2**10) 73 | gen_float = lambda: random.random() 74 | gen_string = lambda: "foo" 75 | gen_ob_a = lambda: left 76 | gen_ob_b = lambda: right 77 | gen_index_a = lambda: length_left and random.randrange(length_left) 78 | gen_index_b = lambda: length_right and random.randrange(length_right) 79 | gen_hash = lambda: hash 80 | gen_key = lambda: (lambda x: x) 81 | 82 | gens = [ 83 | gen_int, 84 | gen_ob_a, 85 | gen_ob_b, 86 | gen_index_a, 87 | gen_index_b, 88 | gen_hash, 89 | gen_key 90 | ] 91 | 92 | def save_obs(ob1, ob2): 93 | f = open('ob1', 'w') 94 | f.write(pprint.pformat(ob1)) 95 | f.write('\n') 96 | f.close() 97 | f = open('ob2', 'w') 98 | f.write(pprint.pformat(ob2)) 99 | f.write('\n') 100 | f.close() 101 | 102 | def safe_print(s): 103 | s = str(s) 104 | print(s[:300]) 105 | 106 | def gen_arg(): 107 | gener = random.sample(gens, 1)[0] 108 | return gener() 109 | 110 | def call(f, args): 111 | try: 112 | return f(*args) 113 | except SystemExit: 114 | raise 115 | except KeyboardInterrupt: 116 | raise 117 | except BaseException as e: 118 | return str(type(e)) 119 | 120 | def smart_eq(a, b, d=None): 121 | try: 122 | return a == b 123 | except RuntimeError: 124 | pass 125 | if d is None: 126 | d = set() 127 | if id(a) in d and id(b) in d: 128 | return True 129 | d.add(id(a)) 130 | d.add(id(b)) 131 | if len(a) != len(b): 132 | return False 133 | print(len(a), end=' ') 134 | sys.stdout.flush() 135 | if len(a) > 100000: 136 | print('skipping', end=' ') 137 | sys.stdout.flush() 138 | return True 139 | for i in range(len(a)): 140 | if not smart_eq(a[i], b[i], d): 141 | return False 142 | return True 143 | 144 | last = None 145 | 146 | for _ in range(iterations): 147 | if not smart_eq(lobs, blobs): 148 | print() 149 | safe_print(last) 150 | safe_print('Mismatched objects') 151 | safe_print(lobs) 152 | print() 153 | safe_print(blobs) 154 | break 155 | print() 156 | if random.random() < 0.5: 157 | lobs.reverse() 158 | blobs.reverse() 159 | 160 | left, right = lobs[0], lobs[1] 161 | length_left = len(left) 162 | length_right = len(right) 163 | 164 | if random.random() < 0.01 or length_left + length_right > 1000000: 165 | print('(%d,%d)' % (len(lobs[0]), len(lobs[1]))) 166 | sys.stdout.flush() 167 | lobs = [type1(), type1()] 168 | blobs = [type2(), type2()] 169 | left, right = lobs[0], lobs[1] 170 | length_left = len(left) 171 | length_right = len(right) 172 | else: 173 | #print '.', 174 | #sys.stdout.flush() 175 | pass 176 | 177 | method = random.sample(list(methods), 1)[0] 178 | 179 | args = [gen_arg() for i in range(methods[method])] 180 | 181 | last = ' list: %s%s' % (method, str(tuple(args))) 182 | print(method, '(%d, %d)' % (length_left, length_right), end=' ') 183 | sys.stdout.flush() 184 | f = get_method(left, method) 185 | rv1 = call(f, args) 186 | print('.', end=' ') 187 | sys.stdout.flush() 188 | 189 | left, right = blobs[0], blobs[1] 190 | args2 = [] 191 | for arg in args: 192 | if arg is lobs[0]: 193 | args2.append(blobs[0]) 194 | elif arg is lobs[1]: 195 | args2.append(blobs[1]) 196 | else: 197 | args2.append(arg) 198 | #print ('type2: %s%s' % (method, str(tuple(args2)))) 199 | f = get_method(left, method) 200 | rv2 = call(f, args2) 201 | print('.', end=' ') 202 | sys.stdout.flush() 203 | if method in ('__repr__', '__str__'): 204 | continue 205 | if type(rv1) == type('') and 'MemoryError' in rv1: 206 | if method.startswith('__i'): 207 | lobs = [type1(), type1()] 208 | blobs = [type2(), type2()] 209 | continue 210 | if type(rv2) == type(''): 211 | rv2 = rv2.replace('blist', 'list') 212 | elif type(rv2) == type2 and random.random() < 0.25: 213 | blobs[0] = rv2 214 | lobs[0] = rv1 215 | 216 | if not smart_eq(rv1, rv2): 217 | if method == 'sort' and length_left == 1: 218 | continue 219 | 220 | save_obs(lobs, blobs) 221 | print() 222 | safe_print(lobs) 223 | print() 224 | 225 | #print last 226 | safe_print('Mismatched return values') 227 | safe_print(rv1) 228 | print() 229 | safe_print(rv2) 230 | sys.exit(0) 231 | 232 | -------------------------------------------------------------------------------- /replot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from speed_test import * 4 | 5 | if len(sys.argv) == 1: 6 | for k in timing_d: 7 | plot(k, True) 8 | plot(k, False) 9 | html(k) 10 | else: 11 | name = sys.argv[1] 12 | plot(name, True) 13 | plot(name, False) 14 | html(name) 15 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | import sys 5 | import ez_setup 6 | ez_setup.use_setuptools() 7 | from setuptools import setup, Extension 8 | 9 | 10 | define_macros = [] 11 | 12 | import ctypes 13 | if ctypes.sizeof(ctypes.c_double) == 8: 14 | dv = ctypes.c_double(9006104071832581.0) 15 | iv = ctypes.cast(ctypes.pointer(dv), ctypes.POINTER(ctypes.c_uint64)) 16 | if iv.contents.value == 0x433fff0102030405: 17 | define_macros.append(('BLIST_FLOAT_RADIX_SORT', 1)) 18 | 19 | with open('blist/__init__.py') as f: 20 | line = f.readline() 21 | match = re.search(r'= *[\'"](.*)[\'"]', line) 22 | version = match.group(1) 23 | 24 | setup(name='blist', 25 | version=version, 26 | description='a list-like type with better asymptotic performance and similar performance on small lists', 27 | author='Stutzbach Enterprises, LLC', 28 | author_email='daniel@stutzbachenterprises.com', 29 | url='http://stutzbachenterprises.com/blist/', 30 | license = "BSD", 31 | keywords = "blist list b+tree btree fast copy-on-write sparse array sortedlist sorted sortedset weak weaksortedlist weaksortedset sorteddict btuple", 32 | ext_modules=[Extension('blist._blist', ['blist/_blist.c'], 33 | define_macros=define_macros, 34 | )], 35 | packages=['blist'], 36 | provides = ['blist'], 37 | test_suite = "test_blist.test_suite", 38 | zip_safe = False, # zips are broken on cygwin for C extension modules 39 | classifiers = [ 40 | 'Development Status :: 5 - Production/Stable', 41 | 'Intended Audience :: Developers', 42 | 'Intended Audience :: Science/Research', 43 | 'License :: OSI Approved :: BSD License', 44 | 'Programming Language :: C', 45 | 'Programming Language :: Python :: 2.5', 46 | 'Programming Language :: Python :: 2.6', 47 | 'Programming Language :: Python :: 2.7', 48 | 'Programming Language :: Python :: 3', 49 | 'Programming Language :: Python :: 3.1', 50 | 'Programming Language :: Python :: 3.2', 51 | ], 52 | long_description=open('README.rst').read() 53 | ) 54 | -------------------------------------------------------------------------------- /speed_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from __future__ import print_function 3 | 4 | import os, sys, subprocess 5 | from math import * 6 | 7 | # The tests to run are near the bottom 8 | 9 | MIN_REPS = 3 10 | NUM_POINTS = 9 11 | MIN_TIME = 0.1 12 | MAX_TIME = 1.0 13 | MAX_X = 100000 14 | 15 | def makedir(x): 16 | try: 17 | os.mkdir(x) 18 | except OSError: 19 | pass 20 | 21 | def rm(x): 22 | try: 23 | os.unlink(x) 24 | except OSError: 25 | pass 26 | 27 | makedir('fig') 28 | makedir('fig/relative') 29 | makedir('fig/absolute') 30 | makedir('.cache') 31 | makedir('dat') 32 | makedir('gnuplot') 33 | 34 | limits = (128,) 35 | current_limit = None 36 | def make(limit): 37 | global current_limit 38 | current_limit = limit 39 | 40 | setup = 'from blist import blist' 41 | 42 | ns = [] 43 | for i in range(50+1): 44 | ns.append(int(floor(10**(i*0.1)))) 45 | ns = list(i for i in sorted(set(ns)) if i <= MAX_X) 46 | 47 | def smart_timeit(stmt, setup, hint): 48 | n = hint 49 | while 1: 50 | v = timeit(stmt, setup, n) 51 | if v[0]*n > MIN_TIME: 52 | return v, n 53 | n <<= 1 54 | 55 | import timeit 56 | timeit_path = timeit.__file__ 57 | 58 | timeit_cache = {} 59 | def timeit(stmt, setup, rep): 60 | assert rep >= MIN_REPS 61 | key = (stmt, setup, rep, current_limit) 62 | if key in timeit_cache: 63 | return timeit_cache[key] 64 | try: 65 | n = NUM_POINTS 66 | args =[sys.executable, timeit_path, 67 | '-r', str(n), '-v', '-n', str(rep), '-s', setup, '--', stmt] 68 | p = subprocess.Popen(args, 69 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 70 | so, se = p.communicate() 71 | try: 72 | lines = so.split(b'\n') 73 | raw = lines[0] 74 | number = int(lines[1].split()[0]) 75 | times = [float(x) / number for x in raw.split()[2:]] 76 | times.sort() 77 | # median, lower quartile, upper quartile 78 | v = (times[n//2], times[n//4], times[3*n//4]) 79 | timeit_cache[key] = v 80 | return v 81 | except: 82 | print(so) 83 | print(se) 84 | raise 85 | except: 86 | print(stmt) 87 | print(setup) 88 | raise 89 | 90 | values = {} 91 | def get_timing1(limit, label, setup_n, template, typename, use_rep_map): 92 | f = open('dat/%s-%s.dat' % (str(limit), label), 'w') 93 | print('#', label, file=f) 94 | print('#', template.replace('\n', '\\n'), file=f) 95 | if setup_n is None: 96 | setup_n = "x = TypeToTest(range(n))" 97 | else: 98 | setup_n = setup_n 99 | ftimeit = open('fig/%s.txt' % label, 'w') 100 | print('
    Setup: %s
    ' % setup_n.replace('\n', '
    '), 101 | file=ftimeit) 102 | print('Timed: %s
    ' % template.replace('\n', '
    '), 103 | file=ftimeit) 104 | ftimeit.close() 105 | 106 | for i in reversed(list(range(len(ns)))): 107 | n = ns[i] 108 | key = (limit, label, setup_n, n, template, typename) 109 | print(n, end=' ') 110 | sys.stdout.flush() 111 | setup2 = '\nTypeToTest = %s\nn = %d\n' % (typename, n) 112 | setup3 = setup + '\n' + setup2 + setup_n 113 | stmt = template 114 | if not use_rep_map: 115 | if i < len(ns)-1: 116 | rep_map[n] = max(rep_map[n], rep_map[ns[i+1]]) 117 | v, rep = smart_timeit(stmt, setup3, rep_map[n]) 118 | if rep_map[n] < rep: 119 | rep_map[n] = rep 120 | else: 121 | k = rep_map[n] 122 | if k * values[key] > MAX_TIME: 123 | k = max(MIN_REPS, int(ceil(MAX_TIME / values[key]))) 124 | v = timeit(stmt, setup3, k) 125 | values[key] = v[0] 126 | v = [x*1000 for x in v] 127 | if typename == 'list': 128 | list_values[n] = v[0] 129 | print(n, file=f, end=' ') 130 | for x in v: 131 | print(x, file=f, end=' ') 132 | for x in v: 133 | print(x/list_values[n], file=f, end=' ') 134 | print(file=f) 135 | print() 136 | f.close() 137 | 138 | def get_timing(label, setup_n, template): 139 | global rep_map, list_values 140 | rep_map = {} 141 | list_values = {} 142 | for n in ns: 143 | rep_map[n] = MIN_REPS 144 | make('list') 145 | get_timing1('list', label, setup_n, template, 'list', False) 146 | for limit in limits: 147 | print('Timing', label, limit, ':', end=' ') 148 | sys.stdout.flush() 149 | make(limit) 150 | get_timing1(limit, label, setup_n, template, 'blist', False) 151 | 152 | make('list') 153 | get_timing1('list', label, setup_n, template, 'list', True) 154 | for limit in limits: 155 | print('Timing', label, limit, ':', end=' ') 156 | sys.stdout.flush() 157 | make(limit) 158 | get_timing1(limit, label, setup_n, template, 'blist', True) 159 | 160 | plot(label, True) 161 | plot(label, False) 162 | html(label) 163 | 164 | def html(label): 165 | fname = 'fig/%s.html' % label 166 | f = open(fname, 'w') 167 | if timing_d[label][0] is None: 168 | setup = 'x = TypeToTest(range(n))' 169 | else: 170 | setup = timing_d[label][0] 171 | print(''' 172 | 173 | 174 | BList vs Python list timing results: %s 175 | 176 | 177 | 178 |
    179 | Home 180 | | BList 181 | | Poker Sleuth 182 | | Poker Calculator 183 | | Hand Converter 184 | 185 |
    186 | 187 | 189 | 191 |

    192 | Setup: 193 |

    194 | %s
    195 | 
    196 | Timed: 197 |
    198 | %s
    199 | 
    200 | 201 | 202 | ''' % (label, label, label, setup, timing_d[label][1]), file=f) 203 | f.close() 204 | 205 | def plot(label, relative): 206 | safe_label = label.replace('_', '\\\\_') 207 | fname = 'gnuplot/%s.gnuplot' % label 208 | f = open(fname, 'w') 209 | if relative: 210 | d = 'fig/relative/' 211 | else: 212 | d = 'fig/absolute/' 213 | print(""" 214 | set output "%s/%s.svg" 215 | set xlabel "List Size (n)" 216 | set title "%s" 217 | set terminal svg size 480,360 dynamic enhanced 218 | set size noratio 1,1 219 | set key top left 220 | set bars 0.2 221 | set pointsize 0.5 222 | set xtics ("1" 1, "10" 10, "100" 100, "1k" 1000, "10k" 10000, "100k" 100000, "1M" 1000000) 223 | """ % (d, label, safe_label), file=f) 224 | 225 | if relative: 226 | print('set title "Normalized Execution Times, log-linear scale"', file=f) 227 | print('set logscale x', file=f) 228 | print('set yrange [0:*]', file=f) 229 | print('set yrange [0:200]', file=f) 230 | print('set ylabel "Execution Time (%)"', file=f) 231 | k = 3 232 | m = 100.0 233 | else: 234 | print('set title "Raw Execution Times, log-log scale"', file=f) 235 | print('set logscale xy', file=f) 236 | print('set yrange [0.00001:10]', file=f) 237 | print('set ylabel "Execution Time"', file=f) 238 | print('set ytics ("1 ns" 0.000001, "10 ns" 0.00001, "100 ns" 0.0001, "1 us" 0.001, "10 us" 0.01, "100 us" 0.1, "1 ms" 1.0, "10 ms" 10.0, "100 ms" 100.0)', file=f) 239 | k = 0 240 | m = 1.0 241 | 242 | print (('plot "dat/list-%s.dat" using 1:(%f*$%d):(%f*$%d):(%f*$%d) title "list()" with yerr pt 1, \\' % (label, m, k+2, m, k+3, m, k+4)), file=f) 243 | for limit in limits: 244 | print ((' "dat/%d-%s.dat" using 1:(%f*$%d):(%f*$%d):(%f*$%d) title "blist()" with yerr pt 1 '% (limit, label, m, k+2, m, k+3, m, k+4)), file=f) 245 | print(file=f) 246 | f.flush() 247 | f.close() 248 | if os.system('gnuplot "%s"' % fname): 249 | raise RuntimeError('Gnuplot failure') 250 | 251 | timing_d = {} 252 | def add_timing(name, auto, stmt): 253 | timing_d[name] = (auto, stmt) 254 | 255 | def run_timing(name): 256 | auto, stmt = timing_d[name] 257 | get_timing(name, auto, stmt) 258 | 259 | def run_all(): 260 | for k in sorted(timing_d): 261 | run_timing(k) 262 | 263 | ######################################################################## 264 | # Tests to run are below here. 265 | # The arguments to add_timing are as follows: 266 | # 1) name of the test 267 | # 2) setup code to run once. "None" means x = TypeToTest(range(n)) 268 | # 3) code to execute repeatedly in a loop 269 | # 270 | # The following symbols will autoamtically be defined: 271 | # - blist 272 | # - TypeToTest 273 | # - n 274 | 275 | add_timing('eq list', 'x = TypeToTest(range(n))\ny=range(n)', 'x==y') 276 | #add_timing('eq recursive', 'x = TypeToTest()\nx.append(x)\ny = TypeToTest()\ny.append(y)', 'try:\n x==y\nexcept RuntimeError:\n pass') 277 | 278 | add_timing('FIFO', None, """\ 279 | x.insert(0, 0) 280 | x.pop(0) 281 | """) 282 | 283 | add_timing('LIFO', None, """\ 284 | x.append(0) 285 | x.pop(-1) 286 | """) 287 | 288 | add_timing('add', None, "x + x") 289 | add_timing('contains', None, "-1 in x") 290 | #add_timing('getitem1', None, "x[0]") 291 | #add_timing('getitem2', None, "x.__getitem__(0)") 292 | add_timing('getitem3', 'x = TypeToTest(range(n))\nm = n//2', "x[m]") 293 | add_timing('getslice', None, "x[1:-1]") 294 | add_timing('forloop', None, "for i in x:\n pass") 295 | add_timing('len', None, "len(x)") 296 | add_timing('eq', None, "x == x") 297 | add_timing('mul10', None, "x * 10") 298 | #add_timing('setitem1', None, 'x[0] = 1') 299 | add_timing('setitem3', 'x = TypeToTest(range(n))\nm = n//2', 'x[m] = 1') 300 | add_timing('count', None, 'x.count(5)') 301 | add_timing('reverse', None, 'x.reverse()') 302 | add_timing('delslice', None, 'del x[len(x)//4:3*len(x)//4]\nx *= 2') 303 | add_timing('setslice', None, 'x[:] = x') 304 | 305 | add_timing('sort random', 'import random\nx = [random.randrange(n*4) for i in range(n)]', 'y = TypeToTest(x)\ny.sort()') 306 | add_timing('sort random key', 'import random\nx = [random.randrange(n*4) for i in range(n)]', 'y = TypeToTest(x)\ny.sort(key=float)') 307 | add_timing('sort sorted', None, 'x.sort()') 308 | add_timing('sort sorted key', None, 'x.sort(key=int)') 309 | add_timing('sort reversed', 'x = list(range(n))\nx.reverse()', 'y = TypeToTest(x)\ny.sort()') 310 | add_timing('sort reversed key', 'x = list(range(n))\nx.reverse()', 'y = TypeToTest(x)\ny.sort(key=int)') 311 | 312 | add_timing('sort random tuples', 'import random\nx = [(random.random(), random.random()) for i in range(n)]', 'y = TypeToTest(x)\ny.sort()') 313 | 314 | ob_def = ''' 315 | import random 316 | class ob: 317 | def __init__(self, v): 318 | self.v = v 319 | def __lt__(self, other): 320 | return self.v < other.v 321 | x = [ob(random.randrange(n*4)) for i in range(n)] 322 | ''' 323 | 324 | add_timing('sort random objects', ob_def, 'y = TypeToTest(x)\ny.sort()') 325 | add_timing('sort sorted objects', ob_def + 'x.sort()', 'x.sort()') 326 | 327 | add_timing('init from list', 'x = list(range(n))', 'y = TypeToTest(x)') 328 | add_timing('init from tuple', 'x = tuple(range(n))', 'y = TypeToTest(x)') 329 | add_timing('init from iterable', 'x = range(n)', 'y = TypeToTest(x)') 330 | add_timing('init from same type', None, 'y = TypeToTest(x)') 331 | 332 | add_timing('shuffle', 'from random import shuffle\nx = TypeToTest(range(n))', 'shuffle(x)') 333 | 334 | if __name__ == '__main__': 335 | make(128) 336 | if len(sys.argv) == 1: 337 | run_all() 338 | else: 339 | for name in sys.argv[1:]: 340 | run_timing(name) 341 | -------------------------------------------------------------------------------- /speed_test_native.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | 3 | import os, sys, subprocess 4 | from math import * 5 | 6 | # The tests to run are near the bottom 7 | 8 | MIN_REPS = 3 9 | MIN_TIME = 0.01 10 | MAX_TIME = 1.0 11 | 12 | if 'cygwin' in os.uname()[0].lower(): 13 | extension = 'dll' 14 | else: 15 | extension = 'so' 16 | 17 | def makedir(x): 18 | try: 19 | os.mkdir(x) 20 | except OSError: 21 | pass 22 | 23 | def rm(x): 24 | try: 25 | os.unlink(x) 26 | except OSError: 27 | pass 28 | 29 | makedir('fig') 30 | makedir('fig/relative') 31 | makedir('fig/absolute') 32 | makedir('.cache') 33 | makedir('dat') 34 | makedir('gnuplot') 35 | 36 | setup = '' 37 | 38 | types = ('blist', 'list') 39 | 40 | typemap = { 41 | 'blist': '/home/agthorr/mypython-2.5/python', 42 | 'list': '/home/agthorr/Python-2.5/python', 43 | } 44 | 45 | ns = (range(1,10) + range(10, 100, 10) + range(100, 1000, 100) 46 | + range(1000, 10001, 1000)) 47 | 48 | def smart_timeit(python, stmt, setup, hint): 49 | n = hint 50 | while 1: 51 | v = timeit(python, stmt, setup, n) 52 | if v[0]*n > MIN_TIME: 53 | return v, n 54 | n <<= 1 55 | 56 | timeit_cache = {} 57 | def timeit(python, stmt, setup, rep): 58 | assert rep >= MIN_REPS 59 | key = (python, stmt, setup, rep) 60 | if key in timeit_cache: 61 | return timeit_cache[key] 62 | try: 63 | n = 9 64 | p = subprocess.Popen([python, '/usr/lib/python2.5/timeit.py', 65 | '-r', str(n), '-v', '-n', str(rep), '-s', setup, '--', stmt], 66 | stdout=subprocess.PIPE) 67 | so, se = p.communicate() 68 | try: 69 | lines = so.split('\n') 70 | 71 | raw = lines[0] 72 | number = int(lines[1].split()[0]) 73 | times = [float(x) / number for x in raw.split()[2:]] 74 | times.sort() 75 | 76 | v = (times[n//2+1], times[n//4+1], times[(3*n)//4+1]) 77 | 78 | #so = lines[1] 79 | #parts = so.split() 80 | #v = float(parts[-4]) 81 | #units = parts[-3] 82 | #if units == 'usec': 83 | # v *= 10.0**-6 84 | #elif units == 'msec': 85 | # v *= 10.0**-3 86 | #elif units == 'sec': 87 | # pass 88 | #else: 89 | # raise 'Unknown units' 90 | timeit_cache[key] = v 91 | return v 92 | except: 93 | print so 94 | print se 95 | raise 96 | except: 97 | print stmt 98 | print setup 99 | raise 100 | 101 | values = {} 102 | def get_timing1(label, setup_n, template, typename, use_rep_map): 103 | f = open('dat/%s-%s.dat' % (str(typename), label), 'w') 104 | print >>f, '#', label 105 | print >>f, '#', template.replace('\n', '\\n') 106 | for i in reversed(range(len(ns))): 107 | n = ns[i] 108 | key = (label, setup_n, n, template, typename) 109 | print n, 110 | sys.stdout.flush() 111 | setup2 = '\nn = %d\n' % (n,) 112 | if setup_n is None: 113 | setup3 = "x = list(range(n))" 114 | else: 115 | setup3 = setup_n 116 | setup3 = setup + '\n' + setup2 + setup3 117 | stmt = template 118 | if not use_rep_map: 119 | if i < len(ns)-1: 120 | rep_map[n] = max(rep_map[n], rep_map[ns[i+1]]) 121 | v, rep = smart_timeit(typemap[typename], stmt, setup3, rep_map[n]) 122 | if rep_map[n] < rep: 123 | rep_map[n] = rep 124 | else: 125 | k = rep_map[n] 126 | if k * values[key] > MAX_TIME: 127 | k = max(MIN_REPS, int(ceil(MAX_TIME / values[key]))) 128 | v = timeit(typemap[typename], stmt, setup3, k) 129 | values[key] = v[0] 130 | v = [x*1000 for x in v] 131 | if typename == 'list': 132 | list_values[n] = v[0] 133 | print >>f, n, 134 | for x in v: 135 | print >>f, x, 136 | for x in v: 137 | print >>f, x/list_values[n], 138 | print >>f 139 | print 140 | f.close() 141 | 142 | def get_timing(label, setup_n, template): 143 | global rep_map, list_values 144 | rep_map = {} 145 | list_values = {} 146 | for n in ns: 147 | rep_map[n] = MIN_REPS 148 | get_timing1(label, setup_n, template, 'list', False) 149 | print 'Timing', label, ':', 150 | sys.stdout.flush() 151 | get_timing1(label, setup_n, template, 'blist', False) 152 | 153 | get_timing1(label, setup_n, template, 'list', True) 154 | print 'Timing', label, ':', 155 | sys.stdout.flush() 156 | get_timing1(label, setup_n, template, 'blist', True) 157 | 158 | plot(label, True) 159 | plot(label, False) 160 | html(label) 161 | 162 | def html(label): 163 | fname = 'fig/%s.html' % label 164 | f = open(fname, 'w') 165 | if timing_d[label][0] is None: 166 | setup = 'x = list(range(n))' 167 | else: 168 | setup = timing_d[label][0] 169 | print >>f, ''' 170 | 171 | 172 | BList vs Python list timing results: %s 173 | 174 | 175 |
    176 | Home 177 | | BList 178 | | Poker Sleuth 179 | | Poker Calculator 180 | | Hand Converter 181 | 182 |
    183 | 184 | 185 | 186 |

    187 | Setup: 188 |

    189 | %s
    190 | 
    191 | Timed: 192 |
    193 | %s
    194 | 
    195 | 196 | 197 | ''' % (label, label, label, setup, timing_d[label][1]) 198 | f.close() 199 | 200 | def plot(label, relative): 201 | safe_label = label.replace('_', '\\\\_') 202 | fname = 'gnuplot/%s.gnuplot' % label 203 | f = open(fname, 'w') 204 | if relative: 205 | d = 'fig/relative/' 206 | else: 207 | d = 'fig/absolute/' 208 | os.putenv('GDFONTPATH', '/usr/share/fonts/truetype/msttcorefonts/') 209 | print >>f, """ 210 | set output "%s/%s.png" 211 | set xlabel "List Size (n)" 212 | set title "%s" 213 | #set bmargin 3 214 | 215 | #set pointsize 2 216 | #set view 60, 30, 1.0, 1.0 217 | #set lmargin 12 218 | #set rmargin 10 219 | #set tmargin 1 220 | #set bmargin 5 221 | #set ylabel 0 222 | #set mxtics default 223 | #set mytics default 224 | #set tics out 225 | #set nox2tics 226 | #set noy2tics 227 | #set border 3 228 | #set xtics nomirror autofreq 229 | #set ytics nomirror autofreq 230 | #set key height 1 231 | #set nokey 232 | #unset xdata 233 | #unset y2label 234 | #unset x2label 235 | 236 | #set format "%%g" 237 | set terminal png transparent interlace medium font "./times.ttf" size 640,480 nocrop enhanced xffffff x000000 xff0000 x0000ff xc030c0 xff0000 x000000 238 | set size noratio 1,1 239 | 240 | #set key below height 1 241 | """ % (d, label, safe_label) 242 | 243 | if relative: 244 | print >>f, 'set title "Normalized Execution Times, log-linear scale"' 245 | print >>f, 'set logscale x' 246 | print >>f, 'set yrange [0:*]' 247 | print >>f, 'set yrange [0:200]' 248 | print >>f, 'set ylabel "Execution Time (%)"' 249 | print >>f, 'set key bottom left' 250 | print >>f, 'set mytics 5' 251 | else: 252 | print >>f, 'set title "Raw Execution Times, log-log scale"' 253 | print >>f, 'set key top left' 254 | #print >>f, 'set mytics 10' 255 | print >>f, 'set logscale xy' 256 | print >>f, 'set yrange [0.0001:10]' 257 | print >>f, 'set ylabel "Execution Time"' 258 | print >>f, 'set ytics ("1 ns" 0.000001, "10 ns" 0.00001, "100 ns" 0.0001, "1 us" 0.001, "10 us" 0.01, "100 us" 0.1, "1 ms" 1.0, "10 ms" 10.0, "100 ms" 100.0)' 259 | 260 | if relative: 261 | k = 3 262 | m = 100.0 263 | else: 264 | k = 0 265 | m = 1.0 266 | 267 | print >>f, ('plot "dat/list-%s.dat" using 1:(%f*$%d):(%f*$%d):(%f*$%d) title "list()" with yerrorlines, \\' 268 | % (label, m, k+2, m, k+3, m, k+4)) 269 | print >>f, (' "dat/blist-%s.dat" using 1:(%f*$%d):(%f*$%d):(%f*$%d) title "blist()" with yerrorlines ' 270 | % (label, m, k+2, m, k+3, m, k+4)) 271 | 272 | print >>f 273 | f.flush() 274 | f.close() 275 | if os.system('gnuplot "%s"' % fname): 276 | raise 'Gnuplot failure' 277 | 278 | timing_d = {} 279 | def add_timing(name, auto, stmt): 280 | timing_d[name] = (auto, stmt) 281 | 282 | def run_timing(name): 283 | auto, stmt = timing_d[name] 284 | get_timing(name, auto, stmt) 285 | 286 | def run_all(): 287 | for k in sorted(timing_d): 288 | run_timing(k) 289 | 290 | ######################################################################## 291 | # Tests to run are below here. 292 | # The arguments to add_timing are as follows: 293 | # 1) name of the test 294 | # 2) setup code to run once. "None" means x = list(range(n)) 295 | # 3) code to execute repeatedly in a loop 296 | # 297 | # The following symbols will automatically be defined: 298 | # - n 299 | 300 | add_timing('FIFO', None, """\ 301 | x.insert(0, 0) 302 | del x[0] 303 | """) 304 | 305 | add_timing('LIFO', None, """\ 306 | x.append(0) 307 | del x[-1] 308 | """) 309 | 310 | add_timing('add', None, "x + x") 311 | add_timing('contains', None, "-1 in x") 312 | add_timing('getitem1', None, "x[0]") 313 | add_timing('getitem2', None, "x.__getitem__(0)") 314 | add_timing('getitem3', 'x = range(n)\nm = n//2', "x[m]") 315 | add_timing('getslice', None, "x[1:-1]") 316 | add_timing('forloop', None, "for i in x:\n pass") 317 | add_timing('len', None, "len(x)") 318 | add_timing('eq', None, "x == x") 319 | add_timing('mul10', None, "x * 10") 320 | add_timing('setitem1', None, 'x[0] = 1') 321 | add_timing('setitem3', 'x = range(n)\nm = n//2', 'x[m] = 1') 322 | add_timing('count', None, 'x.count(5)') 323 | add_timing('reverse', None, 'x.reverse()') 324 | add_timing('delslice', None, 'del x[len(x)//4:3*len(x)//4]\nx *= 2') 325 | add_timing('setslice', None, 'x[:] = x') 326 | 327 | add_timing('sort random', 'import random\nx = [random.random() for i in range(n)]', 'y = list(x)\ny.sort()') 328 | add_timing('sort sorted', None, 'y = list(x)\ny.sort()') 329 | add_timing('sort reversed', 'x = range(n)\nx.reverse()', 'y = list(x)\ny.sort()') 330 | 331 | add_timing('init from list', 'x = range(n)', 'y = list(x)') 332 | add_timing('init from tuple', 'x = tuple(range(n))', 'y = list(x)') 333 | add_timing('init from iterable', 'x = xrange(n)', 'y = list(x)') 334 | add_timing('init from same type', None, 'y = list(x)') 335 | 336 | add_timing('shuffle', 'from random import shuffle\nx = list(range(n))', 'shuffle(x)') 337 | 338 | if __name__ == '__main__': 339 | if len(sys.argv) == 1: 340 | run_all() 341 | else: 342 | for name in sys.argv[1:]: 343 | run_timing(name) 344 | -------------------------------------------------------------------------------- /summarize.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os, sys 4 | 5 | names = set() 6 | for fname in os.listdir('dat/'): 7 | if fname.endswith('.dat'): 8 | names.add(fname.split('.')[0].split('-')[1]) 9 | 10 | if len(sys.argv) > 1: 11 | names = set(sys.argv[1:]) 12 | 13 | final = {} 14 | for name in names: 15 | data = {} 16 | with open('dat/128-%s.dat' % name) as f: 17 | lines = f.readlines() 18 | for line in lines: 19 | if line[0] == '#': 20 | continue 21 | line = line.split() 22 | if len(line) < 5: 23 | continue 24 | data[int(line[0])] = float(line[4]) 25 | if not data: 26 | continue 27 | final[name] = sum(data.values())/len(data) 28 | 29 | items = [(t[1], t[0]) for t in final.items()] 30 | items.sort() 31 | hit1 = len(sys.argv) > 1 32 | for v, name in items: 33 | if v >= 1.0 and not hit1: 34 | hit1 = True 35 | print '-'*72 36 | print '%.0f%% %s' % (v*100, name) 37 | 38 | 39 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | PyMODINIT_FUNC 4 | initblist(void); 5 | int main(int argc, char** argv) 6 | { 7 | //Initialize python 8 | Py_Initialize(); 9 | 10 | 11 | initblist(); 12 | 13 | //Get the main module 14 | PyRun_SimpleString("import sys"); 15 | PyRun_SimpleString("sys.path.append('.')"); 16 | PyRun_SimpleString("import fuzz"); 17 | Py_Finalize(); 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /test_blist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from __future__ import print_function 3 | 4 | """ 5 | Copyright 2007-2010 Stutzbach Enterprises, LLC (daniel@stutzbachenterprises.com) 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | 1. Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 2. Redistributions in binary form must reproduce the above 14 | copyright notice, this list of conditions and the following 15 | disclaimer in the documentation and/or other materials provided 16 | with the distribution. 17 | 3. The name of the author may not be used to endorse or promote 18 | products derived from this software without specific prior written 19 | permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 22 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 25 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 29 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 30 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | POSSIBILITY OF SUCH DAMAGE. 32 | 33 | """ 34 | 35 | 36 | import sys 37 | import os 38 | 39 | import unittest, operator 40 | import blist, pickle 41 | from blist import _blist 42 | #BList = list 43 | from blist.test import test_support, list_tests, sortedlist_tests, btuple_tests 44 | from blist.test import sorteddict_tests, test_set 45 | 46 | limit = _blist._limit 47 | n = 512//8 * limit 48 | 49 | class BListTest(list_tests.CommonTest): 50 | type2test = blist.blist 51 | 52 | def test_delmul(self): 53 | x = self.type2test(list(range(10000))) 54 | for i in range(100): 55 | del x[len(x)//4:3*len(x)//4] 56 | x *= 2 57 | 58 | def test_truth(self): 59 | super(BListTest, self).test_truth() 60 | self.assert_(not self.type2test()) 61 | self.assert_(self.type2test([42])) 62 | 63 | def test_identity(self): 64 | self.assert_(self.type2test([]) is not self.type2test([])) 65 | 66 | def test_len(self): 67 | super(BListTest, self).test_len() 68 | self.assertEqual(len(self.type2test()), 0) 69 | self.assertEqual(len(self.type2test([0])), 1) 70 | self.assertEqual(len(self.type2test([0, 1, 2])), 3) 71 | 72 | def test_append2(self): 73 | lst = self.type2test() 74 | t = tuple(range(n)) 75 | for i in range(n): 76 | lst.append(i) 77 | self.assertEqual(tuple(lst), t[:i+1]) 78 | 79 | def test_delstuff(self): 80 | lst = self.type2test(list(range(n))) 81 | t = tuple(range(n)) 82 | x = lst[4:258] 83 | self.assertEqual(tuple(x), tuple(t[4:258])) 84 | x.append(-1) 85 | self.assertEqual(tuple(x), tuple(t[4:258] + (-1,))) 86 | self.assertEqual(tuple(lst), t) 87 | lst[200] = 6 88 | self.assertEqual(tuple(x), tuple(t[4:258] + (-1,))) 89 | self.assertEqual(tuple(lst), tuple(t[0:200] + (6,) + t[201:])) 90 | del lst[200] 91 | self.assertEqual(tuple(lst), tuple(t[0:200] + t[201:])) 92 | 93 | def test_del1(self): 94 | lst2 = self.type2test(list(range(limit+1))) 95 | self.assertEqual(tuple(lst2), tuple(range(limit+1))) 96 | del lst2[1] 97 | del lst2[-1] 98 | self.assertEqual(tuple(lst2), (0,) + tuple(range(2,limit))) 99 | 100 | def test_insert_and_del(self): 101 | lst = self.type2test(list(range(n))) 102 | t = tuple(range(n)) 103 | lst.insert(200, 0) 104 | self.assertEqual(tuple(lst), (t[0:200] + (0,) + t[200:])) 105 | del lst[200:] 106 | self.assertEqual(tuple(lst), tuple(range(200))) 107 | 108 | def test_mul3(self): 109 | lst = self.type2test(list(range(3))) 110 | self.assertEqual(tuple(lst*3), tuple(list(range(3))*3)) 111 | 112 | def test_mul(self): 113 | x = self.type2test(list(range(limit**2))) 114 | for i in range(10): 115 | self.assertEqual(len(x*i), i*limit**2) 116 | 117 | def test_extendspam(self): 118 | a = self.type2test('spam') 119 | a.extend('eggs') 120 | self.assertEqual(list(a), list('spameggs')) 121 | 122 | def test_bigmul1(self): 123 | x = self.type2test([0]) 124 | for i in list(range(290)) + [1000, 10000, 100000, 1000000, 10000000, 2**29]: 125 | self.assertEqual(len(x*i), i) 126 | 127 | def test_badinit(self): 128 | self.assertRaises(TypeError, self.type2test, 0, 0, 0) 129 | 130 | def test_copyself(self): 131 | x = self.type2test(list(range(n))) 132 | x[:] = x 133 | 134 | def test_nohash(self): 135 | x = self.type2test() 136 | d = {} 137 | self.assertRaises(TypeError, d.__setitem__, x, 5) 138 | 139 | def test_collapseboth(self): 140 | x = self.type2test(list(range(512))) 141 | del x[193:318] 142 | 143 | def test_collapseright(self): 144 | x = self.type2test(list(range(512))) 145 | del x[248:318] 146 | 147 | def test_badrepr(self): 148 | class BadExc(Exception): 149 | pass 150 | 151 | class BadRepr: 152 | def __repr__(self): 153 | raise BadExc 154 | 155 | x = self.type2test([BadRepr()]) 156 | self.assertRaises(BadExc, repr, x) 157 | x = self.type2test(list(range(n))) 158 | x.append(BadRepr()) 159 | self.assertRaises(BadExc, repr, x) 160 | 161 | def test_slice0(self): 162 | x = self.type2test(list(range(n))) 163 | x[slice(5,3,1)] = [] 164 | self.assertEqual(x, list(range(n))) 165 | x = self.type2test(list(range(n))) 166 | self.assertRaises(ValueError, x.__setitem__, slice(5,3,1), [5,3,2]) 167 | del x[slice(5,3,1)] 168 | self.assertEqual(x, list(range(n))) 169 | 170 | def test_badindex(self): 171 | x = self.type2test() 172 | self.assertRaises(TypeError, x.__setitem__, 's', 5) 173 | 174 | def test_comparelist(self): 175 | x = self.type2test(list(range(n))) 176 | y = list(range(n-1)) 177 | self.assert_(not (x == y)) 178 | self.assert_(x != y) 179 | self.assert_(not (x < y)) 180 | self.assert_(not (x <= y)) 181 | self.assert_(x > y) 182 | self.assert_(x >= y) 183 | 184 | y = list(range(n)) 185 | self.assert_(x == y) 186 | self.assert_(y == x) 187 | 188 | y[100] = 6 189 | self.assert_(not (x == y)) 190 | self.assert_(x != y) 191 | 192 | def test_compareblist(self): 193 | x = self.type2test(list(range(n))) 194 | y = self.type2test(list(range(n-1))) 195 | self.assert_(not (x == y)) 196 | self.assert_(x != y) 197 | self.assert_(not (x < y)) 198 | self.assert_(not (x <= y)) 199 | self.assert_(x > y) 200 | self.assert_(x >= y) 201 | 202 | y[100] = 6 203 | self.assert_(not (x == y)) 204 | self.assert_(x != y) 205 | 206 | def test_comparetuple(self): 207 | x = self.type2test(list(range(n))) 208 | y = tuple(range(n)) 209 | self.assert_(x != y) 210 | 211 | def test_indexempty(self): 212 | x = self.type2test(list(range(10))) 213 | self.assertRaises(ValueError, x.index, 'spam') 214 | 215 | def test_indexargs(self): 216 | x = self.type2test(list(range(10))) 217 | self.assertEqual(x.index(5,1,-1), 5) 218 | self.assertRaises(ValueError, x.index, 5, -1, -9) 219 | self.assertRaises(ValueError, x.index, 8, 1, 4) 220 | self.assertRaises(ValueError, x.index, 0, 1, 4) 221 | 222 | def test_reversebig(self): 223 | x = self.type2test(list(range(n))) 224 | x.reverse() 225 | self.assertEqual(x, list(range(n-1,-1,-1))) 226 | 227 | def test_badconcat(self): 228 | x = self.type2test() 229 | y = 'foo' 230 | self.assertRaises(TypeError, operator.add, x, y) 231 | 232 | def test_bad_assign(self): 233 | x = self.type2test(list(range(n))) 234 | self.assertRaises(TypeError, x.__setitem__, slice(1,10,2), 5) 235 | 236 | def sort_evil(self, after): 237 | class EvilCompare: 238 | count = 0 239 | num_raises = 0 240 | def __init__(self, x): 241 | self.x = x 242 | def __lt__(self, other): 243 | EvilCompare.count += 1 244 | if EvilCompare.count > after: 245 | EvilCompare.num_raises += 1 246 | raise ValueError 247 | return self.x < other.x 248 | 249 | x = self.type2test(EvilCompare(x) for x in range(n)) 250 | from random import shuffle 251 | shuffle(x) 252 | self.assertRaises(ValueError, x.sort) 253 | self.assertEqual(EvilCompare.num_raises, 1) 254 | x = [a.x for a in x] 255 | x.sort() 256 | self.assertEquals(x, list(range(n))) 257 | 258 | def test_sort_evil_small(self): 259 | self.sort_evil(limit * 5) 260 | 261 | def test_sort_evil_big(self): 262 | self.sort_evil(n + limit) 263 | 264 | def test_big_extend(self): 265 | x = self.type2test([1]) 266 | x.extend(range(n)) 267 | self.assertEqual(tuple(x), (1,) + tuple(range(n))) 268 | 269 | def test_big_getslice(self): 270 | x = self.type2test([0]) * 65536 271 | self.assertEqual(len(x[256:512]), 256) 272 | 273 | def test_modify_original(self): 274 | x = self.type2test(list(range(1024))) 275 | y = x[:] 276 | x[5] = 'z' 277 | self.assertEqual(tuple(y), tuple(range(1024))) 278 | self.assertEqual(x[5], 'z') 279 | self.assertEqual(tuple(x[:5]), tuple(range(5))) 280 | self.assertEqual(tuple(x[6:]), tuple(range(6, 1024))) 281 | 282 | def test_modify_copy(self): 283 | x = self.type2test(list(range(1024))) 284 | y = x[:] 285 | y[5] = 'z' 286 | self.assertEqual(tuple(x), tuple(range(1024))) 287 | self.assertEqual(y[5], 'z') 288 | self.assertEqual(tuple(y[:5]), tuple(range(5))) 289 | self.assertEqual(tuple(y[6:]), tuple(range(6, 1024))) 290 | 291 | def test_bigsort(self): 292 | x = self.type2test(list(range(100000))) 293 | x.sort() 294 | 295 | def test_sort_twice(self): 296 | y = blist.blist(list(range(limit+1))) 297 | for i in range(2): 298 | x = blist.blist(y) 299 | x.sort() 300 | self.assertEqual(tuple(x), tuple(range(limit+1))) 301 | 302 | def test_LIFO(self): 303 | x = blist.blist() 304 | for i in range(1000): 305 | x.append(i) 306 | for j in range(1000-1,-1,-1): 307 | self.assertEqual(x.pop(), j) 308 | 309 | def pickle_test(self, pickler, x): 310 | y = pickler.dumps(x) 311 | z = pickler.loads(y) 312 | self.assertEqual(x, z) 313 | self.assertEqual(repr(x), repr(z)) 314 | 315 | def pickle_tests(self, pickler): 316 | self.pickle_test(pickler, blist.blist()) 317 | self.pickle_test(pickler, blist.blist(list(range(limit)))) 318 | self.pickle_test(pickler, blist.blist(list(range(limit+1)))) 319 | self.pickle_test(pickler, blist.blist(list(range(n)))) 320 | 321 | x = blist.blist([0]) 322 | x *= n 323 | self.pickle_test(pickler, x) 324 | y = blist.blist(x) 325 | y[5] = 'x' 326 | self.pickle_test(pickler, x) 327 | self.pickle_test(pickler, y) 328 | 329 | def test_pickle(self): 330 | self.pickle_tests(pickle) 331 | 332 | def test_types(self): 333 | type(blist.blist()) 334 | type(iter(blist.blist())) 335 | type(iter(reversed(blist.blist()))) 336 | 337 | def test_iterlen_empty(self): 338 | it = iter(blist.blist()) 339 | if hasattr(it, '__next__'): # pragma: no cover 340 | self.assertRaises(StopIteration, it.__next__) 341 | else: # pragma: no cover 342 | self.assertRaises(StopIteration, it.next) 343 | self.assertEqual(it.__length_hint__(), 0) 344 | 345 | def test_sort_floats(self): 346 | x = blist.blist([0.1, 0.2, 0.3]) 347 | x.sort() 348 | 349 | tests = [BListTest, 350 | sortedlist_tests.SortedListTest, 351 | sortedlist_tests.WeakSortedListTest, 352 | sortedlist_tests.SortedSetTest, 353 | sortedlist_tests.WeakSortedSetTest, 354 | btuple_tests.bTupleTest, 355 | sorteddict_tests.sorteddict_test 356 | ] 357 | tests += test_set.test_classes 358 | 359 | def test_suite(): 360 | suite = unittest.TestSuite() 361 | for test in tests: 362 | suite.addTest(unittest.TestLoader().loadTestsFromTestCase(test)) 363 | return suite 364 | 365 | def test_main(verbose=None): 366 | test_support.run_unittest(*tests) 367 | 368 | if __name__ == "__main__": 369 | test_main(verbose=True) 370 | --------------------------------------------------------------------------------