├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.rst ├── bench.py ├── pyskip.py ├── setup.cfg ├── setup.py └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | __pycache__ 4 | env3 5 | env2 6 | build 7 | dist 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Daniel Lindsley. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of pyskip nor the names of its contributors may be used 15 | to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | pyskip 3 | ====== 4 | 5 | A pure Python skiplist implementation. 6 | 7 | A skiplist provides a quickly searchable structure (like a balanced binary 8 | tree) that also updates fairly cheaply (no nasty rebalancing acts). 9 | In other words, it's **awesome**. 10 | 11 | See http://en.wikipedia.org/wiki/Skip_list for more information. 12 | 13 | Written mostly an exercise for myself, it turns out skiplists are really useful. 14 | It also comes with a (mostly-underdocumented) linked-list implementation 15 | (+ a sorted variant), if that's useful. 16 | 17 | 18 | Requirements 19 | ============ 20 | 21 | * Python 3.3+ (should work on Python 2.6+ as well, as well as PyPy 2.0+) 22 | * ``nose>=1.30`` for running unittests 23 | 24 | 25 | Usage 26 | ===== 27 | 28 | Using it looks like: 29 | 30 | >>> import skiplist 31 | >>> skip = skiplist.Skiplist() 32 | >>> len(skip) 33 | 0 34 | >>> 6 in skip 35 | False 36 | >>> skip.insert(0) 37 | >>> skip.insert(7) 38 | >>> skip.insert(3) 39 | >>> skip.insert(6) 40 | >>> skip.insert(245) 41 | >>> len(skip) 42 | 5 43 | >>> 6 in skip 44 | True 45 | >>> skip.remove(245) 46 | >>> len(skip) 47 | 4 48 | >>> skip.find(3) 49 | 50 | 51 | 52 | Performance 53 | =========== 54 | 55 | Performance is alright, though I'm sure there's room for improvement. See the 56 | ``bench.py`` script for more information. 57 | 58 | 59 | Running Tests 60 | ============= 61 | 62 | Run ``pip install nose`` (preferrably within a virtualenv) to install nose. 63 | 64 | Then run ``nosetests -s -v tests.py`` to exercise the full suite. 65 | 66 | 67 | TODO 68 | ==== 69 | 70 | * A more performant implementation of ``remove`` (still O(N)) 71 | * More performance testing 72 | 73 | * Loading data seems slow 74 | 75 | 76 | Meta 77 | ==== 78 | 79 | :author: Daniel Lindsley 80 | :license: BSD 81 | :version: 0.9.0 82 | -------------------------------------------------------------------------------- /bench.py: -------------------------------------------------------------------------------- 1 | import random 2 | import time 3 | 4 | import pyskip 5 | import bintrees 6 | 7 | 8 | MAX_LOAD = 500 9 | 10 | 11 | def load_skiplist(): 12 | skip = pyskip.Skiplist() 13 | 14 | for i in range(MAX_LOAD): 15 | print(i, end='\r') 16 | skip.insert(i) 17 | 18 | print() 19 | return skip 20 | 21 | 22 | def load_sorted_list(): 23 | the_list = pyskip.SortedLinkedList() 24 | 25 | for i in range(MAX_LOAD): 26 | the_list.insert(pyskip.SingleNode(value=i)) 27 | 28 | return the_list 29 | 30 | 31 | def load_binary_tree(): 32 | tree = bintrees.AVLTree() 33 | 34 | for i in range(MAX_LOAD): 35 | tree.insert(i, True) 36 | 37 | return tree 38 | 39 | 40 | def contains_skiplist(skip, randoms): 41 | for rand in randoms: 42 | try: 43 | assert rand in skip 44 | except AssertionError: 45 | print("{0} not in the skiplist?".format(rand)) 46 | 47 | 48 | def contains_sorted_list(the_list, randoms): 49 | for rand in randoms: 50 | try: 51 | assert rand in the_list 52 | except AssertionError: 53 | print("{0} not in the list?".format(rand)) 54 | 55 | 56 | def contains_binary_tree(tree, randoms): 57 | for rand in randoms: 58 | try: 59 | assert rand in tree 60 | except AssertionError: 61 | print("{0} not in the tree?".format(rand)) 62 | 63 | 64 | def inserts_skiplist(skip, randoms): 65 | for rand in randoms: 66 | skip.insert(rand) 67 | 68 | 69 | def inserts_sorted_list(the_list, randoms): 70 | for rand in randoms: 71 | the_list.insert(pyskip.SingleNode(value=rand)) 72 | 73 | 74 | def inserts_binary_tree(tree, randoms): 75 | for rand in randoms: 76 | tree.insert(rand, True) 77 | 78 | 79 | def run(): 80 | start = time.time() 81 | skip = load_skiplist() 82 | end = time.time() 83 | print("Skiplist loaded in {0} seconds.".format(end - start)) 84 | 85 | start = time.time() 86 | the_list = load_sorted_list() 87 | end = time.time() 88 | print("Sorted Linked List loaded in {0} seconds.".format(end - start)) 89 | 90 | start = time.time() 91 | tree = load_binary_tree() 92 | end = time.time() 93 | print("Binary tree loaded in {0} seconds.".format(end - start)) 94 | 95 | print() 96 | 97 | randoms = [random.randint(0, MAX_LOAD - 1) for i in range(20)] 98 | 99 | start = time.time() 100 | contains_skiplist(skip, randoms) 101 | end = time.time() 102 | print("Skiplist checked contains in {0} seconds.".format(end - start)) 103 | 104 | start = time.time() 105 | contains_sorted_list(the_list, randoms) 106 | end = time.time() 107 | print("Sorted Linked List checked contains in {0} seconds.".format(end - start)) 108 | 109 | start = time.time() 110 | contains_binary_tree(tree, randoms) 111 | end = time.time() 112 | print("Binary tree checked contains in {0} seconds.".format(end - start)) 113 | 114 | print() 115 | 116 | randoms = [random.randint(0, MAX_LOAD - 1) for i in range(50)] 117 | 118 | start = time.time() 119 | inserts_skiplist(skip, randoms) 120 | end = time.time() 121 | print("Skiplist inserted 50 new values in {0} seconds.".format(end - start)) 122 | 123 | start = time.time() 124 | inserts_sorted_list(the_list, randoms) 125 | end = time.time() 126 | print("Sorted Linked List inserted 50 new values in {0} seconds.".format(end - start)) 127 | 128 | start = time.time() 129 | inserts_binary_tree(tree, randoms) 130 | end = time.time() 131 | print("Binary tree inserted 50 new values in {0} seconds.".format(end - start)) 132 | 133 | 134 | if __name__ == '__main__': 135 | run() 136 | -------------------------------------------------------------------------------- /pyskip.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | __author__ = 'Daniel Lindsley' 5 | __license__ = 'BSD' 6 | __version__ = (0, 9, 0) 7 | 8 | 9 | class InsertError(Exception): 10 | pass 11 | 12 | 13 | class SingleNode(object): 14 | """ 15 | A simple, singly linked list node. 16 | """ 17 | def __init__(self, value=None, next=None): 18 | self.value = value 19 | self.next = next 20 | 21 | def __repr__(self): 22 | return "{0}: {1}".format(self.__class__.__name__, self.value) 23 | 24 | def __lt__(self, other): 25 | return self.value < other.value 26 | 27 | def __le__(self, other): 28 | return self.value <= other.value 29 | 30 | def __eq__(self, other): 31 | return self.value == other.value 32 | 33 | def __ge__(self, other): 34 | return self.value >= other.value 35 | 36 | def __gt__(self, other): 37 | return self.value > other.value 38 | 39 | def __ne__(self, other): 40 | return self.value != other.value 41 | 42 | 43 | class LinkedList(object): 44 | """ 45 | A simple linked list. 46 | """ 47 | def __init__(self): 48 | self.head = None 49 | 50 | def __str__(self): 51 | return 'LinkedList: {0} items starting with {1}'.format( 52 | len(self), 53 | self.head.value 54 | ) 55 | 56 | def __iter__(self): 57 | self.current = self.head 58 | return self 59 | 60 | def __next__(self): 61 | if self.current is None: 62 | raise StopIteration() 63 | 64 | cur = self.current 65 | self.current = cur.next 66 | return cur 67 | # For Python 2.X-compat. 68 | next = __next__ 69 | 70 | def __len__(self): 71 | count = 0 72 | 73 | for node in self: 74 | count += 1 75 | 76 | return count 77 | 78 | def __getitem__(self, offset): 79 | if offset < 0: 80 | raise IndexError( 81 | "Can't do negative offsets with a singly linked list." 82 | ) 83 | 84 | for i, node in enumerate(self): 85 | if offset == i: 86 | return node 87 | 88 | raise IndexError("Index '{0}' out of range.".format(offset)) 89 | 90 | def __contains__(self, value): 91 | return self.find(value) is not None 92 | 93 | def find(self, value): 94 | for node in self: 95 | if node.value == value: 96 | # We found it! Yay! Bail early. 97 | return node 98 | 99 | return None 100 | 101 | def insert_first(self, insert_node): 102 | """ 103 | Inserts the new node at the beginning of the list. 104 | """ 105 | insert_node.next = self.head 106 | self.head = insert_node 107 | return insert_node 108 | 109 | def insert_after(self, existing_node, insert_node): 110 | """ 111 | Inserts the new node after a given node in the list. 112 | """ 113 | insert_node.next = existing_node.next 114 | existing_node.next = insert_node 115 | return insert_node 116 | 117 | def remove_first(self): 118 | """ 119 | Removes the first node (if any) in the list. 120 | """ 121 | if self.head is None: 122 | return None 123 | 124 | old_head = self.head 125 | self.head = self.head.next 126 | return old_head 127 | 128 | def remove_after(self, existing_node): 129 | """ 130 | Removes the node (if any) that follows the provided node in the list. 131 | """ 132 | if existing_node.next is None: 133 | return None 134 | 135 | old_next = existing_node.next 136 | existing_node.next = existing_node.next.next 137 | return old_next 138 | 139 | 140 | class SortedLinkedList(LinkedList): 141 | """ 142 | A linked list that maintains the correct sort order. 143 | """ 144 | def find(self, value): 145 | # We can be more efficient here, since we know we're sorted. 146 | for node in self: 147 | if node.value == value: 148 | # We found it! Yay! Bail early. 149 | return node 150 | 151 | if node.value > value: 152 | # We've exceeded the value & we didn't already come across it. 153 | # Must not be here. Bail. 154 | return None 155 | 156 | return None 157 | 158 | def insert_after(self, existing_node, new_node): 159 | if not existing_node <= new_node: 160 | raise InsertError("Invalid placement for the new node.") 161 | 162 | if existing_node.next and not new_node <= existing_node.next: 163 | raise InsertError("Invalid placement for the new node.") 164 | 165 | return super(SortedLinkedList, self).insert_after( 166 | existing_node, 167 | new_node 168 | ) 169 | 170 | def insert_first(self, new_node): 171 | if self.head and not new_node <= self.head: 172 | raise InsertError("Invalid placement for the new node.") 173 | 174 | return super(SortedLinkedList, self).insert_first(new_node) 175 | 176 | def insert(self, new_node): 177 | if not self.head or new_node < self.head: 178 | self.insert_first(new_node) 179 | return 180 | 181 | previous = self.head 182 | 183 | for node in self: 184 | if previous <= new_node <= node: 185 | self.insert_after(previous, new_node) 186 | return 187 | 188 | previous = node 189 | 190 | return self.insert_after(previous, new_node) 191 | 192 | def remove(self, remove_node): 193 | previous = self.head 194 | 195 | for node in self: 196 | if node == remove_node: 197 | return self.remove_after(previous) 198 | 199 | previous = node 200 | 201 | return None 202 | 203 | 204 | class SkiplistNode(SingleNode): 205 | def __init__(self, value=None, next=None, down=None): 206 | super(SkiplistNode, self).__init__(value=value, next=next) 207 | self.down = down 208 | 209 | 210 | class Skiplist(object): 211 | """ 212 | Implements a basic skiplist. 213 | 214 | A skiplist provides a quickly searchable structure (like a balanced binary 215 | tree) that also updates fairly cheaply (no nasty rebalancing acts). 216 | 217 | In other words, it's awesome. 218 | 219 | See http://en.wikipedia.org/wiki/Skip_list for more information. 220 | """ 221 | list_class = SortedLinkedList 222 | node_class = SkiplistNode 223 | max_layers = 32 224 | 225 | def __init__(self, list_class=None, node_class=None, max_layers=None): 226 | if list_class is not None: 227 | self.list_class = list_class 228 | 229 | if node_class is not None: 230 | self.node_class = node_class 231 | 232 | if max_layers is not None: 233 | self.max_layers = max_layers 234 | 235 | self.layers = [ 236 | self.list_class() 237 | ] 238 | 239 | def __str__(self): 240 | return 'Skiplist: {0} items'.format(len(self.layers[-1])) 241 | 242 | def __len__(self): 243 | return len(self.layers[-1]) 244 | 245 | def __contains__(self, value): 246 | return self.find(value) is not None 247 | 248 | def __iter__(self): 249 | return iter(self.layers[-1]) 250 | 251 | def generate_height(self): 252 | """ 253 | Generates a random height (between ``2`` & ``self.max_layers``) to fill 254 | in. 255 | """ 256 | return random.randint(2, self.max_layers) 257 | 258 | def find(self, value): 259 | """ 260 | Looks for a given value within the skiplist. 261 | 262 | Returns the node if found, ``None`` if the value was not found. 263 | """ 264 | layer_offset = 0 265 | current = self.layers[layer_offset].head 266 | is_first = True 267 | 268 | while True: 269 | if current is None: 270 | # We've exhausted all options. Bail out. 271 | break 272 | 273 | if current.value == value: 274 | # We found it! Bail out. 275 | return current 276 | elif current.value > value: 277 | if is_first: 278 | # We're at the beginning of the list, but there may be 279 | # levels below with numbers befor this one. 280 | layer_offset += 1 281 | 282 | if layer_offset <= len(self.layers) - 1: 283 | current = self.layers[layer_offset].head 284 | continue 285 | else: 286 | break 287 | 288 | if current.next and current.next.value <= value: 289 | # The next node in the current layer might match. 290 | current = current.next 291 | is_first = False 292 | continue 293 | else: 294 | # Either the next node is too high or we reached the end of that 295 | # layer. Attempt to descend. 296 | if current.down is not None: 297 | current = current.down 298 | layer_offset += 1 299 | continue 300 | else: 301 | break 302 | 303 | return None 304 | 305 | def insert(self, value, **kwargs): 306 | num_layers = len(self.layers) 307 | height = self.generate_height() 308 | 309 | # Make sure we have enough layers to accommodate. 310 | if height > num_layers: 311 | for i in range(num_layers, height): 312 | self.layers.insert(0, self.list_class()) 313 | 314 | down = None 315 | 316 | for i in range(height): 317 | new_node = self.node_class(value=value, **kwargs) 318 | self.layers[num_layers - (i + 1)].insert(new_node) 319 | new_node.down = down 320 | down = new_node 321 | 322 | def remove(self, value): 323 | node = self.find(value) 324 | 325 | if node is None: 326 | return None 327 | 328 | # FIXME: This is bad. 329 | # The other operations can be O(log N), but without knowing what 330 | # layer offset we found the node in, we can't properly remove it 331 | # without reaching in. 332 | # For now, let's settle on something that works but is slow. 333 | for layer in self.layers: 334 | layer.remove(node) 335 | 336 | def debug(self, column_width=4): 337 | """ 338 | Prints a representation of the skiplist's structure. 339 | 340 | Default ``column_width`` parameter is ``4``. 341 | """ 342 | column_format_string = "{:" + str(column_width) + "} " 343 | full = self.layers[-1] 344 | 345 | for layer_offset, layer in enumerate(self.layers): 346 | print("{:<3}".format(layer_offset), end=": ") 347 | 348 | for node in full: 349 | if node.value in layer: 350 | print(column_format_string.format(node.value), end="") 351 | else: 352 | print(" ", end="") 353 | 354 | print() 355 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup 3 | except ImportError: 4 | from distutils.core import setup 5 | 6 | 7 | setup( 8 | name="pyskip", 9 | version="0.9.0", 10 | description="A pure Python skiplist.", 11 | author='Daniel Lindsley', 12 | author_email='daniel@toastdriven.com', 13 | long_description=open('README.rst', 'r').read(), 14 | py_modules=[ 15 | 'pyskip', 16 | ], 17 | classifiers=[ 18 | 'Development Status :: 5 - Production/Stable', 19 | 'Intended Audience :: Developers', 20 | 'License :: OSI Approved :: BSD License', 21 | 'Operating System :: OS Independent', 22 | 'Programming Language :: Python', 23 | 'Programming Language :: Python :: 2', 24 | 'Programming Language :: Python :: 3', 25 | ], 26 | url='https://github.com/toastdriven/pyskip/', 27 | license='BSD' 28 | ) 29 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | try: 2 | import unittest2 as unittest 3 | except ImportError: 4 | import unittest 5 | 6 | import pyskip 7 | 8 | 9 | class TestNode(pyskip.SingleNode): 10 | def __init__(self, *args, **kwargs): 11 | super(TestNode, self).__init__(*args, **kwargs) 12 | 13 | @property 14 | def name(self): 15 | return 'test{0}'.format(self.value) 16 | 17 | 18 | class SingleNodeTestCase(unittest.TestCase): 19 | def test_init(self): 20 | n1 = pyskip.SingleNode() 21 | self.assertEqual(n1.value, None) 22 | self.assertEqual(n1.next, None) 23 | 24 | n2 = pyskip.SingleNode(value='whatever') 25 | self.assertEqual(n2.value, 'whatever') 26 | self.assertEqual(n2.next, None) 27 | 28 | n3 = pyskip.SingleNode(value='another', next=n2) 29 | self.assertEqual(n3.value, 'another') 30 | self.assertEqual(n3.next, n2) 31 | 32 | 33 | class LinkedListTestCase(unittest.TestCase): 34 | def setUp(self): 35 | super(LinkedListTestCase, self).setUp() 36 | self.ll = pyskip.LinkedList() 37 | 38 | self.head = pyskip.SingleNode(value=0) 39 | self.first = pyskip.SingleNode(value=2) 40 | self.second = pyskip.SingleNode(value=5) 41 | self.third = pyskip.SingleNode(value=6) 42 | 43 | self.ll.insert_first(self.head) 44 | self.ll.insert_after(self.head, self.first) 45 | self.ll.insert_after(self.first, self.second) 46 | self.ll.insert_after(self.second, self.third) 47 | 48 | def test_init(self): 49 | ll = pyskip.LinkedList() 50 | self.assertEqual(ll.head, None) 51 | 52 | def test_str(self): 53 | output = str(self.ll) 54 | self.assertEqual(output, 'LinkedList: 4 items starting with 0') 55 | 56 | def test_len(self): 57 | self.assertEqual(len(self.ll), 4) 58 | self.assertEqual(len(pyskip.LinkedList()), 0) 59 | 60 | def test_insert_first(self): 61 | ll = pyskip.LinkedList() 62 | self.assertEqual(ll.head, None) 63 | self.assertEqual(len(ll), 0) 64 | 65 | world = pyskip.SingleNode(value='world') 66 | ll.insert_first(world) 67 | self.assertEqual(ll.head.value, 'world') 68 | self.assertEqual(ll.head.next, None) 69 | self.assertEqual(len(ll), 1) 70 | 71 | hello = pyskip.SingleNode(value='Hello') 72 | ll.insert_first(hello) 73 | self.assertEqual(ll.head.value, 'Hello') 74 | self.assertEqual(ll.head.next, world) 75 | self.assertEqual(len(ll), 2) 76 | 77 | def test_insert_after(self): 78 | ll = pyskip.LinkedList() 79 | 80 | hello = pyskip.SingleNode(value='Hello') 81 | world = pyskip.SingleNode(value='world') 82 | there = pyskip.SingleNode(value='there') 83 | the_end = pyskip.SingleNode(value='the end') 84 | 85 | ll.insert_first(hello) 86 | self.assertEqual(ll.head.value, 'Hello') 87 | self.assertEqual(ll.head.next, None) 88 | self.assertEqual(len(ll), 1) 89 | 90 | ll.insert_after(hello, world) 91 | self.assertEqual(ll.head.value, 'Hello') 92 | self.assertEqual(ll.head.next, world) 93 | self.assertEqual(len(ll), 2) 94 | 95 | ll.insert_after(hello, there) 96 | self.assertEqual(ll.head.value, 'Hello') 97 | self.assertEqual(ll.head.next, there) 98 | self.assertEqual(ll.head.next.next, world) 99 | self.assertEqual(len(ll), 3) 100 | 101 | ll.insert_after(world, the_end) 102 | self.assertEqual(ll.head.value, 'Hello') 103 | self.assertEqual(ll.head.next, there) 104 | self.assertEqual(ll.head.next.next, world) 105 | self.assertEqual(ll.head.next.next.next, the_end) 106 | self.assertEqual(len(ll), 4) 107 | 108 | def test_remove_first(self): 109 | self.assertEqual(len(self.ll), 4) 110 | 111 | self.assertEqual(self.ll.remove_first().value, 0) 112 | self.assertEqual(len(self.ll), 3) 113 | 114 | self.assertEqual(self.ll.remove_first().value, 2) 115 | self.assertEqual(len(self.ll), 2) 116 | 117 | self.assertEqual(self.ll.remove_first().value, 5) 118 | self.assertEqual(len(self.ll), 1) 119 | 120 | self.assertEqual(self.ll.remove_first().value, 6) 121 | self.assertEqual(len(self.ll), 0) 122 | 123 | self.assertEqual(self.ll.remove_first(), None) 124 | self.assertEqual(len(self.ll), 0) 125 | 126 | def test_remove_after(self): 127 | self.assertEqual(len(self.ll), 4) 128 | 129 | self.assertEqual(self.ll.remove_after(self.first).value, 5) 130 | self.assertEqual(len(self.ll), 3) 131 | 132 | self.assertEqual(self.ll.remove_after(self.head).value, 2) 133 | self.assertEqual(len(self.ll), 2) 134 | 135 | self.assertEqual(self.ll.remove_after(self.head).value, 6) 136 | self.assertEqual(len(self.ll), 1) 137 | 138 | self.assertEqual(self.ll.remove_after(self.head), None) 139 | self.assertEqual(len(self.ll), 1) 140 | 141 | def test_iter(self): 142 | the_list = iter(self.ll) 143 | 144 | self.assertEqual(next(the_list).value, 0) 145 | self.assertEqual(next(the_list).value, 2) 146 | self.assertEqual(next(the_list).value, 5) 147 | self.assertEqual(next(the_list).value, 6) 148 | 149 | with self.assertRaises(StopIteration): 150 | next(the_list) 151 | 152 | def test_offsets(self): 153 | self.assertEqual(self.ll[0].value, 0) 154 | self.assertEqual(self.ll[1].value, 2) 155 | self.assertEqual(self.ll[2].value, 5) 156 | self.assertEqual(self.ll[3].value, 6) 157 | 158 | with self.assertRaises(IndexError): 159 | self.ll[-1] 160 | 161 | with self.assertRaises(IndexError): 162 | self.ll[5] 163 | 164 | def test_contains(self): 165 | self.assertTrue(0 in self.ll) 166 | self.assertTrue(5 in self.ll) 167 | self.assertTrue(6 in self.ll) 168 | self.assertFalse(-1 in self.ll) 169 | self.assertFalse(4 in self.ll) 170 | 171 | 172 | class SortedLinkedListTestCase(unittest.TestCase): 173 | def setUp(self): 174 | super(SortedLinkedListTestCase, self).setUp() 175 | self.sll = pyskip.SortedLinkedList() 176 | self.sll.insert(pyskip.SingleNode(value=5)) 177 | self.sll.insert(pyskip.SingleNode(value=6)) 178 | self.sll.insert(pyskip.SingleNode(value=2)) 179 | self.sll.insert(pyskip.SingleNode(value=0)) 180 | self.sll.insert(pyskip.SingleNode(value=3)) 181 | self.sll.insert(pyskip.SingleNode(value=2)) 182 | 183 | def test_insert(self): 184 | self.assertEqual(len(self.sll), 6) 185 | the_list = iter(self.sll) 186 | 187 | # Should come out in the correct order. 188 | self.assertEqual(next(the_list).value, 0) 189 | self.assertEqual(next(the_list).value, 2) 190 | self.assertEqual(next(the_list).value, 2) 191 | self.assertEqual(next(the_list).value, 3) 192 | self.assertEqual(next(the_list).value, 5) 193 | self.assertEqual(next(the_list).value, 6) 194 | 195 | def test_remove(self): 196 | self.assertEqual(len(self.sll), 6) 197 | self.assertTrue(self.sll.remove(pyskip.SingleNode(value=3))) 198 | self.assertEqual(len(self.sll), 5) 199 | self.assertTrue(self.sll.remove(pyskip.SingleNode(value=6))) 200 | self.assertEqual(len(self.sll), 4) 201 | # Remove the first ``2``. 202 | self.assertTrue(self.sll.remove(pyskip.SingleNode(value=2))) 203 | self.assertEqual(len(self.sll), 3) 204 | # Remove the second ``2``. 205 | self.assertTrue(self.sll.remove(pyskip.SingleNode(value=2))) 206 | self.assertEqual(len(self.sll), 2) 207 | # Nope, there are no more ``2``s there. 208 | self.assertFalse(self.sll.remove(pyskip.SingleNode(value=2))) 209 | self.assertEqual(len(self.sll), 2) 210 | 211 | the_list = iter(self.sll) 212 | 213 | # Should come out in the correct order. 214 | self.assertEqual(next(the_list).value, 0) 215 | self.assertEqual(next(the_list).value, 5) 216 | 217 | def test_contains(self): 218 | self.assertTrue(0 in self.sll) 219 | self.assertTrue(5 in self.sll) 220 | self.assertTrue(6 in self.sll) 221 | self.assertFalse(-1 in self.sll) 222 | self.assertFalse(4 in self.sll) 223 | 224 | 225 | class SkiplistTestCase(unittest.TestCase): 226 | def setUp(self): 227 | super(SkiplistTestCase, self).setUp() 228 | self.skip = pyskip.Skiplist() 229 | 230 | # Fake a list. 231 | layer_3 = pyskip.SortedLinkedList() 232 | layer_3.insert(pyskip.SkiplistNode(value=3)) 233 | layer_3.insert(pyskip.SkiplistNode(value=4)) 234 | layer_3.insert(pyskip.SkiplistNode(value=7)) 235 | layer_3.insert(pyskip.SkiplistNode(value=12)) 236 | layer_3.insert(pyskip.SkiplistNode(value=13)) 237 | layer_3.insert(pyskip.SkiplistNode(value=14)) 238 | layer_3.insert(pyskip.SkiplistNode(value=17)) 239 | 240 | layer_2 = pyskip.SortedLinkedList() 241 | layer_2.insert(pyskip.SkiplistNode(value=3, down=layer_3[0])) 242 | layer_2.insert(pyskip.SkiplistNode(value=7, down=layer_3[2])) 243 | layer_2.insert(pyskip.SkiplistNode(value=14, down=layer_3[5])) 244 | layer_2.insert(pyskip.SkiplistNode(value=17, down=layer_3[6])) 245 | 246 | layer_1 = pyskip.SortedLinkedList() 247 | # First element must always be full-height. 248 | layer_1.insert(pyskip.SkiplistNode(value=7, down=layer_2[1])) 249 | layer_1.insert(pyskip.SkiplistNode(value=17, down=layer_2[3])) 250 | 251 | # Cheat a little. 252 | # But this ensures the ``find`` tests can run correctly without 253 | # ``insert`` & friends. 254 | self.skip.layers = [ 255 | layer_1, 256 | layer_2, 257 | layer_3, 258 | ] 259 | 260 | def test_find(self): 261 | self.assertEqual(self.skip.find(3).value, 3) 262 | self.assertEqual(self.skip.find(4).value, 4) 263 | self.assertEqual(self.skip.find(7).value, 7) 264 | self.assertEqual(self.skip.find(13).value, 13) 265 | 266 | # Reached the end. 267 | self.assertEqual(self.skip.find(25), None) 268 | # Before the head. 269 | self.assertEqual(self.skip.find(-1), None) 270 | 271 | # An empty skiplist shouldn't fail either. 272 | empty = pyskip.Skiplist() 273 | self.assertEqual(empty.find(6), None) 274 | 275 | def test_contains(self): 276 | self.assertTrue(3 in self.skip) 277 | self.assertTrue(4 in self.skip) 278 | self.assertTrue(7 in self.skip) 279 | self.assertTrue(13 in self.skip) 280 | 281 | # Reached the end. 282 | self.assertFalse(25 in self.skip) 283 | # Before the head. 284 | self.assertFalse(-1 in self.skip) 285 | 286 | # An empty skiplist shouldn't fail either. 287 | empty = pyskip.Skiplist() 288 | self.assertFalse(6 in empty) 289 | 290 | def test_len(self): 291 | self.assertEqual(len(self.skip), 7) 292 | 293 | empty = pyskip.Skiplist() 294 | self.assertEqual(len(empty), 0) 295 | 296 | def test_iter(self): 297 | the_list = iter(self.skip) 298 | 299 | self.assertEqual(next(the_list).value, 3) 300 | self.assertEqual(next(the_list).value, 4) 301 | self.assertEqual(next(the_list).value, 7) 302 | self.assertEqual(next(the_list).value, 12) 303 | self.assertEqual(next(the_list).value, 13) 304 | self.assertEqual(next(the_list).value, 14) 305 | self.assertEqual(next(the_list).value, 17) 306 | 307 | with self.assertRaises(StopIteration): 308 | next(the_list) 309 | 310 | def test_insert(self): 311 | self.assertFalse(6 in self.skip) 312 | self.skip.insert(6) 313 | self.assertTrue(6 in self.skip) 314 | 315 | def test_remove(self): 316 | self.assertTrue(4 in self.skip) 317 | self.skip.remove(4) 318 | self.assertFalse(4 in self.skip) 319 | --------------------------------------------------------------------------------