├── CHANGES.rst ├── LICENSE ├── MANIFEST ├── README.rst ├── setup.py ├── skipdict.c ├── skiplist.c ├── skiplist.h └── tests.py /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Changes 2 | ------- 3 | 4 | 1.0 (2014-09-26) 5 | ---------------- 6 | 7 | - Initial public release. 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Malthe Borch 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | skiplist.c 2 | skiplist.h 3 | skipdict.c 4 | setup.py 5 | tests.py 6 | README.rst 7 | CHANGES.rst 8 | LICENSE 9 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | SkipDict 2 | ======== 3 | 4 | A skip dict is a Python dictionary which is permanently sorted by 5 | value. This package provides a fast, idiomatic implementation written 6 | in C with an extensive test suite. 7 | 8 | The data structure uses a `skip list 9 | `_ internally. 10 | 11 | An example use is a leaderboard where the skip dict provides 12 | logarithmic access to the score and ranking of each user, as well as 13 | efficient iteration in either direction from any node. 14 | 15 | Compatible with Python 2.7+ and Python 3.3+. 16 | 17 | 18 | Usage 19 | ----- 20 | 21 | The skip dict works just like a normal dictionary except it maps only 22 | to floating point values:: 23 | 24 | from skipdict import SkipDict 25 | 26 | skipdict = SkipDict(maxlevel=4) 27 | 28 | skipdict['foo'] = 1.0 29 | skipdict['bar'] = 2.0 30 | 31 | The ``SkipDict`` optionally takes a dictionary or a sequence of 32 | ``(key, value)`` pairs:: 33 | 34 | skipdict = SkipDict({'foo': 1.0, 'bar': 2.0}) 35 | skipdict = SkipDict(('foo', 1.0), ('bar', 1.0), ('bar', 1.0)) 36 | 37 | Note that duplicates are automatically aggregated to a sum. To 38 | illustrate this, we can count the occurrences of letters in a text:: 39 | 40 | skipdict = SkipDict( 41 | (char, 1) for char in 42 | "Everything popular is wrong. - Oscar Wilde" 43 | ) 44 | 45 | # The most frequent letter is a space. 46 | skipdict.keys()[-1] == " " 47 | 48 | The ``skipdict`` is sorted by value which means that iteration and 49 | standard mapping protocol methods such as ``keys()``, ``values()`` and 50 | ``items()`` return items in sorted order. 51 | 52 | Each of these methods have been extended with optional range arguments 53 | ``min`` and ``max`` which limit iteration based on value. In addition, 54 | the iterator objects support the item and slice protocols: 55 | 56 | >>> skipdict.keys(min=2.0)[0] 57 | 'bar' 58 | >>> skipdict.keys(max=2.0)[1:] 59 | ['bar'] 60 | 61 | Note that the methods always return an iterator. Use ``list`` to 62 | expand to a sequence: 63 | 64 | >>> iterator = skipdict.keys() 65 | >>> list(iterator) 66 | ['bar'] 67 | 68 | The ``index(value)`` method returns the first key that has exactly the 69 | required value. If the value is not found then a ``KeyError`` 70 | exception is raised. 71 | 72 | >>> skipdict.index(2.0) 73 | 'bar' 74 | 75 | 76 | Alternatives 77 | ------------ 78 | 79 | Francesco Romani wrote `pyskiplist 80 | `_ which also provides an 81 | implementation of the skip list datastructure for CPython written 82 | in C. 83 | 84 | Paul Colomiets wrote `sortedsets 85 | `_ which is an implementation 86 | in pure Python. The randomized test cases were adapted from this 87 | package. 88 | 89 | 90 | License 91 | ------- 92 | 93 | Copyright (c) 204 Malthe Borch 94 | 95 | This software is provided "as-is" under the BSD License. 96 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools.extension import Extension 4 | from setuptools.command.build_ext import build_ext 5 | from setuptools import setup 6 | 7 | 8 | def read(fname): 9 | with open(os.path.join(os.path.dirname(__file__), fname)) as f: 10 | return f.read() 11 | 12 | ext_modules = [ 13 | Extension( 14 | name='skipdict', 15 | sources=['skipdict.c', 'skiplist.c'], 16 | depends=['skiplist.h'], 17 | ), 18 | ] 19 | 20 | 21 | setup(name="skipdict", 22 | version="1.0", 23 | description="Python dictionary object permanently sorted by value.", 24 | long_description="\n\n".join((read('README.rst'), read('CHANGES.rst'))), 25 | license="BSD", 26 | author="Malthe Borch", 27 | author_email="mborch@gmail.com", 28 | cmdclass=dict(build_ext=build_ext), 29 | ext_modules=ext_modules, 30 | classifiers=[ 31 | 'Development Status :: 4 - Beta', 32 | 'Intended Audience :: Developers', 33 | 'License :: OSI Approved :: MIT License', 34 | 'Programming Language :: C', 35 | 'Programming Language :: Python :: 2.7', 36 | "Programming Language :: Python :: 3.3", 37 | "Programming Language :: Python :: 3.4", 38 | 'Topic :: Software Development :: Libraries :: Python Modules', 39 | ], 40 | url="https://github.com/malthe/skipdict", 41 | zip_safe=False, 42 | test_suite="tests", 43 | py_modules=['skipdict']) 44 | -------------------------------------------------------------------------------- /skipdict.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "skiplist.h" 7 | 8 | #define MAXLEVEL 32 9 | #define P 0.25 10 | 11 | #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) 12 | typedef int Py_ssize_t; 13 | #define PY_SSIZE_T_MAX INT_MAX 14 | #define PY_SSIZE_T_MIN INT_MIN 15 | #endif 16 | 17 | #if PY_MAJOR_VERSION >= 3 18 | inline void PyString_ConcatAndDel(PyObject** lhs, PyObject* rhs) 19 | { 20 | PyObject* s = PyUnicode_Concat(*lhs, rhs); 21 | Py_DECREF(lhs); 22 | *lhs = s; 23 | } 24 | inline void PyString_Concat(PyObject** lhs, PyObject* rhs) 25 | { 26 | PyObject* s = PyUnicode_Concat(*lhs, rhs); 27 | Py_DECREF(lhs); 28 | *lhs = s; 29 | } 30 | #define PySliceObject PyObject 31 | #define PyInt_FromLong PyLong_FromLong 32 | #define PyInt_AsLong PyLong_AsLong 33 | #define PyString_FromString PyUnicode_FromString 34 | #define PyString_FromFormat PyUnicode_FromFormat 35 | #define PyString_FromStringAndSize PyUnicode_FromStringAndSize 36 | #define PyString_AsString PyUnicode_AsASCIIString 37 | #define _PyString_Join PyUnicode_Join 38 | #define INITERROR return NULL 39 | #else 40 | #define INITERROR return 41 | #endif 42 | 43 | #define double_AsString(value) \ 44 | PyOS_double_to_string(value, 'r', 0, Py_DTSF_ADD_DOT_0, 0); 45 | 46 | #define float_Convert(op, obj) \ 47 | if (obj == Py_None) obj = NULL; \ 48 | if (obj) op = PyFloat_AsDouble(obj); \ 49 | if (op == -1 && PyErr_Occurred()) { \ 50 | PyObject *repr = PyObject_Repr(obj); \ 51 | PyErr_Format(PyExc_TypeError, \ 52 | "not a number: %s", \ 53 | PyString_AsString(repr)); \ 54 | Py_DECREF(repr); \ 55 | return NULL; \ 56 | } 57 | 58 | #define PyType_Prepare(mod, name, type) \ 59 | if (PyType_Ready(type) < 0) { \ 60 | INITERROR; \ 61 | } \ 62 | Py_INCREF(type); \ 63 | PyModule_AddObject(mod, name, (PyObject *)type); 64 | 65 | #define skipdict_Check(op) PyObject_TypeCheck(op, &SkipDictType) 66 | #define skipdict_CheckExact(op) (Py_TYPE(op) == &SkipDictType) 67 | 68 | static PyTypeObject SkipDictType; 69 | static PyTypeObject SkipDictIterType; 70 | 71 | typedef enum {KEY, VALUE, ITEM} itertype; 72 | 73 | typedef struct { 74 | PyObject_HEAD 75 | skiplist *skiplist; 76 | PyObject *random; 77 | PyObject *mapping; 78 | } SkipDictObject; 79 | 80 | typedef struct { 81 | PyObject_HEAD 82 | SkipDictObject *skipdict; 83 | skiplistiter *iter; 84 | itertype type; 85 | unsigned long length; 86 | } SkipDictIterObject; 87 | 88 | typedef PyObject * (*iterfunc)(double score, PyObject*); 89 | 90 | static PyObject * skipdictiter_next_key(double score, PyObject* value); 91 | static PyObject * skipdictiter_next_value(double score, PyObject* value); 92 | static PyObject * skipdictiter_next_item(double score, PyObject* value); 93 | 94 | static iterfunc iterators[3] = { skipdictiter_next_key, 95 | skipdictiter_next_value, 96 | skipdictiter_next_item }; 97 | 98 | static const char* iterator_names[3] = { "keys", "values", "items" }; 99 | static const char* booleans[2] = { "false", "true" }; 100 | 101 | static PyObject * 102 | skipdict_index(SkipDictObject *self, PyObject *key) 103 | { 104 | PyObject* item = PyDict_GetItem(self->mapping, key); 105 | if (!item) { 106 | PyErr_SetObject(PyExc_KeyError, 107 | PyObject_Repr(key)); 108 | return NULL; 109 | } 110 | PyObject* value = PyTuple_GET_ITEM(item, 1); 111 | double score = PyFloat_AsDouble(value); 112 | if (score == -1.0 && PyErr_Occurred()) return 0; 113 | 114 | unsigned long rank = slGetRank(self->skiplist, score, (void*) item); 115 | if (!rank) { 116 | return NULL; 117 | } 118 | 119 | return PyLong_FromLong(rank - 1); 120 | } 121 | 122 | static int 123 | skipdictiter_fetch(skiplistiter *iter, double *score, PyObject **obj) 124 | { 125 | PyObject* item; 126 | if (slIterGet(iter, score, (void*) &item)) { 127 | PyErr_SetString(PyExc_StopIteration, ""); 128 | return -1; 129 | } 130 | 131 | if (slIterNext(iter)) { 132 | PyErr_SetString(PyExc_TypeError, 133 | "unable to advance the internal iterator"); 134 | return -1; 135 | } 136 | 137 | *obj = PyTuple_GET_ITEM(item, 0); 138 | return 0; 139 | } 140 | 141 | static PyObject * 142 | skipdictiter_next(SkipDictIterObject *it) 143 | { 144 | if (it->length == 0) { 145 | PyErr_SetString(PyExc_StopIteration, ""); 146 | return NULL; 147 | } 148 | 149 | double score; 150 | PyObject *key; 151 | 152 | int err = skipdictiter_fetch(it->iter, &score, &key); 153 | if (err) { 154 | return NULL; 155 | } 156 | 157 | it->length--; 158 | 159 | return iterators[it->type](score, key); 160 | } 161 | 162 | static PyObject * 163 | skipdictiter_next_key(double score, PyObject* key) 164 | { 165 | Py_INCREF(key); 166 | return key; 167 | } 168 | 169 | static PyObject * 170 | skipdictiter_next_value(double score, PyObject* key) 171 | { 172 | return (PyObject *) PyFloat_FromDouble(score); 173 | } 174 | 175 | static PyObject * 176 | skipdictiter_next_item(double score, PyObject* key) 177 | { 178 | Py_INCREF(key); 179 | return Py_BuildValue("(OO)", 180 | key, 181 | (PyObject *) PyFloat_FromDouble(score)); 182 | } 183 | 184 | static void 185 | skipdictiter_dealloc(SkipDictIterObject *it) 186 | { 187 | PyObject_GC_UnTrack(it); 188 | slIterDel(it->iter); 189 | Py_XDECREF(it->skipdict); 190 | PyObject_GC_Del(it); 191 | } 192 | 193 | static int 194 | skipdictiter_traverse(SkipDictIterObject *it, visitproc visit, void *arg) 195 | { 196 | Py_VISIT(it->skipdict); 197 | return 0; 198 | } 199 | 200 | static int 201 | skipdict_delitem(SkipDictObject *self, PyObject *key, int delete) 202 | { 203 | PyObject* item = PyDict_GetItem(self->mapping, key); 204 | if (!item) goto Fail; 205 | PyObject* value = PyTuple_GET_ITEM(item, 1); 206 | if (delete) PyDict_DelItem(self->mapping, key); 207 | if (!slDelete(self->skiplist, PyFloat_AsDouble(value), (void*) item, 0)) { 208 | goto Fail; 209 | } 210 | return 0; 211 | Fail: 212 | PyErr_SetObject(PyExc_KeyError, key); 213 | return -1; 214 | } 215 | 216 | static int 217 | skipdict_insertobj(SkipDictObject *self, PyObject *key, PyObject *value, 218 | int mode) 219 | { 220 | PyObject *item, *previous; 221 | double score = PyFloat_AsDouble(value); 222 | double s; 223 | int level; 224 | 225 | if (!PyNumber_Check(value)) { 226 | PyObject *repr = PyObject_Repr(value); 227 | PyErr_Format(PyExc_TypeError, 228 | "not a number: %s", 229 | PyString_AsString(repr)); 230 | Py_DECREF(repr); 231 | return -1; 232 | } 233 | 234 | if (score == -1.0 && PyErr_Occurred()) { 235 | return -1; 236 | } 237 | 238 | if (mode > 0) { 239 | if ((item = PyDict_GetItem(self->mapping, key))) { 240 | previous = PyTuple_GET_ITEM(item, 1); 241 | s = PyFloat_AsDouble(previous); 242 | 243 | if (mode == 1) { 244 | score -= s; 245 | } else { 246 | value = PyNumber_Add(value, previous); 247 | if (!value) return -1; 248 | } 249 | 250 | switch (slDelete(self->skiplist, s, (void*) item, score)) { 251 | case 0: 252 | return -1; 253 | case 1: 254 | break; 255 | case 2: 256 | PyTuple_SetItem(item, 1, value); 257 | Py_INCREF(value); 258 | return 0; 259 | } 260 | 261 | score += s; 262 | } 263 | } 264 | 265 | item = PyTuple_Pack(2, key, value); 266 | PyDict_SetItem(self->mapping, key, item); 267 | Py_DECREF(item); 268 | 269 | if (self->random) { 270 | PyObject* result = PyObject_CallFunction(self->random, "i", 271 | self->skiplist->maxlevel); 272 | level = (int) PyInt_AsLong(result); 273 | if (level == -1 && PyErr_Occurred()) { 274 | PyErr_Format(PyExc_TypeError, 275 | "not an integer: %s", 276 | PyString_AsString(PyObject_Repr(result))); 277 | Py_XDECREF(result); 278 | return -1; 279 | } 280 | Py_XDECREF(result); 281 | if (level > self->skiplist->maxlevel) { 282 | PyErr_Format(PyExc_ValueError, 283 | "level not in range (0-%d): %d", 284 | self->skiplist->maxlevel, 285 | level); 286 | return -1; 287 | } 288 | } else { 289 | level = 1; 290 | while((random() & 0xffff) < (P * 0xffff)) 291 | level += 1; 292 | level = (level < self->skiplist->maxlevel) \ 293 | ? level : self->skiplist->maxlevel; 294 | } 295 | slInsert(self->skiplist, score, (void*) item, level); 296 | return 0; 297 | } 298 | 299 | static int 300 | skipdict_insertseq(SkipDictObject *self, PyObject *seq) 301 | { 302 | PyObject *it = NULL; 303 | Py_ssize_t i; 304 | PyObject *item = NULL; 305 | PyObject *fast = NULL; 306 | PyObject *ref = NULL; 307 | 308 | assert(seq != NULL); 309 | 310 | if (PyDict_Check(seq)) { 311 | seq = ref = PyDict_Items(seq); 312 | } 313 | 314 | if (seq != Py_None && !(PySequence_Check(seq) || PyIter_Check(seq))) { 315 | PyErr_SetString(PyExc_TypeError, 316 | "expected dict, item sequence or iterable"); 317 | goto Fail; 318 | } 319 | 320 | it = PyObject_GetIter(seq); 321 | if (it == NULL) goto Fail; 322 | 323 | for (i = 0; ; ++i) { 324 | PyObject *key, *value; 325 | Py_ssize_t n; 326 | 327 | fast = NULL; 328 | item = PyIter_Next(it); 329 | 330 | if (item == NULL) { 331 | if (PyErr_Occurred()) 332 | goto Fail; 333 | break; 334 | } 335 | 336 | /* Convert item to sequence, and verify length 2. */ 337 | fast = PySequence_Fast(item, ""); 338 | if (fast == NULL) { 339 | if (PyErr_ExceptionMatches(PyExc_TypeError)) 340 | PyErr_Format(PyExc_TypeError, 341 | "cannot convert sequence element #%zd to a sequence", 342 | i); 343 | goto Fail; 344 | } 345 | n = PySequence_Fast_GET_SIZE(fast); 346 | if (n != 2) { 347 | PyErr_Format(PyExc_ValueError, 348 | "dictionary update sequence element #%zd " 349 | "has length %zd; 2 is required", 350 | i, n); 351 | goto Fail; 352 | } 353 | 354 | /* Update/merge with this (key, value) pair. */ 355 | key = PySequence_Fast_GET_ITEM(fast, 0); 356 | value = PySequence_Fast_GET_ITEM(fast, 1); 357 | 358 | if (skipdict_insertobj(self, key, value, 2)) { 359 | goto Fail; 360 | } 361 | 362 | Py_DECREF(fast); 363 | Py_DECREF(item); 364 | } 365 | 366 | i = 0; 367 | goto Return; 368 | Fail: 369 | Py_XDECREF(item); 370 | Py_XDECREF(fast); 371 | Py_XDECREF(ref); 372 | i = -1; 373 | Return: 374 | Py_XDECREF(it); 375 | return Py_SAFE_DOWNCAST(i, Py_ssize_t, int); 376 | } 377 | 378 | static PyObject * 379 | skipdict_change(SkipDictObject *self, PyObject *args) 380 | { 381 | PyObject *key, *change; 382 | if (!PyArg_ParseTuple(args, "OO:change", &key, &change)) { 383 | return NULL; 384 | } 385 | 386 | if (skipdict_insertobj(self, key, change, 2)) return NULL; 387 | 388 | Py_INCREF(Py_None); 389 | return Py_None; 390 | } 391 | 392 | static int 393 | skipdict_setup(SkipDictObject *self, int maxlevel, 394 | PyObject *rnd, PyObject *seq) 395 | { 396 | int err = 0; 397 | self->random = rnd; 398 | Py_XINCREF(rnd); 399 | self->mapping = NULL; 400 | self->skiplist = NULL; 401 | self->skiplist = slCreate(maxlevel); 402 | if (!self->skiplist) { 403 | return -1; 404 | } 405 | 406 | self->mapping = PyDict_New(); 407 | 408 | if (seq) { 409 | err = skipdict_insertseq(self, seq); 410 | if (err) { 411 | return -1; 412 | } 413 | } 414 | 415 | return 0; 416 | } 417 | 418 | static int 419 | skipdict_init(SkipDictObject *self, PyObject *args, PyObject *kw) 420 | { 421 | int maxlevel = MAXLEVEL; 422 | PyObject *rnd = NULL; 423 | PyObject *seq = NULL; 424 | static char *kwlist[] = { 425 | "sequence", "maxlevel", "random", NULL 426 | }; 427 | 428 | if (!PyArg_ParseTupleAndKeywords(args, kw, "|OiO:SkipDict", kwlist, 429 | &seq, &maxlevel, &rnd)) { 430 | return -1; 431 | } 432 | return skipdict_setup(self, maxlevel, rnd, seq); 433 | } 434 | 435 | static void 436 | skipdict_dealloc(SkipDictObject* self) 437 | { 438 | slFree(self->skiplist); 439 | if (self->mapping) { 440 | PyDict_Clear(self->mapping); 441 | Py_DECREF(self->mapping); 442 | } 443 | Py_XDECREF(self->random); 444 | Py_TYPE(self)->tp_free((PyObject*)self); 445 | } 446 | 447 | static PyObject * 448 | skipdict_iterator(SkipDictObject *self, skiplistiter *iter, itertype type, Py_ssize_t length) 449 | { 450 | SkipDictIterObject *it = NULL; 451 | if (!skipdict_Check(self)) { 452 | PyErr_BadInternalCall(); 453 | return NULL; 454 | } 455 | 456 | if (!iter) { 457 | iter = slIterNewFromHead(self->skiplist); 458 | } 459 | 460 | if (!iter) { 461 | return NULL; 462 | } 463 | 464 | it = PyObject_GC_New(SkipDictIterObject, &SkipDictIterType); 465 | if (!it) { 466 | slIterDel(iter); 467 | return NULL; 468 | } 469 | 470 | Py_INCREF(self); 471 | 472 | it->skipdict = self; 473 | it->iter = iter; 474 | it->type = type; 475 | it->length = length > 1 ? (unsigned long) length : slLength(self->skiplist); 476 | PyObject_GC_Track(it); 477 | 478 | return (PyObject *)it; 479 | } 480 | 481 | static PyObject * 482 | skipdict_iter(SkipDictObject *skipdict) 483 | { 484 | return skipdict_iterator(skipdict, NULL, KEY, -1); 485 | } 486 | 487 | static PyObject * 488 | skipdictiter_item(SkipDictIterObject *self, Py_ssize_t index) 489 | { 490 | if (index < 0) { 491 | index = self->length + index; 492 | } 493 | 494 | index = index % slLength(self->skipdict->skiplist); 495 | 496 | skiplistNode* node = slGetNodeByRank(self->iter->node, 497 | self->skipdict->skiplist->level - 1, 498 | index); 499 | 500 | if (!node) { 501 | PyErr_SetObject(PyExc_IndexError, 502 | PyString_FromString("index out of range")); 503 | return NULL; 504 | } 505 | 506 | PyObject *obj = PyTuple_GET_ITEM(node->obj, 0); 507 | return iterators[self->type](node->score, obj); 508 | } 509 | 510 | static PyObject * 511 | skipdictiter_slice(SkipDictIterObject *self, PyObject* item) 512 | { 513 | if (PyIndex_Check(item)) { 514 | PyObject *i, *result; 515 | i = PyNumber_Index(item); 516 | if (!i) 517 | return NULL; 518 | long index = PyLong_AsLong(i); 519 | Py_DECREF(i); 520 | result = skipdictiter_item(self, index); 521 | return result; 522 | } 523 | 524 | if (!PySlice_Check(item)) { 525 | PyErr_Format(PyExc_TypeError, 526 | "range indices must be integers or slices, not %.200s", 527 | Py_TYPE(item)->tp_name); 528 | return NULL; 529 | } 530 | 531 | Py_ssize_t ilow; 532 | Py_ssize_t ihigh; 533 | Py_ssize_t step; 534 | Py_ssize_t length; 535 | 536 | if (PySlice_GetIndicesEx((PySliceObject *)item, 537 | slLength(self->skipdict->skiplist), 538 | &ilow, &ihigh, &step, &length)) { 539 | return NULL; 540 | } 541 | 542 | if (step != 1) { 543 | PyErr_Format(PyExc_ValueError, 544 | "slice step %d not supported", 545 | (int) step); 546 | return NULL; 547 | } 548 | 549 | 550 | skiplistNode* node = slGetNodeByRank(self->iter->node, 551 | self->skipdict->skiplist->level - 1, 552 | ilow); 553 | 554 | skiplistiter *iter = slIterNew(self->skipdict->skiplist, node); 555 | 556 | if (!iter) { 557 | return NULL; 558 | } 559 | 560 | PyObject *it = skipdict_iterator(self->skipdict, iter, self->type, ihigh - ilow); 561 | if (!it) { 562 | slIterDel(iter); 563 | } 564 | 565 | return it; 566 | } 567 | 568 | static Py_ssize_t 569 | skipdict_length(SkipDictObject *self) 570 | { 571 | Py_ssize_t len = slLength(self->skiplist); 572 | return len; 573 | } 574 | 575 | static int 576 | skipdict_contains(SkipDictObject *self, PyObject *key) 577 | { 578 | return PyDict_Contains(self->mapping, key); 579 | } 580 | 581 | static PyObject * 582 | skipdict_getitem(SkipDictObject *self, PyObject *key) 583 | { 584 | PyObject *item = PyDict_GetItem(self->mapping, key); 585 | if (!item) { 586 | PyErr_SetObject(PyExc_KeyError, key); 587 | return NULL; 588 | } 589 | PyObject* value = PyTuple_GET_ITEM(item, 1); 590 | Py_INCREF(value); 591 | return value; 592 | } 593 | 594 | static int 595 | skipdict_ass_sub(SkipDictObject *self, PyObject *key, PyObject *value) 596 | { 597 | if (value) { 598 | if (skipdict_insertobj(self, key, value, 1)) return -1; 599 | } else { 600 | if (skipdict_delitem(self, key, 1)) return -1; 601 | } 602 | return 0; 603 | } 604 | 605 | static PyObject * 606 | skipdict_get(SkipDictObject *self, PyObject *args) 607 | { 608 | PyObject *key, *value; 609 | PyObject *failobj = Py_None; 610 | 611 | if (!PyArg_UnpackTuple(args, "get", 1, 2, &key, &failobj)) 612 | return NULL; 613 | 614 | PyObject *item = PyDict_GetItem(self->mapping, key); 615 | if (item) { 616 | value = PyTuple_GET_ITEM(item, 1); 617 | } else { 618 | value = failobj; 619 | } 620 | 621 | Py_INCREF(value); 622 | return value; 623 | } 624 | 625 | PyObject * 626 | skipdict_setdefault(SkipDictObject *self, PyObject *args) 627 | { 628 | PyObject *key, *value; 629 | PyObject *defaultobj = Py_None; 630 | 631 | if (!PyArg_UnpackTuple(args, "setdefault", 1, 2, &key, &defaultobj)) 632 | return NULL; 633 | 634 | PyObject *item = PyDict_GetItem(self->mapping, key); 635 | if (!item) { 636 | if (skipdict_insertobj(self, key, defaultobj, 0)) { 637 | return NULL; 638 | } 639 | value = defaultobj; 640 | } else { 641 | value = PyTuple_GET_ITEM(item, 1); 642 | } 643 | 644 | Py_XINCREF(value); 645 | return value; 646 | } 647 | 648 | static PyObject * 649 | skipdict_iterator_from_range(SkipDictObject *self, 650 | PyObject *args, PyObject *kw, 651 | itertype type) 652 | { 653 | PyObject *min = NULL; 654 | PyObject *max = NULL; 655 | 656 | static char *kwlist[] = {"min", "max", NULL}; 657 | 658 | if (!PyArg_ParseTupleAndKeywords(args, kw, "|OO:SkipDict", kwlist, 659 | &min, &max)) { 660 | return NULL; 661 | } 662 | 663 | PyObject *it = NULL; 664 | skiplistiter *iter; 665 | 666 | if (!self->skiplist->tail) { 667 | it = PyTuple_New(0); 668 | return PyObject_GetIter(it); 669 | } 670 | 671 | if (!(min || max)) { 672 | iter = slIterNewFromHead(self->skiplist); 673 | } else { 674 | double dmin = self->skiplist->header->level[0].forward->score; 675 | double dmax = self->skiplist->tail->score; 676 | 677 | float_Convert(dmin, min); 678 | float_Convert(dmax, max); 679 | 680 | iter = slIterNewFromRange(self->skiplist, dmin, dmax); 681 | } 682 | 683 | if (!iter) { 684 | return NULL; 685 | } 686 | it = skipdict_iterator(self, iter, type, -1); 687 | if (!it) { 688 | slIterDel(iter); 689 | } 690 | return it; 691 | } 692 | 693 | static PyObject * 694 | skipdict_keys(SkipDictObject *self, PyObject *args, PyObject *kw) 695 | { 696 | return skipdict_iterator_from_range(self, args, kw, KEY); 697 | } 698 | 699 | static PyObject * 700 | skipdict_values(SkipDictObject *self, PyObject *args, PyObject *kw) 701 | { 702 | return skipdict_iterator_from_range(self, args, kw, VALUE); 703 | } 704 | 705 | static PyObject * 706 | skipdict_items(SkipDictObject *self, PyObject *args, PyObject *kw) 707 | { 708 | return skipdict_iterator_from_range(self, args, kw, ITEM); 709 | } 710 | 711 | static PyObject * 712 | skipdict_repr(SkipDictObject *self) 713 | { 714 | Py_ssize_t i; 715 | PyObject *s, *temp, *colon = NULL; 716 | PyObject *pieces = NULL, *result = NULL; 717 | PyObject *key, *value; 718 | SkipDictIterObject * it = NULL; 719 | double score; 720 | 721 | i = Py_ReprEnter((PyObject *)self); 722 | if (i != 0) { 723 | return i > 0 ? PyString_FromString("{...}") : NULL; 724 | } 725 | 726 | if (slLength(self->skiplist) == 0) { 727 | result = PyString_FromString("{}"); 728 | goto Done; 729 | } 730 | 731 | pieces = PyList_New(0); 732 | if (pieces == NULL) 733 | goto Done; 734 | 735 | colon = PyString_FromString(": "); 736 | if (colon == NULL) 737 | goto Done; 738 | 739 | it = (SkipDictIterObject* ) skipdict_iterator(self, NULL, ITEM, -1); 740 | 741 | /* Do repr() on each key+value pair, and insert ": " between them. 742 | Note that repr may mutate the dict. */ 743 | i = 0; 744 | while (!skipdictiter_fetch(it->iter, &score, &key)) { 745 | value = (PyObject *) PyFloat_FromDouble(score); 746 | int status; 747 | s = PyObject_Repr(key); 748 | PyString_Concat(&s, colon); 749 | PyString_ConcatAndDel(&s, PyObject_Repr(value)); 750 | Py_DECREF(value); 751 | if (s == NULL) 752 | goto Done; 753 | status = PyList_Append(pieces, s); 754 | Py_DECREF(s); /* append created a new ref */ 755 | if (status < 0) 756 | goto Done; 757 | } 758 | 759 | PyErr_Clear(); 760 | 761 | /* Add "{}" decorations to the first and last items. */ 762 | assert(PyList_GET_SIZE(pieces) > 0); 763 | s = PyString_FromString("{"); 764 | if (s == NULL) 765 | goto Done; 766 | 767 | temp = PyList_GET_ITEM(pieces, 0); 768 | PyString_ConcatAndDel(&s, temp); 769 | PyList_SET_ITEM(pieces, 0, s); 770 | if (s == NULL) 771 | goto Done; 772 | 773 | s = PyString_FromString("}"); 774 | if (s == NULL) 775 | goto Done; 776 | temp = PyList_GET_ITEM(pieces, PyList_GET_SIZE(pieces) - 1); 777 | PyString_ConcatAndDel(&temp, s); 778 | PyList_SET_ITEM(pieces, PyList_GET_SIZE(pieces) - 1, temp); 779 | if (temp == NULL) 780 | goto Done; 781 | 782 | /* Paste them all together with ", " between. */ 783 | s = PyString_FromString(", "); 784 | if (s == NULL) 785 | goto Done; 786 | result = _PyString_Join(s, pieces); 787 | Py_DECREF(s); 788 | Done: 789 | Py_XDECREF(pieces); 790 | Py_XDECREF(colon); 791 | Py_XDECREF(it); 792 | Py_ReprLeave((PyObject *)self); 793 | return result; 794 | } 795 | 796 | static PyObject * 797 | skipdict_maxlevel(SkipDictObject *self) 798 | { 799 | return PyInt_FromLong(self->skiplist->maxlevel); 800 | } 801 | 802 | static PyObject * 803 | skipdictiter_repr(SkipDictIterObject *self) 804 | { 805 | char* min = double_AsString(self->iter->min); 806 | char* max = double_AsString(self->iter->max); 807 | PyObject *repr = PyString_FromFormat("", 813 | iterator_names[self->type], 814 | booleans[self->iter->forward], 815 | min, 816 | max, 817 | self->skipdict); 818 | PyMem_Free(min); 819 | PyMem_Free(max); 820 | return repr; 821 | } 822 | 823 | static PyObject * 824 | skipdict_richcompare(SkipDictObject *self, PyObject* other, int op) 825 | { 826 | PyObject *mapping; 827 | if (PyDict_Check(other)) { 828 | mapping = other; 829 | } else { 830 | if (!skipdict_Check(other)) { 831 | PyObject *repr = PyObject_Repr(other); 832 | PyErr_Format(PyExc_TypeError, 833 | "can't compare with %s", 834 | PyString_AsString(repr)); 835 | Py_DECREF(repr); 836 | return NULL; 837 | } 838 | SkipDictObject* obj = (SkipDictObject*) other; 839 | mapping = obj->mapping; 840 | } 841 | return PyDict_Type.tp_richcompare(self->mapping, mapping, op); 842 | } 843 | 844 | static PyMethodDef skipdict_methods[] = { 845 | {"get", (PyCFunction)skipdict_get, METH_VARARGS, NULL}, 846 | {"setdefault", (PyCFunction)skipdict_setdefault, METH_VARARGS, NULL}, 847 | {"keys", (PyCFunction)skipdict_keys, METH_VARARGS | METH_KEYWORDS, NULL}, 848 | {"values", (PyCFunction)skipdict_values, METH_VARARGS | METH_KEYWORDS, NULL}, 849 | {"items", (PyCFunction)skipdict_items, METH_VARARGS | METH_KEYWORDS, NULL}, 850 | {"index", (PyCFunction)skipdict_index, METH_O, NULL}, 851 | {"change", (PyCFunction)skipdict_change, METH_VARARGS, NULL}, 852 | {NULL} 853 | }; 854 | 855 | static PyGetSetDef skipdict_getset[] = { 856 | {"maxlevel", (getter)skipdict_maxlevel, NULL, "maxlevel", NULL}, 857 | {NULL} 858 | }; 859 | 860 | static PyMappingMethods mapping = { 861 | (lenfunc)skipdict_length, /*mp_length*/ 862 | (binaryfunc)skipdict_getitem, /*mp_subscript*/ 863 | (objobjargproc)skipdict_ass_sub, /*mp_ass_subscript*/ 864 | }; 865 | 866 | static PySequenceMethods sequence = { 867 | (lenfunc)skipdict_length, /*sq_length*/ 868 | 0, /*sq_concat*/ 869 | 0, /*sq_repeat*/ 870 | 0, /*sq_item*/ 871 | 0, /*sq_slice*/ 872 | 0, /*sq_ass_item*/ 873 | 0, /*sq_ass_slice*/ 874 | (objobjproc)skipdict_contains, /*sq_contains*/ 875 | 0, /*sq_inplace_concat*/ 876 | 0, /*sq_inplace_repeat*/ 877 | }; 878 | 879 | static PyTypeObject SkipDictType = { 880 | PyVarObject_HEAD_INIT(NULL, 0) 881 | "skipdict.SkipDict", /* tp_name */ 882 | sizeof(SkipDictObject), /* tp_basicsize */ 883 | 0, /* tp_itemsize */ 884 | (destructor)skipdict_dealloc, /* tp_dealloc */ 885 | 0, /* tp_print */ 886 | 0, /* tp_getattr */ 887 | 0, /* tp_setattr */ 888 | 0, /* tp_compare */ 889 | (reprfunc)skipdict_repr, /* tp_repr */ 890 | 0, /* tp_as_number */ 891 | &sequence, /* tp_as_sequence*/ 892 | &mapping, /* tp_as_mapping */ 893 | (hashfunc)PyObject_HashNotImplemented, /* tp_hash */ 894 | 0, /* tp_call */ 895 | 0, /* tp_str */ 896 | 0, /* tp_getattro */ 897 | 0, /* tp_setattro */ 898 | 0, /* tp_as_buffer */ 899 | Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,/* tp_flags */ 900 | 0, /* tp_doc */ 901 | 0, /* tp_traverse */ 902 | 0, /* tp_clear */ 903 | (richcmpfunc)skipdict_richcompare, /* tp_richcompare */ 904 | 0, /* tp_weaklistoffset */ 905 | (getiterfunc)skipdict_iter, /* tp_iter */ 906 | 0, /* tp_iternext */ 907 | skipdict_methods, /* tp_methods */ 908 | 0, /* tp_members */ 909 | skipdict_getset, /* tp_getset */ 910 | 0, /* tp_base */ 911 | 0, /* tp_dict */ 912 | 0, /* tp_descr_get */ 913 | 0, /* tp_descr_set */ 914 | 0, /* tp_dictoffset */ 915 | (initproc)skipdict_init, /* tp_init */ 916 | PyType_GenericAlloc, /* tp_alloc */ 917 | PyType_GenericNew, /* tp_new */ 918 | PyObject_Del, /* tp_free */ 919 | 0, /* tp_is_gc */ 920 | }; 921 | 922 | static PyMappingMethods skipdictiter_as_mapping = { 923 | 0, /*mp_length*/ 924 | (binaryfunc)skipdictiter_slice, /*mp_subscript*/ 925 | 0, /*mp_ass_subscript*/ 926 | }; 927 | 928 | static PyTypeObject SkipDictIterType = { 929 | PyVarObject_HEAD_INIT(NULL, 0) 930 | "skipdict.SkipDictIterator", /* tp_name */ 931 | sizeof(SkipDictIterObject), /* tp_basicsize */ 932 | 0, /* tp_itemsize */ 933 | /* methods */ 934 | (destructor)skipdictiter_dealloc, /* tp_dealloc */ 935 | 0, /* tp_print */ 936 | 0, /* tp_getattr */ 937 | 0, /* tp_setattr */ 938 | 0, /* tp_compare */ 939 | (reprfunc)skipdictiter_repr, /* tp_repr */ 940 | 0, /* tp_as_number */ 941 | 0, /* tp_as_sequence */ 942 | &skipdictiter_as_mapping, /* tp_as_mapping */ 943 | 0, /* tp_hash */ 944 | 0, /* tp_call */ 945 | 0, /* tp_str */ 946 | PyObject_GenericGetAttr, /* tp_getattro */ 947 | 0, /* tp_setattro */ 948 | 0, /* tp_as_buffer */ 949 | Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, /* tp_flags */ 950 | 0, /* tp_doc */ 951 | (traverseproc)skipdictiter_traverse, /* tp_traverse */ 952 | 0, /* tp_clear */ 953 | 0, /* tp_richcompare */ 954 | 0, /* tp_weaklistoffset */ 955 | PyObject_SelfIter, /* tp_iter */ 956 | (iternextfunc)skipdictiter_next, /* tp_iternext */ 957 | 0, /* tp_methods */ 958 | }; 959 | 960 | static PyMethodDef methods[] = { 961 | {NULL, NULL, 0, NULL} 962 | }; 963 | 964 | #if PY_MAJOR_VERSION >= 3 965 | static struct PyModuleDef moduledef = { 966 | PyModuleDef_HEAD_INIT, 967 | "skipdict", 968 | NULL, 969 | 0, 970 | methods, 971 | NULL, 972 | NULL, 973 | NULL, 974 | NULL 975 | }; 976 | 977 | PyObject * 978 | PyInit_skipdict(void) 979 | #else 980 | void 981 | initskipdict(void) 982 | #endif 983 | { 984 | #if PY_MAJOR_VERSION >= 3 985 | PyObject *module = PyModule_Create(&moduledef); 986 | #else 987 | PyObject *module = Py_InitModule("skipdict", methods); 988 | #endif 989 | 990 | if (module == NULL) 991 | INITERROR; 992 | 993 | PyType_Prepare(module, "SkipDict", &SkipDictType); 994 | PyType_Prepare(module, "SkipDictIterator", &SkipDictIterType); 995 | 996 | #if PY_MAJOR_VERSION >= 3 997 | return module; 998 | #endif 999 | } 1000 | /* EOF */ 1001 | -------------------------------------------------------------------------------- /skiplist.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "skiplist.h" 6 | 7 | #define SWAP(x, y, T) do { T temp##x##y = x; x = y; y = temp##x##y; } while (0) 8 | 9 | skiplistNode *slCreateNode(int level, double score, void *obj) { 10 | skiplistNode *n = calloc(1, sizeof(*n) + level * sizeof(struct skiplistLevel)); 11 | n->score = score; 12 | n->obj = obj; 13 | return n; 14 | } 15 | 16 | skiplist *slCreate(int maxlevel) { 17 | int j; 18 | skiplist *sl; 19 | 20 | sl = malloc(sizeof(*sl)); 21 | sl->level = 1; 22 | sl->maxlevel = maxlevel; 23 | sl->length = 0; 24 | sl->header = slCreateNode(maxlevel, 0, NULL); 25 | for (j=0; j < maxlevel; j++) { 26 | sl->header->level[j].forward = NULL; 27 | sl->header->level[j].span = 0; 28 | } 29 | sl->header->backward = NULL; 30 | sl->tail = NULL; 31 | return sl; 32 | } 33 | 34 | void slFree(skiplist *sl) { 35 | skiplistNode *node = sl->header->level[0].forward, *next; 36 | 37 | free(sl->header); 38 | while(node) { 39 | next = node->level[0].forward; 40 | free(node); 41 | node = next; 42 | } 43 | free(sl); 44 | } 45 | 46 | void slInsert(skiplist *sl, double score, void *obj, int level) { 47 | skiplistNode *update[sl->maxlevel], *x; 48 | unsigned int rank[sl->maxlevel]; 49 | int i; 50 | 51 | x = sl->header; 52 | for (i = sl->level-1; i >= 0; i--) { 53 | /* store rank that is crossed to reach the insert position */ 54 | rank[i] = i == (sl->level-1) ? 0 : rank[i+1]; 55 | while (x->level[i].forward && 56 | (x->level[i].forward->score < score || 57 | (x->level[i].forward->score == score && 58 | x->level[i].forward->obj < obj))) { 59 | rank[i] += x->level[i].span; 60 | x = x->level[i].forward; 61 | } 62 | update[i] = x; 63 | } 64 | /* we assume the key is not already inside, since we allow duplicated 65 | * scores, and the re-insertion of score and object should never 66 | * happen since the caller of slInsert() should test in the hash table 67 | * if the element is already inside or not. */ 68 | if (level > sl->level) { 69 | for (i = sl->level; i < level; i++) { 70 | rank[i] = 0; 71 | update[i] = sl->header; 72 | update[i]->level[i].span = sl->length; 73 | } 74 | sl->level = level; 75 | } 76 | x = slCreateNode(sl->maxlevel, score, obj); 77 | for (i = 0; i < level; i++) { 78 | x->level[i].forward = update[i]->level[i].forward; 79 | update[i]->level[i].forward = x; 80 | 81 | /* update span covered by update[i] as x is inserted here */ 82 | x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]); 83 | update[i]->level[i].span = (rank[0] - rank[i]) + 1; 84 | } 85 | 86 | /* increment span for untouched levels */ 87 | for (i = level; i < sl->level; i++) { 88 | update[i]->level[i].span++; 89 | } 90 | 91 | x->backward = (update[0] == sl->header) ? NULL : update[0]; 92 | if (x->level[0].forward) { 93 | x->level[0].forward->backward = x; 94 | } else { 95 | sl->tail = x; 96 | } 97 | sl->length++; 98 | } 99 | 100 | /* Internal function used by slDelete, slDeleteByScore */ 101 | void slDeleteNode(skiplist *sl, skiplistNode *x, skiplistNode **update) { 102 | int i; 103 | for (i = 0; i < sl->level; i++) { 104 | if (update[i]->level[i].forward == x) { 105 | update[i]->level[i].span += x->level[i].span - 1; 106 | update[i]->level[i].forward = x->level[i].forward; 107 | } else { 108 | update[i]->level[i].span -= 1; 109 | } 110 | } 111 | if (x->level[0].forward) { 112 | x->level[0].forward->backward = x->backward; 113 | } else { 114 | sl->tail = x->backward; 115 | } 116 | while(sl->level > 1 && sl->header->level[sl->level-1].forward == NULL) 117 | sl->level--; 118 | sl->length--; 119 | } 120 | 121 | /* Delete an element with matching score/object from the skiplist. */ 122 | int slDelete(skiplist *sl, double score, void *obj, double change) { 123 | skiplistNode *update[sl->maxlevel], *x, *y; 124 | int i; 125 | 126 | x = sl->header; 127 | for (i = sl->level-1; i >= 0; i--) { 128 | while (x->level[i].forward && 129 | (x->level[i].forward->score < score || 130 | (x->level[i].forward->score == score && 131 | x->level[i].forward->obj < obj))) 132 | x = x->level[i].forward; 133 | update[i] = x; 134 | } 135 | /* We may have multiple elements with the same score, what we need 136 | * is to find the element with both the right score and object. */ 137 | x = x->level[0].forward; 138 | if (x && score == x->score && x->obj == obj) { 139 | if (change > 0) { 140 | y = x->level[0].forward; 141 | if (y && score + change < y->score) { 142 | x->score = score + change; 143 | return 2; 144 | } 145 | } 146 | slDeleteNode(sl, x, update); 147 | free(x); 148 | return 1; 149 | } else { 150 | return 0; /* not found */ 151 | } 152 | return 0; /* not found */ 153 | } 154 | 155 | /* Delete all the elements with rank between start and end from the skiplist. 156 | * Start and end are inclusive. Note that start and end need to be 1-based */ 157 | unsigned long slDeleteByRank(skiplist *sl, unsigned int start, unsigned int end, slDeleteCb cb, void* ud) { 158 | skiplistNode *update[sl->maxlevel], *x; 159 | unsigned long traversed = 0, removed = 0; 160 | int i; 161 | 162 | x = sl->header; 163 | for (i = sl->level-1; i >= 0; i--) { 164 | while (x->level[i].forward && (traversed + x->level[i].span) < start) { 165 | traversed += x->level[i].span; 166 | x = x->level[i].forward; 167 | } 168 | update[i] = x; 169 | } 170 | 171 | traversed++; 172 | x = x->level[0].forward; 173 | while (x && traversed <= end) { 174 | skiplistNode *next = x->level[0].forward; 175 | slDeleteNode(sl,x,update); 176 | cb(ud, x->obj); 177 | free(x); 178 | removed++; 179 | traversed++; 180 | x = next; 181 | } 182 | return removed; 183 | } 184 | 185 | /* Find the rank for an element by both score and key. 186 | * Returns 0 when the element cannot be found, rank otherwise. 187 | * Note that the rank is 1-based due to the span of sl->header to the 188 | * first element. */ 189 | unsigned long slGetRank(skiplist *sl, double score, void *obj) { 190 | skiplistNode *x; 191 | unsigned long rank = 0; 192 | int i; 193 | 194 | x = sl->header; 195 | for (i = sl->level-1; i >= 0; i--) { 196 | while (x->level[i].forward && 197 | (x->level[i].forward->score < score || 198 | (x->level[i].forward->score == score && 199 | x->level[i].forward->obj <= obj))) { 200 | rank += x->level[i].span; 201 | x = x->level[i].forward; 202 | } 203 | 204 | /* x might be equal to sl->header, so test if obj is non-NULL */ 205 | if (x->obj && x->obj == obj) { 206 | return rank; 207 | } 208 | } 209 | return 0; 210 | } 211 | 212 | /* Finds an element by its rank. The rank argument needs to be 1-based. */ 213 | skiplistNode* slGetNodeByRank(skiplistNode* node, int level, unsigned long rank) 214 | { 215 | unsigned long traversed = 0; 216 | int i; 217 | 218 | for (i = level; i >= 0; i--) { 219 | while (node->level[i].forward && (traversed + node->level[i].span) <= rank) 220 | { 221 | traversed += node->level[i].span; 222 | node = node->level[i].forward; 223 | } 224 | if (traversed == rank) { 225 | return node; 226 | } 227 | } 228 | 229 | return NULL; 230 | } 231 | 232 | /* range [min, max], left & right both include */ 233 | /* Returns if there is a part of the dict in range. */ 234 | int slIsInRange(skiplist *sl, double min, double max) { 235 | skiplistNode *x; 236 | 237 | /* Test for ranges that will always be empty. */ 238 | if(min > max) { 239 | return 0; 240 | } 241 | x = sl->tail; 242 | if (x == NULL || x->score < min) 243 | return 0; 244 | 245 | x = sl->header->level[0].forward; 246 | if (x == NULL || x->score > max) 247 | return 0; 248 | return 1; 249 | } 250 | 251 | /* Find the first node that is contained in the specified range. 252 | * Returns NULL when no element is contained in the range. */ 253 | skiplistNode *slFirstInRange(skiplist *sl, double min, double max) { 254 | skiplistNode *x; 255 | int i; 256 | 257 | /* If everything is out of range, return early. */ 258 | if (!slIsInRange(sl,min, max)) return NULL; 259 | 260 | x = sl->header; 261 | for (i = sl->level-1; i >= 0; i--) { 262 | /* Go forward while *OUT* of range. */ 263 | while (x->level[i].forward && x->level[i].forward->score < min) 264 | x = x->level[i].forward; 265 | } 266 | 267 | /* This is an inner range, so the next node cannot be NULL. */ 268 | x = x->level[0].forward; 269 | return x; 270 | } 271 | 272 | /* Find the last node that is contained in the specified range. 273 | * Returns NULL when no element is contained in the range. */ 274 | skiplistNode *slLastInRange(skiplist *sl, double min, double max) { 275 | skiplistNode *x; 276 | int i; 277 | 278 | /* If everything is out of range, return early. */ 279 | if (!slIsInRange(sl, min, max)) return NULL; 280 | 281 | x = sl->header; 282 | for (i = sl->level-1; i >= 0; i--) { 283 | /* Go forward while *IN* range. */ 284 | while (x->level[i].forward && 285 | x->level[i].forward->score <= max) 286 | x = x->level[i].forward; 287 | } 288 | 289 | /* This is an inner range, so this node cannot be NULL. */ 290 | return x; 291 | } 292 | 293 | unsigned long slLength(skiplist *sl) 294 | { 295 | return sl->length; 296 | } 297 | 298 | skiplistiter *slIterNew(skiplist *sl, skiplistNode* head) 299 | { 300 | skiplistiter *it = calloc(1, sizeof(struct skiplistiter)); 301 | if (it) { 302 | it->parent = sl; 303 | it->node = head; 304 | it->forward = 1; 305 | it->min = it->node->score; 306 | it->max = sl->tail->score; 307 | } 308 | return it; 309 | } 310 | 311 | skiplistiter *slIterNewFromHead(skiplist *sl) 312 | { 313 | return slIterNew(sl, sl->header->level[0].forward); 314 | } 315 | 316 | skiplistiter *slIterNewFromRange(skiplist *sl, double min, double max) 317 | { 318 | skiplistiter *it = calloc(1, sizeof(struct skiplistiter)); 319 | if (it) { 320 | int reversed = min > max; 321 | it->parent = sl; 322 | it->forward = (reversed) ? 0 : 1; 323 | if (reversed) { 324 | SWAP(min, max, double); 325 | it->node = slLastInRange(sl, min, max); 326 | } else { 327 | it->node = slFirstInRange(sl, min, max); 328 | } 329 | it->min = min; 330 | it->max = max; 331 | } 332 | return it; 333 | } 334 | 335 | void slIterDel(skiplistiter *it) 336 | { 337 | free(it); 338 | } 339 | 340 | int slIterGet(skiplistiter *it, double *score, const void **obj) 341 | { 342 | int err = -1; 343 | if (it && 344 | it->node && 345 | it->parent && 346 | it->node != it->parent->header && 347 | it->min <= it->node->score && 348 | it->max >= it->node->score) { 349 | *score = it->node->score; 350 | *obj = it->node->obj; 351 | err = 0; 352 | } 353 | return err; 354 | } 355 | 356 | int slIterNext(skiplistiter *it) 357 | { 358 | int err = -1; 359 | if (it && it->node) { 360 | if (it->forward) { 361 | it->node = it->node->level[0].forward; 362 | } else { 363 | it->node = it->node->backward; 364 | } 365 | err = 0; 366 | } 367 | return err; 368 | } 369 | -------------------------------------------------------------------------------- /skiplist.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef struct skiplistNode { 4 | void *obj; 5 | double score; 6 | struct skiplistNode *backward; 7 | struct skiplistLevel { 8 | struct skiplistNode *forward; 9 | unsigned int span; 10 | }level[]; 11 | } skiplistNode; 12 | 13 | typedef struct skiplist { 14 | struct skiplistNode *header, *tail; 15 | unsigned long length; 16 | int level; 17 | int maxlevel; 18 | } skiplist; 19 | 20 | typedef struct skiplistiter { 21 | skiplist *parent; 22 | skiplistNode *node; 23 | int forward; 24 | double min; 25 | double max; 26 | } skiplistiter; 27 | 28 | typedef void (*slDeleteCb) (void *ud, void *obj); 29 | void* slCreateObj(const char* ptr, size_t length); 30 | void slFreeObj(void *obj); 31 | 32 | skiplist *slCreate(int maxlevel); 33 | void slFree(skiplist *sl); 34 | void slDump(skiplist *sl); 35 | 36 | void slInsert(skiplist *sl, double score, void *obj, int level); 37 | int slDelete(skiplist *sl, double score, void *obj, double change); 38 | unsigned long slLength(skiplist *sl); 39 | unsigned long slDeleteByRank(skiplist *sl, unsigned int start, unsigned int end, slDeleteCb cb, void* ud); 40 | 41 | unsigned long slGetRank(skiplist *sl, double score, void *o); 42 | skiplistNode* slGetNodeByRank(skiplistNode* x, int level, unsigned long rank); 43 | skiplistNode *slFirstInRange(skiplist *sl, double min, double max); 44 | skiplistNode *slLastInRange(skiplist *sl, double min, double max); 45 | 46 | skiplistiter *slIterNew(skiplist *sl, skiplistNode* head); 47 | skiplistiter* slIterNewFromHead(skiplist *sl); 48 | skiplistiter *slIterNewFromRange(skiplist *sl, double min, double max); 49 | void slIterDel(skiplistiter *it); 50 | int slIterGet(skiplistiter *it, double *score, const void **obj); 51 | int slIterNext(skiplistiter *it); 52 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | from gc import collect 2 | from unittest import TestCase 3 | from operator import itemgetter 4 | from random import Random, randrange, shuffle 5 | from itertools import combinations 6 | 7 | 8 | class BaseTestCase(TestCase): 9 | maxlevel = 6 10 | items = () 11 | reverse = False 12 | maxDiff = None 13 | 14 | def setUp(self): 15 | items = self.items if not self.reverse else reversed(self.items) 16 | self.skipdict = self.make(items, self.maxlevel) 17 | 18 | def tearDown(self): 19 | collect() 20 | 21 | def make(self, *args, **kwargs): 22 | from skipdict import SkipDict 23 | return SkipDict(*args, **kwargs) 24 | 25 | 26 | class PropertyTestCase(BaseTestCase): 27 | def test_maxlevel(self): 28 | self.assertEqual(self.skipdict.maxlevel, self.maxlevel) 29 | 30 | 31 | class DictTestCase(BaseTestCase): 32 | items = {'Xe2W0QxllGdCW251l7U9Dg': 150.0} 33 | 34 | def test_length(self): 35 | self.assertEqual(len(self.skipdict), len(self.items)) 36 | 37 | 38 | class RandomTestCase(BaseTestCase): 39 | items = (("foo", 1.0), ) 40 | 41 | def test_random_function(self): 42 | called = [] 43 | 44 | def random(maxlevel): 45 | called.append(1) 46 | return maxlevel // 2 47 | 48 | inst = self.make(self.items, self.maxlevel, random) 49 | self.assertEqual(list(inst.items()), list(self.items)) 50 | self.assertEqual(called, [1]) 51 | 52 | def test_random_wrong_type(self): 53 | def random(maxlevel): 54 | return "foo" 55 | 56 | self.assertRaises( 57 | TypeError, 58 | self.make, 59 | self.items, 60 | self.maxlevel, 61 | random 62 | ) 63 | 64 | def test_random_out_of_range(self): 65 | def random(maxlevel): 66 | return maxlevel + 1 67 | 68 | self.assertRaises( 69 | ValueError, 70 | self.make, 71 | self.items, 72 | self.maxlevel, 73 | random 74 | ) 75 | 76 | 77 | class IterTestCase(BaseTestCase): 78 | quote = "Everything popular is wrong. - Oscar Wilde" 79 | 80 | @property 81 | def items(self): 82 | return ((char, 1) for char in self.quote) 83 | 84 | def test_length(self): 85 | self.assertEqual(len(self.skipdict), 24) 86 | 87 | def test_least_common(self): 88 | self.assertTrue(self.quote.count(self.skipdict.keys()[0]), 1) 89 | 90 | def test_second_most_common(self): 91 | self.assertEqual(self.skipdict.keys()[-2], 'r') 92 | 93 | def test_most_common(self): 94 | self.assertEqual(self.skipdict.keys()[-1], ' ') 95 | 96 | 97 | class FixtureTestCase(BaseTestCase): 98 | items = ( 99 | ('Xe2W0QxllGdCW251l7U9Dg', 150.0), 100 | ('3HT/SVSdCwM+4ZjtSqHCew', 476.0), 101 | ('Q2BKuEOFwIojkPsnjmPNFg', 2390.0), 102 | ('Qjq1fGnHVc5nXvWnbEyXjQ', 3773.0), 103 | ('wamjIdfm+ajk81fR7gKcAA', 4729.0), 104 | ('uDUHFv5CtiY/Gm5LOCfGUg', 6143.0), 105 | ('78l934GXETN68sql2vjP5w', 6487.0), 106 | ('1LAwgvIO0tEikYySkaSaXw', 7449.0), 107 | ('Y4mDqz7LfIV4L8h2aUwAkA', 10234.0), 108 | ('AidPceym19y/lmIdi6JxQQ', 10779.0), 109 | ('hHqwNSMusq7O895yFkr+rQ', 10789.0), 110 | ('dg16QiUDC2rgE39FWTSOxg', 11873.0), 111 | ('sgAdgtQ5wRFGSOZ3xZYHyA', 12273.0), 112 | ('OACKY0A1ftBbyLvTzyf8lQ', 12776.0), 113 | ('f+dLA1jK8EFEAHxm1FKUkA', 13079.0), 114 | ('1uDN4mSmsEQF/o6VNiBl3g', 13147.0), 115 | ('nNwOvGfk9AH2tIzK8uNdzQ', 16636.0), 116 | ('tMUZ6A1e/1SKd3ko0FhhBQ', 17933.0), 117 | ('M77ZQiFlYeU4ySUtVa6XYg', 18570.0), 118 | ('fY7RKQu8toBxoug4CMmznA', 18711.0), 119 | ('UaZorA+/GnCL4nmgLs3c/g', 19968.0), 120 | ('VbXaOsRHqH2CAoNJiYsrqg', 20064.0), 121 | ('dAr84/axpItIAjjNcVPzHA', 20250.0), 122 | ('HjzS0QlpofFhDO2iU4mXAw', 20756.0), 123 | ('ipksmQaeYErYwjZ6ia46TA', 21084.0), 124 | ('XemDsutAYPpCZ6WY4M//ig', 21609.0), 125 | ('6U6fbOs8jYVfqWeArQ5HHQ', 22410.0), 126 | ('QFblGefWYZqFbuSK0SDPRQ', 23267.0), 127 | ('J13bR75czCiLfeBcIy4XTg', 26556.0), 128 | ('e6XlDT9h6GVPdfvBOrXW5Q', 26608.0), 129 | ('/eLYo+GKgAt7I2PrOrFTzQ', 28597.0), 130 | ('48W/xF+VIQZoyKlhktifMw', 31364.0), 131 | ('NTUtbi4YOHiNIV6SVrpwyg', 33709.0), 132 | ('364+KUYYuwlmezv1EvaE0g', 33945.0), 133 | ('YaD6Ktqw1iIWcFGgMEvMxg', 38248.0), 134 | ('cJSZfsidFuaMK9jY15g44A', 38668.0), 135 | ('UeP/HvscsnQXUK37Dyo8/w', 40861.0), 136 | ('xon2bN9ZToI4LpN4o7M2nQ', 41836.0), 137 | ('MQKXJCNNtWsRqoGbSaDorw', 47171.0), 138 | ('LCcqUwfmOFq+VXI2kGBQow', 49311.0), 139 | ('gMXF4DMHCWBjbgucOqWKQg', 50725.0), 140 | ('JKHDvGMcLQrR4G3zC2g9ug', 50875.0), 141 | ('Mp1feZZmnmMPJk8bGv0NaA', 51017.0), 142 | ('rhZyspOoakQBO9Ses3jl+A', 53781.0), 143 | ('JB9bMHKHoT+hMVjuBrbqlg', 56409.0), 144 | ('/DsgGH+7F6Fh2/81SzyXYA', 56512.0), 145 | ('InjjAuUMGHYUIRdRnkUw2w', 56903.0), 146 | ('otVFi6DLAO+v7XUAcmKttA', 57114.0), 147 | ('mVTvHObgjfzvZLOzl/xo2Q', 58550.0), 148 | ('uU1yLoXCgPtifROhCST0sA', 60267.0)) 149 | 150 | keys = list(map(itemgetter(0), items)) 151 | values = list(map(itemgetter(1), items)) 152 | 153 | 154 | class ProtocolTestCase(FixtureTestCase): 155 | def test_contains(self): 156 | for key in self.keys: 157 | self.assertTrue(key in self.skipdict) 158 | 159 | def test_get(self): 160 | for key, value in self.items: 161 | self.assertEqual(self.skipdict.get(key), value) 162 | 163 | def test_repr(self): 164 | self.assertEqual( 165 | repr(self.skipdict), 166 | "{" + ", ".join("%r: %r" % item for item in self.items) + "}" 167 | ) 168 | 169 | def test_change(self): 170 | for i, key in enumerate(self.keys): 171 | v = 1.0 / (i + 1) 172 | self.skipdict.change(key, v) 173 | self.assertEqual(self.skipdict[key], self.values[i] + v) 174 | 175 | def test_change_non_existing(self): 176 | self.skipdict.change('foo', 5.0) 177 | self.assertEqual(self.skipdict['foo'], 5.0) 178 | 179 | def test_del_item_concrete(self): 180 | del self.skipdict['mVTvHObgjfzvZLOzl/xo2Q'] 181 | 182 | def test_del_item_in_order(self): 183 | for key in self.keys: 184 | del self.skipdict[key] 185 | self.assertRaises(KeyError, self.skipdict.__getitem__, key) 186 | 187 | def test_del_item_in_reverse_order(self): 188 | for key in reversed(self.keys): 189 | del self.skipdict[key] 190 | self.assertRaises(KeyError, self.skipdict.__getitem__, key) 191 | 192 | def test_del_item_in_random_order(self): 193 | keys = list(self.keys) 194 | shuffle(keys) 195 | for key in keys: 196 | del self.skipdict[key] 197 | self.assertRaises(KeyError, self.skipdict.__getitem__, key) 198 | 199 | def test_del_item_missing(self): 200 | self.assertRaises( 201 | KeyError, self.skipdict.__delitem__, 202 | 'zI1yPoXCgPtifROhCST0sA' 203 | ) 204 | 205 | def test_set_item(self): 206 | self.skipdict['zI1yPoXCgPtifROhCST0sA'] = 62365.0 207 | self.assertEqual(self.skipdict['zI1yPoXCgPtifROhCST0sA'], 62365.0) 208 | 209 | def test_set_item_existing(self): 210 | for i in range(len(self.keys)): 211 | v = self.values[-i] + 1 212 | self.skipdict[self.keys[i]] = v 213 | self.assertEqual(self.skipdict[self.keys[i]], v) 214 | 215 | def test_set_item_existing_random(self): 216 | for i in (10, 20, 30, 40, 50, 60, 70): 217 | rnd = Random(i) 218 | for j in range(len(self.keys)): 219 | k = self.keys[rnd.randrange(0, len(self.keys), 1)] 220 | v = rnd.randrange(self.values[0], self.values[-1], 1) 221 | self.skipdict[k] = v 222 | self.assertEqual(self.skipdict[k], v) 223 | 224 | def test_set_many_items(self): 225 | for i in range(100000): 226 | key = "key%d" % i 227 | self.skipdict[key] = i * 2 228 | 229 | for i in reversed(range(10000)): 230 | key = "key%d" % i 231 | self.assertEqual(self.skipdict[key], i * 2) 232 | 233 | def test_setdefault(self): 234 | for key, value in self.items: 235 | self.assertEqual(self.skipdict.setdefault(key), value) 236 | 237 | def test_setdefault_already_exists(self): 238 | for key, value in self.items: 239 | self.assertEqual(self.skipdict.setdefault(key), value) 240 | 241 | def test_iteration(self): 242 | self.assertEqual(list(self.skipdict), self.keys) 243 | 244 | def test_length(self): 245 | self.assertEqual(len(self.skipdict), len(self.items)) 246 | 247 | def test_index(self): 248 | for i in range(len(self.items)): 249 | key, score = self.items[i] 250 | self.assertEqual(self.skipdict.index(key), i) 251 | 252 | def test_index_missing_key(self): 253 | self.assertRaises(KeyError, self.skipdict.index, 'foo') 254 | 255 | def test_index_no_args(self): 256 | self.assertRaises(TypeError, self.skipdict.index) 257 | 258 | def test_random(self): 259 | # Lets test 7 random insertion orders 260 | for i in (10, 20, 30, 40, 50, 60, 70): 261 | rnd = Random(i) 262 | to_insert = list(self.items) 263 | rnd.shuffle(to_insert) 264 | 265 | # Let's check several sets created with different methods 266 | set1 = self.make() 267 | for k, v in to_insert: 268 | set1[k] = v 269 | 270 | # Random access 271 | keys = list(self.keys) 272 | rnd.shuffle(keys) 273 | for key in keys: 274 | self.assertEqual(set1[key], self.skipdict[key]) 275 | 276 | # Create additional sets 277 | set2 = self.make(to_insert) 278 | set3 = self.make(set1.items()) 279 | set4 = self.make(set2.items()) 280 | 281 | # Check all of them 282 | cursets = (set1, set2, set3, set4) 283 | for cur in cursets: 284 | self.assertEqual(list(cur), self.keys) 285 | self.assertEqual(list(cur.keys()), self.keys) 286 | self.assertEqual(list(cur.values()), self.values) 287 | self.assertEqual(list(cur.items()), list(self.items)) 288 | 289 | # Check equality of all combinations 290 | all_sets = (self.skipdict, set1, set2, set3, set4) 291 | for s1, s2 in combinations(all_sets, 2): 292 | self.assertEqual(s1, s2) 293 | 294 | # Let's pick up items to delete 295 | left = list(self.items) 296 | to_delete = [] 297 | for i in range(rnd.randrange(10, 30)): 298 | idx = randrange(len(left)) 299 | to_delete.append(left[idx]) 300 | del left[idx] 301 | 302 | # Let's test deletion 303 | for cur in cursets: 304 | rnd.shuffle(to_delete) 305 | for key, value in to_delete: 306 | del cur[key] 307 | 308 | # Test random access 309 | keys = list(dict(left)) 310 | rnd.shuffle(keys) 311 | for key in keys: 312 | self.assertNotIn(key, to_delete) 313 | self.assertEqual(cur[key], self.skipdict[key]) 314 | 315 | self.assertEqual(list(cur.items()), left) 316 | self.assertNotEqual(cur, self.skipdict) 317 | 318 | # Let's reinsert keys in random order, and check if it's 319 | # still ok 320 | for j, cur in enumerate(cursets): 321 | rnd.shuffle(to_delete) 322 | for key, value in to_delete: 323 | cur[key] = value 324 | self.assertEqual(cur[key], value) 325 | self.assertEqual(cur, self.skipdict) 326 | self.assertEqual(len(cur), len(self.skipdict)) 327 | self.assertEqual(list(cur.items()), list(self.items)) 328 | 329 | keys1 = [] 330 | keys2 = [] 331 | 332 | for i in range(len(self.keys)): 333 | key = self.keys[i] 334 | self.assertEqual(cur.index(key), i) 335 | keys1.append(key) 336 | keys2.append(cur.keys()[i]) 337 | 338 | self.assertEqual(keys1, keys2) 339 | 340 | 341 | class ReverseProtocolTestCase(ProtocolTestCase): 342 | reverse = True 343 | 344 | 345 | class IteratorTestCaseMixin: 346 | @property 347 | def iterator(self): 348 | return self.func() 349 | 350 | @property 351 | def func(self): 352 | return getattr(self.skipdict, self.method) 353 | 354 | @property 355 | def expected(self): 356 | return getattr(self, self.method) 357 | 358 | def test_item(self): 359 | for i in range(len(self.keys)): 360 | self.assertEqual( 361 | self.iterator[i], 362 | self.expected[i] 363 | ) 364 | 365 | def test_item_range(self): 366 | values = tuple(self.func(2, 3)) 367 | 368 | for i in range(len(values)): 369 | self.assertEqual(values[i], self.func(2, 3)[i]) 370 | 371 | def test_item_min_zero(self): 372 | self.assertEqual( 373 | self.func(0)[0], 374 | self.expected[0] 375 | ) 376 | 377 | def test_call(self): 378 | self.assertRaises(TypeError, self.iterator) 379 | 380 | def test_step(self): 381 | self.assertRaises( 382 | ValueError, 383 | self.iterator.__getitem__, 384 | slice(None, None, 2), 385 | ) 386 | 387 | def test_length(self): 388 | self.assertRaises(TypeError, len, self.iterator) 389 | 390 | def test_next(self): 391 | iterator = self.func() 392 | i = 0 393 | while True: 394 | try: 395 | item = next(iterator) 396 | except StopIteration: 397 | break 398 | 399 | self.assertEqual(item, self.expected[i]) 400 | i += 1 401 | 402 | def test_repr(self): 403 | self.assertEqual( 404 | repr(self.iterator), 405 | "" % ( 411 | self.method, 412 | self.values[0], 413 | self.values[-1], 414 | id(self.skipdict), 415 | ) 416 | ) 417 | 418 | def test_min(self): 419 | self.assertEqual( 420 | list(self.func(self.values[25])), 421 | list(self.expected[25:]) 422 | ) 423 | 424 | def test_max(self): 425 | self.assertEqual( 426 | list(self.func(None, self.values[25])), 427 | list(self.expected[:26]) 428 | ) 429 | 430 | def test_min_max(self): 431 | self.assertEqual( 432 | list(self.func(self.values[13], self.values[25])), 433 | list(self.expected[13:26]) 434 | ) 435 | 436 | def test_max_min(self): 437 | self.assertEqual( 438 | list(self.func(self.values[25], self.values[13])), 439 | list(reversed(self.expected[13:26])) 440 | ) 441 | 442 | def decorate(f): 443 | def wrapper(self): 444 | s = f(self) 445 | self.assertEqual( 446 | list(self.expected[s]), 447 | list(self.iterator[s]) 448 | ) 449 | return wrapper 450 | 451 | @decorate 452 | def test_slice_min(self): 453 | return slice(13, None, 1) 454 | 455 | @decorate 456 | def test_slice_max(self): 457 | return slice(None, 13, 1) 458 | 459 | @decorate 460 | def test_slice_min_max(self): 461 | return slice(13, 25, 1) 462 | 463 | @decorate 464 | def test_slice_max_negative(self): 465 | return slice(None, -2, 1) 466 | 467 | 468 | class IterKeysTestCase(IteratorTestCaseMixin, FixtureTestCase): 469 | method = "keys" 470 | 471 | 472 | class IterValuesTestCase(IteratorTestCaseMixin, FixtureTestCase): 473 | method = "values" 474 | 475 | 476 | class IterItemsTestCase(IteratorTestCaseMixin, FixtureTestCase): 477 | method = "items" 478 | --------------------------------------------------------------------------------