diff --git a/.gitignore b/.gitignore index e43c43a..7baad79 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist *.pyc *.so *.swp +*~ diff --git a/CHANGES b/CHANGES index 82a2ebd..b388c0a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,33 @@ +* cllist-1.0 + + Forked from python-llist to python-cllist + + Work by Tim Savannah: + + - Implement pop(idx) to pop any given index + - Implement "contains" sequence method, so the "in" operator doesn't run the whole list multiple times + - Implement "index" and "rindex" methods to return an index/rindex + - Remove "last_accessed_idx" and "last_accessed" node from dllist, replace with "middle" which is used when + the list size exceeds a certain value (defined as 10). This greatly improves random-access and random-pop performance + on dllist to be comprable or better to that of a base python list + - Remove the "hash" function, which did NOT generate unique hashes (very easy to construct two linked lists with same hash, + such as [1, 5, 7] and [5, 1, 7] or [2, 1] and [3] + - Remove all compiler warnings + - Add some basic benchmarks + - Add some more tests + - Some minor cleanups + + - Move types into headers, make generic LList node and list structures, which are common to both double and single linked lists. + - Allow a double-linked list to extend with a single-linked list, and a single-linked list to extend with a double (for much higher performance) + + - Implement mappings on sllist and dllist + - Implement slicing (including with step) on both sllist and dllist + + - Add __version__ and __version_tuple__ + + - Some general optimizations + + * llist-0.4 (2013-01-01) - Python 3.x support diff --git a/MANIFEST.in b/MANIFEST.in index 812b7fa..216cad4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,12 @@ -include Makefile CHANGES LICENSE MANIFEST MANIFEST.in +include Makefile +include CHANGES +include LICENSE +include MANIFEST.in +include README.rst +include README.md recursive-include src *.c *.h recursive-include docs *.py *.rst +recursive-include pydocs *.html include docs/Makefile docs/make.bat graft docs/_static graft docs/_templates diff --git a/README b/README deleted file mode 100644 index 36da05b..0000000 --- a/README +++ /dev/null @@ -1,36 +0,0 @@ -llist - linked lists for CPython -================================ - -llist is an extension module for CPython providing basic linked list -data structures. -Collections implemented in the llist module perform well in problems -which rely on fast insertions and/or deletions of elements in -the middle of a sequence. -For this kind of workload, they can be significantly faster than -collections.deque or standard Python lists. - -This extension requires CPython 2.5 or newer (3.x is supported). -If you are looking for an implementation of linked lists in pure Python, -visit http://github.com/rgsoda/pypy-llist/ -The pypy-llist module has the same API as this extension, but is -significantly slower in CPython. - -Currently llist provides the following types of linked lists: - - dllist - a doubly linked list - - sllist - a singly linked list - -Full documentation of these classes is available at: -http://packages.python.org/llist/ - -To install this package, either run "pip install llist", -or download it manually from http://pypi.python.org/pypi -then unpack the sources and compile them with "python setup.py install". - -The most current development version is available at: -https://github.com/ajakubek/python-llist/ - -Bugs can be reported at: -https://github.com/ajakubek/python-llist/issues - -This software is distributed under the MIT license. -Please see the LICENSE file included in the package for details. diff --git a/README.md b/README.md new file mode 100644 index 0000000..91bd042 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +cllist - C-implemented Linked Lists for Python +============================================== + +cllist is an extension module for CPython providing basic linked list +data structures. + +Collections implemented in the llist module perform well in problems +which rely on fast insertions, pops, and removal of elements in +the middle of a sequence. + +For this kind of workload, they can be significantly faster than +collections.deque or standard Python lists. + + +This project was forked from https://github.com/ajakubek/python-llist + +and adds many features and enhancements to the original, under a new name "cllist". + +The cllist version now lives at https://github.com/kata198/python-cllist + +If you were using the previous module, you can change to this new module, it is completely backwards (but not forwards) compatible. + + + +Single Linked List +------------------ + +Singly linked lists are provided by the "sllist" module. This is your basic single-linked list, and might be useful for some scenarios. + +A single linked list is far less efficient at everything than the double-linked list implementation. + + +Double Linked List +------------------ + +A double-linked list is provided by the "dllist" module. + +This provides great performance when doing pops and insertions at random (in the middle), or at either end. + +This implementation has been enhanced by implementing a "middle" marker. + +This "middle" marker is used when the list size exceeds 10 elements, and is used in all operations which involve walking the list, + +which ensures that AT MOST N/4 elements will need to be walked (so the shortest distance from either start, middle, or end is calculated, and walked from there). + +This additional feature makes this linked list much more efficient on larger data sets than a standard double-linked list implementation. + + +Why use a linked list? +---------------------- + +A linked list should be used when you are going to be adding or removing elements in the middle of the dataset. A standard python list forces the entire list +to be reallocated and copied when such happens, whereas this can do so without reallocating. + + diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..91bd042 --- /dev/null +++ b/README.rst @@ -0,0 +1,55 @@ +cllist - C-implemented Linked Lists for Python +============================================== + +cllist is an extension module for CPython providing basic linked list +data structures. + +Collections implemented in the llist module perform well in problems +which rely on fast insertions, pops, and removal of elements in +the middle of a sequence. + +For this kind of workload, they can be significantly faster than +collections.deque or standard Python lists. + + +This project was forked from https://github.com/ajakubek/python-llist + +and adds many features and enhancements to the original, under a new name "cllist". + +The cllist version now lives at https://github.com/kata198/python-cllist + +If you were using the previous module, you can change to this new module, it is completely backwards (but not forwards) compatible. + + + +Single Linked List +------------------ + +Singly linked lists are provided by the "sllist" module. This is your basic single-linked list, and might be useful for some scenarios. + +A single linked list is far less efficient at everything than the double-linked list implementation. + + +Double Linked List +------------------ + +A double-linked list is provided by the "dllist" module. + +This provides great performance when doing pops and insertions at random (in the middle), or at either end. + +This implementation has been enhanced by implementing a "middle" marker. + +This "middle" marker is used when the list size exceeds 10 elements, and is used in all operations which involve walking the list, + +which ensures that AT MOST N/4 elements will need to be walked (so the shortest distance from either start, middle, or end is calculated, and walked from there). + +This additional feature makes this linked list much more efficient on larger data sets than a standard double-linked list implementation. + + +Why use a linked list? +---------------------- + +A linked list should be used when you are going to be adding or removing elements in the middle of the dataset. A standard python list forces the entire list +to be reallocated and copied when such happens, whereas this can do so without reallocating. + + diff --git a/docs/index.rst b/docs/index.rst index b09b4b4..5d988a3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,6 +9,7 @@ .. module:: llist :synopsis: Linked list datatypes for Python +.. moduleauthor:: Timothy Savannah .. moduleauthor:: Adam Jakubek .. moduleauthor:: Rafał Gałczyński diff --git a/pydocs/cllist.dllist.html b/pydocs/cllist.dllist.html new file mode 100644 index 0000000..e0e49ca --- /dev/null +++ b/pydocs/cllist.dllist.html @@ -0,0 +1,109 @@ + + +Python: class dllist + + +

+ + + + + + + +
 
+cllist.dllist = class dllist(__builtin__.object)
   Doubly linked list
 
 Methods defined here:
+
__add__(...)
x.__add__(y) <==> x+y
+ +
__contains__(...)
x.__contains__(y) <==> y in x
+ +
__delitem__(...)
x.__delitem__(y) <==> del x[y]
+ +
__eq__(...)
x.__eq__(y) <==> x==y
+ +
__ge__(...)
x.__ge__(y) <==> x>=y
+ +
__getitem__(...)
x.__getitem__(y) <==> x[y]
+ +
__getslice__(...)
x.__getslice__(i, j) <==> x[i:j]

+Use of negative indices is not supported.
+ +
__gt__(...)
x.__gt__(y) <==> x>y
+ +
__iadd__(...)
x.__iadd__(y) <==> x+=y
+ +
__init__(...)
x.__init__(...) initializes x; see help(type(x)) for signature
+ +
__iter__(...)
x.__iter__() <==> iter(x)
+ +
__le__(...)
x.__le__(y) <==> x<=y
+ +
__len__(...)
x.__len__() <==> len(x)
+ +
__lt__(...)
x.__lt__(y) <==> x<y
+ +
__mul__(...)
x.__mul__(n) <==> x*n
+ +
__ne__(...)
x.__ne__(y) <==> x!=y
+ +
__repr__(...)
x.__repr__() <==> repr(x)
+ +
__rmul__(...)
x.__rmul__(n) <==> n*x
+ +
__setitem__(...)
x.__setitem__(i, y) <==> x[i]=y
+ +
__str__(...)
x.__str__() <==> str(x)
+ +
append(...)
Append element at the end of the list
+ +
appendleft(...)
Append element at the beginning of the list
+ +
appendright(...)
Append element at the end of the list
+ +
clear(...)
Remove all elements from the list
+ +
extend(...)
Append elements from iterable at the right side of the list
+ +
extendleft(...)
Append elements from iterable at the left side of the list
+ +
extendright(...)
Append elements from iterable at the right side of the list
+ +
index(...)
Returns the first index of a value
+ +
insert(...)
Inserts element before node
+ +
nodeat(...)
Return node at index
+ +
pop(...)
Remove an element by index from the list and return it, or last item if no index provided
+ +
popleft(...)
Remove first element from the list and return it
+ +
popright(...)
Remove last element from the list and return it
+ +
remove(...)
Remove element from the list
+ +
rindex(...)
Returns the last index of a value
+ +
rotate(...)
Rotate the list n steps to the right
+ +
+Data descriptors defined here:
+
first
+
First node
+
+
last
+
Next node
+
+
middle
+
Middle node
+
+
size
+
Number of elements in the list
+
+
+Data and other attributes defined here:
+
__new__ = <built-in method __new__ of type object>
T.__new__(S, ...) -> a new object with type S, a subtype of T
+ +
+ \ No newline at end of file diff --git a/pydocs/cllist.html b/pydocs/cllist.html new file mode 100644 index 0000000..e70c377 --- /dev/null +++ b/pydocs/cllist.html @@ -0,0 +1,341 @@ + + +Python: module cllist + + + + + +
 
+ 
cllist (version 1.0.0)
index
/home/media/projects/python-llist/myenv2/lib/python2.7/site-packages/cllist.so
+

C-extension providing single and double linked lists.

+

+ + + + + +
 
+Classes
       
+
__builtin__.object +
+
+
llist.dllist +
llist.dllistiterator +
llist.dllistnode +
llist.sllist +
llist.sllistiterator +
llist.sllistnode +
+
+
+

+ + + + + + + +
 
+class dllist(__builtin__.object)
   Doubly linked list
 
 Methods defined here:
+
__add__(...)
x.__add__(y) <==> x+y
+ +
__contains__(...)
x.__contains__(y) <==> y in x
+ +
__delitem__(...)
x.__delitem__(y) <==> del x[y]
+ +
__eq__(...)
x.__eq__(y) <==> x==y
+ +
__ge__(...)
x.__ge__(y) <==> x>=y
+ +
__getitem__(...)
x.__getitem__(y) <==> x[y]
+ +
__getslice__(...)
x.__getslice__(i, j) <==> x[i:j]

+Use of negative indices is not supported.
+ +
__gt__(...)
x.__gt__(y) <==> x>y
+ +
__iadd__(...)
x.__iadd__(y) <==> x+=y
+ +
__init__(...)
x.__init__(...) initializes x; see help(type(x)) for signature
+ +
__iter__(...)
x.__iter__() <==> iter(x)
+ +
__le__(...)
x.__le__(y) <==> x<=y
+ +
__len__(...)
x.__len__() <==> len(x)
+ +
__lt__(...)
x.__lt__(y) <==> x<y
+ +
__mul__(...)
x.__mul__(n) <==> x*n
+ +
__ne__(...)
x.__ne__(y) <==> x!=y
+ +
__repr__(...)
x.__repr__() <==> repr(x)
+ +
__rmul__(...)
x.__rmul__(n) <==> n*x
+ +
__setitem__(...)
x.__setitem__(i, y) <==> x[i]=y
+ +
__str__(...)
x.__str__() <==> str(x)
+ +
append(...)
Append element at the end of the list
+ +
appendleft(...)
Append element at the beginning of the list
+ +
appendright(...)
Append element at the end of the list
+ +
clear(...)
Remove all elements from the list
+ +
extend(...)
Append elements from iterable at the right side of the list
+ +
extendleft(...)
Append elements from iterable at the left side of the list
+ +
extendright(...)
Append elements from iterable at the right side of the list
+ +
index(...)
Returns the first index of a value
+ +
insert(...)
Inserts element before node
+ +
nodeat(...)
Return node at index
+ +
pop(...)
Remove an element by index from the list and return it, or last item if no index provided
+ +
popleft(...)
Remove first element from the list and return it
+ +
popright(...)
Remove last element from the list and return it
+ +
remove(...)
Remove element from the list
+ +
rindex(...)
Returns the last index of a value
+ +
rotate(...)
Rotate the list n steps to the right
+ +
+Data descriptors defined here:
+
first
+
First node
+
+
last
+
Next node
+
+
middle
+
Middle node
+
+
size
+
Number of elements in the list
+
+
+Data and other attributes defined here:
+
__new__ = <built-in method __new__ of type object>
T.__new__(S, ...) -> a new object with type S, a subtype of T
+ +

+ + + + + + + +
 
+class dllistiterator(__builtin__.object)
   Doubly linked list iterator
 
 Methods defined here:
+
next(...)
x.next() -> the next value, or raise StopIteration
+ +
+Data and other attributes defined here:
+
__new__ = <built-in method __new__ of type object>
T.__new__(S, ...) -> a new object with type S, a subtype of T
+ +

+ + + + + + + +
 
+class dllistnode(__builtin__.object)
   Doubly linked list node
 
 Methods defined here:
+
__call__(...)
x.__call__(...) <==> x(...)
+ +
__init__(...)
x.__init__(...) initializes x; see help(type(x)) for signature
+ +
__repr__(...)
x.__repr__() <==> repr(x)
+ +
__str__(...)
x.__str__() <==> str(x)
+ +
+Data descriptors defined here:
+
next
+
Next node
+
+
prev
+
Previous node
+
+
value
+
Value stored in node
+
+
+Data and other attributes defined here:
+
__new__ = <built-in method __new__ of type object>
T.__new__(S, ...) -> a new object with type S, a subtype of T
+ +

+ + + + + + + +
 
+class sllist(__builtin__.object)
   Singly linked list
 
 Methods defined here:
+
__add__(...)
x.__add__(y) <==> x+y
+ +
__contains__(...)
x.__contains__(y) <==> y in x
+ +
__delitem__(...)
x.__delitem__(y) <==> del x[y]
+ +
__eq__(...)
x.__eq__(y) <==> x==y
+ +
__ge__(...)
x.__ge__(y) <==> x>=y
+ +
__getitem__(...)
x.__getitem__(y) <==> x[y]
+ +
__getslice__(...)
x.__getslice__(i, j) <==> x[i:j]

+Use of negative indices is not supported.
+ +
__gt__(...)
x.__gt__(y) <==> x>y
+ +
__iadd__(...)
x.__iadd__(y) <==> x+=y
+ +
__init__(...)
x.__init__(...) initializes x; see help(type(x)) for signature
+ +
__iter__(...)
x.__iter__() <==> iter(x)
+ +
__le__(...)
x.__le__(y) <==> x<=y
+ +
__len__(...)
x.__len__() <==> len(x)
+ +
__lt__(...)
x.__lt__(y) <==> x<y
+ +
__mul__(...)
x.__mul__(n) <==> x*n
+ +
__ne__(...)
x.__ne__(y) <==> x!=y
+ +
__repr__(...)
x.__repr__() <==> repr(x)
+ +
__rmul__(...)
x.__rmul__(n) <==> n*x
+ +
__setitem__(...)
x.__setitem__(i, y) <==> x[i]=y
+ +
__str__(...)
x.__str__() <==> str(x)
+ +
append(...)
Append element at the end of the list
+ +
appendleft(...)
Append element at the beginning of the list
+ +
appendright(...)
Append element at the end of the list
+ +
clear(...)
Remove all elements from the list
+ +
extend(...)
Append elements from iterable at the right side of the list
+ +
extendleft(...)
Append elements from iterable at the left side of the list
+ +
extendright(...)
Append elements from iterable at the right side of the list
+ +
index(...)
Returns the first index of a value
+ +
insertafter(...)
Inserts element after node
+ +
insertbefore(...)
Inserts element before node
+ +
nodeat(...)
Return node at index
+ +
pop(...)
Remove an element by index from the list and return it, or last item if no index provided
+ +
popleft(...)
Remove first element from the list and return it
+ +
popright(...)
Remove last element from the list and return it
+ +
remove(...)
Remove element from the list
+ +
rindex(...)
Returns the last index of a value
+ +
rotate(...)
Rotate the list n steps to the right
+ +
+Data descriptors defined here:
+
first
+
First node
+
+
last
+
Next node
+
+
size
+
size
+
+
+Data and other attributes defined here:
+
__new__ = <built-in method __new__ of type object>
T.__new__(S, ...) -> a new object with type S, a subtype of T
+ +

+ + + + + + + +
 
+class sllistiterator(__builtin__.object)
   Singly linked list iterator
 
 Methods defined here:
+
next(...)
x.next() -> the next value, or raise StopIteration
+ +
+Data and other attributes defined here:
+
__new__ = <built-in method __new__ of type object>
T.__new__(S, ...) -> a new object with type S, a subtype of T
+ +

+ + + + + + + +
 
+class sllistnode(__builtin__.object)
   Singly linked list node
 
 Methods defined here:
+
__call__(...)
x.__call__(...) <==> x(...)
+ +
__init__(...)
x.__init__(...) initializes x; see help(type(x)) for signature
+ +
__repr__(...)
x.__repr__() <==> repr(x)
+ +
__str__(...)
x.__str__() <==> str(x)
+ +
+Data descriptors defined here:
+
next
+
next node
+
+
value
+
value
+
+
+Data and other attributes defined here:
+
__new__ = <built-in method __new__ of type object>
T.__new__(S, ...) -> a new object with type S, a subtype of T
+ +

+ + + + + +
 
+Data
       __version__ = '1.0.0'
+__version_tuple__ = (1L, 0L, 0L)
+ \ No newline at end of file diff --git a/pydocs/cllist.sllist.html b/pydocs/cllist.sllist.html new file mode 100644 index 0000000..b55a401 --- /dev/null +++ b/pydocs/cllist.sllist.html @@ -0,0 +1,108 @@ + + +Python: class sllist + + +

+ + + + + + + +
 
+cllist.sllist = class sllist(__builtin__.object)
   Singly linked list
 
 Methods defined here:
+
__add__(...)
x.__add__(y) <==> x+y
+ +
__contains__(...)
x.__contains__(y) <==> y in x
+ +
__delitem__(...)
x.__delitem__(y) <==> del x[y]
+ +
__eq__(...)
x.__eq__(y) <==> x==y
+ +
__ge__(...)
x.__ge__(y) <==> x>=y
+ +
__getitem__(...)
x.__getitem__(y) <==> x[y]
+ +
__getslice__(...)
x.__getslice__(i, j) <==> x[i:j]

+Use of negative indices is not supported.
+ +
__gt__(...)
x.__gt__(y) <==> x>y
+ +
__iadd__(...)
x.__iadd__(y) <==> x+=y
+ +
__init__(...)
x.__init__(...) initializes x; see help(type(x)) for signature
+ +
__iter__(...)
x.__iter__() <==> iter(x)
+ +
__le__(...)
x.__le__(y) <==> x<=y
+ +
__len__(...)
x.__len__() <==> len(x)
+ +
__lt__(...)
x.__lt__(y) <==> x<y
+ +
__mul__(...)
x.__mul__(n) <==> x*n
+ +
__ne__(...)
x.__ne__(y) <==> x!=y
+ +
__repr__(...)
x.__repr__() <==> repr(x)
+ +
__rmul__(...)
x.__rmul__(n) <==> n*x
+ +
__setitem__(...)
x.__setitem__(i, y) <==> x[i]=y
+ +
__str__(...)
x.__str__() <==> str(x)
+ +
append(...)
Append element at the end of the list
+ +
appendleft(...)
Append element at the beginning of the list
+ +
appendright(...)
Append element at the end of the list
+ +
clear(...)
Remove all elements from the list
+ +
extend(...)
Append elements from iterable at the right side of the list
+ +
extendleft(...)
Append elements from iterable at the left side of the list
+ +
extendright(...)
Append elements from iterable at the right side of the list
+ +
index(...)
Returns the first index of a value
+ +
insertafter(...)
Inserts element after node
+ +
insertbefore(...)
Inserts element before node
+ +
nodeat(...)
Return node at index
+ +
pop(...)
Remove an element by index from the list and return it, or last item if no index provided
+ +
popleft(...)
Remove first element from the list and return it
+ +
popright(...)
Remove last element from the list and return it
+ +
remove(...)
Remove element from the list
+ +
rindex(...)
Returns the last index of a value
+ +
rotate(...)
Rotate the list n steps to the right
+ +
+Data descriptors defined here:
+
first
+
First node
+
+
last
+
Next node
+
+
size
+
size
+
+
+Data and other attributes defined here:
+
__new__ = <built-in method __new__ of type object>
T.__new__(S, ...) -> a new object with type S, a subtype of T
+ +
+ \ No newline at end of file diff --git a/pydocs/index.html b/pydocs/index.html new file mode 120000 index 0000000..f7bbc6f --- /dev/null +++ b/pydocs/index.html @@ -0,0 +1 @@ +cllist.html \ No newline at end of file diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 6cb024c..f7c3d48 --- a/setup.py +++ b/setup.py @@ -1,37 +1,60 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: utf-8 -*- -VERSION='0.4' +VERSION='1.0.0' -from distutils.core import setup, Extension +import os -sources = ['src/llist.c', +try: + from setuptools import setup, Extension +except ImportError: + from distutils.core import setup, Extension + +sources = ['src/cllist.c', 'src/dllist.c', 'src/sllist.c', ] -setup(name='llist', - description='Linked list data structures for Python', - long_description=open('README').read(), - author='Adam Jakubek, Rafał Gałczyński', - author_email='ajakubek@gmail.com, rafal.galczynski@gmail.com', - version=VERSION, - url='https://github.com/ajakubek/python-llist', - download_url='http://pypi.python.org/pypi/llist/%s' % VERSION, - license='MIT', - keywords='linked list, list', - ext_modules=[Extension('llist', sources)], - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Programming Language :: C', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: Implementation :: CPython', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - ) +if __name__ == '__main__': + + dirName = os.path.dirname(__file__) + if dirName and os.getcwd() != dirName: + os.chdir(dirName) + + try: + with open('README.rst', 'rt') as f: + long_description = f.read() + except Exception as e: + long_description = 'Error reading description: ' + str(e) + + setup(name='llist', + description='C-implemented linked-list module for Python', + long_description=long_description, + author='Timothy Savannah, Adam Jakubek, Rafał Gałczyński', + author_email='kata198@gmail.com, ajakubek@gmail.com, rafal.galczynski@gmail.com', + maintainer_email='kata198@gmail.com', + version=VERSION, + url='https://github.com/kata198/python-cllist', + download_url='http://pypi.python.org/pypi/cllist/%s' % VERSION, + license='MIT', + keywords=['llist', 'linked', 'double', 'single', 'linked list'], + ext_modules=[Extension('cllist', sources)], + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Programming Language :: C', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: Implementation :: CPython', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + ) diff --git a/src/cllist.c b/src/cllist.c new file mode 100644 index 0000000..79ded54 --- /dev/null +++ b/src/cllist.c @@ -0,0 +1,162 @@ +/* Copyright (c) 2011-2013 Adam Jakubek, Rafał Gałczyński + * Copyright (c) 2017 Timothy Savannah + * Released under the MIT license (see attached LICENSE file). + */ + +#include + +#include "llist.h" +#include "sllist.h" +#include "dllist.h" + +static PyMethodDef cllist_methods[] = +{ + { NULL } /* sentinel */ +}; + +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#define PyMODINIT_FUNC void +#endif + +#define docstr ("C-extension providing single and double linked lists.") + +#if PY_MAJOR_VERSION >= 3 +#define PyString_FromString PyUnicode_FromString +#endif + +static PyObject *get_version_tuple_object(long major, long minor, long patch) +{ + + PyObject *majorO, *minorO, *patchO; + PyObject *versionTupleObj; + + majorO = PyLong_FromLong(major); + minorO = PyLong_FromLong(minor); + patchO = PyLong_FromLong(patch); + + Py_INCREF(majorO); + Py_INCREF(minorO); + Py_INCREF(patchO); + + versionTupleObj = PyTuple_New(3); + + PyTuple_SetItem(versionTupleObj, 0, majorO); + PyTuple_SetItem(versionTupleObj, 1, minorO); + PyTuple_SetItem(versionTupleObj, 2, patchO); + + Py_INCREF(versionTupleObj); + + return versionTupleObj; +} + +static PyObject *get_version_object(const char *versionStr) +{ + PyObject *versionObj; + + versionObj = PyString_FromString(versionStr); + + Py_INCREF(versionObj); + + return versionObj; +} + + +static void free_version_tuple_object(PyObject *versionTupleObj) +{ + PyObject *majorO, *minorO, *patchO; + + majorO = PyTuple_GetItem(versionTupleObj, 0); + minorO = PyTuple_GetItem(versionTupleObj, 1); + patchO = PyTuple_GetItem(versionTupleObj, 2); + + Py_DECREF(majorO); + Py_DECREF(minorO); + Py_DECREF(patchO); + + Py_DECREF(versionTupleObj); + +} + +static void free_version_object(PyObject *versionObj) +{ + + Py_DECREF(versionObj); +} + +PyObject *mod_version_tuple; +PyObject *mod_version; + + + +static void cllist_setup_module(PyObject *mod) +{ + mod_version_tuple = get_version_tuple_object(LLIST_VERSION_MAJOR, LLIST_VERSION_MINOR, LLIST_VERSION_PATCH); + mod_version = get_version_object(LLIST_VERSION_STR); + + PyModule_AddObject(mod, "__version_tuple__", mod_version_tuple); + PyModule_AddObject(mod, "__version__", mod_version); + +} + +static void cllist_free(void *modPtr) +{ + free_version_object(mod_version); + free_version_tuple_object(mod_version_tuple); +} + + +#if PY_MAJOR_VERSION >= 3 + +static struct PyModuleDef cllist_moduledef = { + PyModuleDef_HEAD_INIT, + "cllist", /* m_name */ + docstr, /* m_doc */ + -1, /* m_size */ + cllist_methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + cllist_free, /* m_free */ +}; + +PyMODINIT_FUNC +PyInit_cllist(void) +{ + PyObject* m; + + if (!sllist_init_type()) + return NULL; + if (!dllist_init_type()) + return NULL; + + m = PyModule_Create(&cllist_moduledef); + + cllist_setup_module(m); + + sllist_register(m); + dllist_register(m); + + return m; +} + +#else + +PyMODINIT_FUNC +initcllist(void) +{ + PyObject* m; + + if (!sllist_init_type()) + return; + if (!dllist_init_type()) + return; + + m = Py_InitModule3("cllist", cllist_methods, + docstr); + + cllist_setup_module(m); + sllist_register(m); + dllist_register(m); +} + +#endif /* PY_MAJOR_VERSION >= 3 */ diff --git a/src/dllist.c b/src/dllist.c index 03eaa03..80ffb81 100644 --- a/src/dllist.c +++ b/src/dllist.c @@ -1,32 +1,29 @@ /* Copyright (c) 2011-2013 Adam Jakubek, Rafał Gałczyński + * Copyright (c) 2017 Timothy Savannah * Released under the MIT license (see attached LICENSE file). */ #include #include #include "py23macros.h" +#include "llist.h" +#include "llist_types.h" +#include "dllist_types.h" +#include "sllist_types.h" #ifndef PyVarObject_HEAD_INIT #define PyVarObject_HEAD_INIT(type, size) \ PyObject_HEAD_INIT(type) size, #endif +#include -static PyTypeObject DLListType; -static PyTypeObject DLListNodeType; -static PyTypeObject DLListIteratorType; +#include + +#define START_MIDDLE_AFTER 10 -/* DLListNode */ -typedef struct -{ - PyObject_HEAD - PyObject* value; - PyObject* prev; - PyObject* next; - PyObject* list_weakref; -} DLListNodeObject; /* Convenience function for creating list nodes. * Automatically update pointers in neigbours. @@ -63,7 +60,7 @@ static DLListNodeObject* dllistnode_create(PyObject* prev, } node = (DLListNodeObject*)PyObject_CallObject( - (PyObject*)&DLListNodeType, args); + (PyObject*)DLListNodeType, args); Py_XDECREF(args); @@ -200,6 +197,15 @@ static int dllistnode_init(DLListNodeObject* self, return 0; } +/* + * dllistnode_make - A slightly cheaper version that doesn't set next or prev + */ +static inline DLListNodeObject *dllistnode_make(DLListObject *dllist, PyObject *value) +{ + return (DLListNodeObject *)llistnode_make(DLListNodeType, (PyObject *)dllist, value); +} + + static PyObject* dllistnode_call(DLListNodeObject* self, PyObject* args, PyObject* kw) @@ -229,7 +235,7 @@ static PyMemberDef DLListNodeMembers[] = { NULL }, /* sentinel */ }; -static PyTypeObject DLListNodeType = +PyTypeObject DLListNodeType[] = { { PyVarObject_HEAD_INIT(NULL, 0) "llist.dllistnode", /* tp_name */ @@ -269,25 +275,125 @@ static PyTypeObject DLListNodeType = (initproc)dllistnode_init, /* tp_init */ 0, /* tp_alloc */ dllistnode_new, /* tp_new */ +} }; /* DLList */ -typedef struct +static PyObject* dllist_node_at(PyObject* self, PyObject* indexObject); +static DLListNodeObject* dllist_get_node_internal(DLListObject* self, Py_ssize_t index); + + +/**************** +**** Middle functions +******************** +***/ + + +/* _middle_do_recalc - + * Do a recalulation of middle + */ +static inline void _middle_do_recalc(DLListObject *self) { - PyObject_HEAD - PyObject* first; - PyObject* last; - PyObject* last_accessed_node; - Py_ssize_t last_accessed_idx; - Py_ssize_t size; - PyObject* weakref_list; -} DLListObject; + Py_ssize_t midIdx; + + midIdx = self->size / 2; + /* Set middle_idx to -1 so we don't use it on the lookup */ + + self->middle_idx = -1; + self->middle = (PyObject *) dllist_get_node_internal(self, midIdx); + + self->middle_idx = midIdx; +} -static Py_ssize_t py_ssize_t_abs(Py_ssize_t x) +/* _middle_postappend_adjust - + * Do an adjustment after a move + * + * sizeAdjust: If called before size is incremented/decremented, pass the difference here + */ +static inline void _middle_postappend_adjust(DLListObject *self, int sizeAdjust) { - return (x >= 0) ? x : -x; + Py_ssize_t midpoint; + + midpoint = ( (self->size) + sizeAdjust) / 2; + + if ( self->middle_idx < midpoint ) + { + /* We move at least one, then see if we need to move more */ + for( ; self->middle_idx < midpoint; self->middle_idx++ ) + { + self->middle = ((DLListNodeObject*)self->middle)->next; + } + } + else if ( self->middle_idx > midpoint ) + { + for( ; self->middle_idx > midpoint ; self->middle_idx-- ) + { + self->middle = ((DLListNodeObject*)self->middle)->prev; + } + } + +} + +/** + * _middle_popped_left_of_middle - When left-of-middle is popped, + * call this to adjust the middle index + */ +static inline void _middle_popped_left_of_middle(DLListObject *self) +{ + if ( self->middle != Py_None ) { + self->middle_idx -= 1; + } +} + +/** + * _middle_check_recalc - + Do a recalc if we are past START_MIDDLE_AFTER + */ +static inline void _middle_check_recalc(DLListObject *self) +{ + if( self->size > START_MIDDLE_AFTER ) { + _middle_do_recalc(self); + } +} + +/** + * _middle_check_adjust_or_recalc - + Check if we are past START_MIDDLE_AFTER, and if so, update middle or recalc + */ +static inline void _middle_check_adjust_or_recalc(DLListObject *self) +{ + if ( self->size > START_MIDDLE_AFTER ) + { + if ( self->middle != Py_None) + { + _middle_postappend_adjust(self, 0); + } + else + { + _middle_do_recalc(self); + } + } +} + + +/** + * _middle_check_on_shrink + After shrinking the list, check if we are small enough that we should clear middle, + and do so if we are. + + return - 1 if middle was cleared, otherwise 0 +*/ +static inline int _middle_check_on_shrink(DLListObject *self) +{ + if ( self->size <= START_MIDDLE_AFTER ) + { + self->middle = Py_None; + self->middle_idx = -1; + return 1; + } + return 0; } /* Convenience function for locating list nodes using index. */ @@ -295,7 +401,7 @@ static DLListNodeObject* dllist_get_node_internal(DLListObject* self, Py_ssize_t index) { Py_ssize_t i; - Py_ssize_t middle = self->size / 2; + Py_ssize_t midpoint = self->size / 2; DLListNodeObject* node; Py_ssize_t start_pos; int reverse_dir; @@ -305,29 +411,37 @@ static DLListNodeObject* dllist_get_node_internal(DLListObject* self, PyErr_SetString(PyExc_IndexError, "Index out of range"); return NULL; } - /* pick the closest base node */ - if (index <= middle) + if (index <= midpoint) { - node = (DLListNodeObject*)self->first; - start_pos = 0; - reverse_dir = 0; + if ( self->middle_idx != -1 && self->middle_idx - index < index ) + { + node = (DLListNodeObject*)self->middle; + start_pos = self->middle_idx; + reverse_dir = 1; + } + else + { + node = (DLListNodeObject*)self->first; + start_pos = 0; + reverse_dir = 0; + } } else { - node = (DLListNodeObject*)self->last; - start_pos = self->size - 1; - reverse_dir = 1; - } - - /* check if last accessed index is closer */ - if (self->last_accessed_node != Py_None && - self->last_accessed_idx >= 0 && - py_ssize_t_abs(index - self->last_accessed_idx) < middle) - { - node = (DLListNodeObject*)self->last_accessed_node; - start_pos = self->last_accessed_idx; - reverse_dir = (index < self->last_accessed_idx) ? 1 : 0; + if ( self->middle_idx != -1 && index - self->middle_idx < self->size - index ) + { + node = (DLListNodeObject*)self->middle; + start_pos = self->middle_idx; + reverse_dir = 0; + + } + else + { + node = (DLListNodeObject*)self->last; + start_pos = self->size - 1; + reverse_dir = 1; + } } assert((PyObject*)node != Py_None); @@ -352,75 +466,120 @@ static DLListNodeObject* dllist_get_node_internal(DLListObject* self, * the list with elements from a sequence. */ static int dllist_extend_internal(DLListObject* self, PyObject* sequence) { - Py_ssize_t i; + Py_ssize_t i = 0; Py_ssize_t sequence_len; - if (PyObject_TypeCheck(sequence, &DLListType)) + if (PyObject_TypeCheck(sequence, DLListType) || PyObject_TypeCheck(sequence, SLListType)) { - /* Special path for extending with a DLList. - * It's not strictly required but it will maintain - * the last accessed item. */ - PyObject* iter_node_obj = ((DLListObject*)sequence)->first; - PyObject* last_node_obj = self->last; + /* Special path for extending with a LList (double or single) + * It's not strictly required but greatly improves performance + */ + PyObject *iter_node_obj = ((LListObject*)sequence)->first; + LListNodeObject *iter_node; + PyObject *new_node; - while (iter_node_obj != Py_None) - { - DLListNodeObject* iter_node = (DLListNodeObject*)iter_node_obj; - PyObject* new_node; + sequence_len = ((LListObject*)sequence)->size; - new_node = (PyObject*)dllistnode_create( - self->last, NULL, iter_node->value, (PyObject*)self); + if ( unlikely(sequence_len == 0) ) + return 1; + + self->size += sequence_len; - if (self->first == Py_None) - self->first = new_node; - self->last = new_node; + if ( self->size == sequence_len ) + { + iter_node = (LListNodeObject*)iter_node_obj; - if (iter_node_obj == last_node_obj) - { - /* This is needed to terminate loop if self == sequence. */ - break; - } + new_node = (PyObject *)dllistnode_make(self, iter_node->value); + self->first = new_node; + self->last = new_node; + ((DLListNodeObject *)new_node)->prev = Py_None; iter_node_obj = iter_node->next; + sequence_len -= 1; } - self->size += ((DLListObject*)sequence)->size; + while (sequence_len-- > 0) + { + iter_node = (LListNodeObject*)iter_node_obj; + + new_node = (PyObject *)dllistnode_make(self, iter_node->value); - return 1; - } + ((DLListNodeObject *)self->last)->next = new_node; + ((DLListNodeObject *)new_node)->prev = self->last; + self->last = new_node; - sequence_len = PySequence_Length(sequence); - if (sequence_len == -1) - { - PyErr_SetString(PyExc_ValueError, "Invalid sequence"); - return 0; - } - for (i = 0; i < sequence_len; ++i) - { - PyObject* item; - PyObject* new_node; + iter_node_obj = iter_node->next; + } + if( likely( self->last != Py_None) ) + ((DLListNodeObject *)self->last)->next = Py_None; - item = PySequence_GetItem(sequence, i); - if (item == NULL) + } + else + { + sequence_len = PySequence_Length(sequence); + if ( unlikely(sequence_len == -1) ) { - PyErr_SetString(PyExc_ValueError, - "Failed to get element from sequence"); + PyErr_SetString(PyExc_ValueError, "Invalid sequence"); return 0; } + if ( unlikely(sequence_len == 0) ) + return 1; - new_node = (PyObject*)dllistnode_create( - self->last, NULL, item, (PyObject*)self); + PyObject *item; + PyObject *new_node; - if (self->first == Py_None) + self->size += sequence_len; + + if ( self->size == sequence_len ) + { + + item = PySequence_GetItem(sequence, 0); + if ( unlikely(item == NULL) ) + { + PyErr_SetString(PyExc_ValueError, + "Failed to get element from sequence"); + return 0; + } + + new_node = (PyObject *)dllistnode_make(self, item); self->first = new_node; - self->last = new_node; + self->last = new_node; + ((DLListNodeObject *)new_node)->prev = Py_None; + + Py_DECREF(item); + + i = 1; + } - ++self->size; + for(; i < sequence_len; i++) + { + + item = PySequence_GetItem(sequence, i); + if ( unlikely(item == NULL) ) + { + PyErr_SetString(PyExc_ValueError, + "Failed to get element from sequence"); + return 0; + } + + new_node = (PyObject *)dllistnode_make(self, item); + + ((DLListNodeObject *)self->last)->next = new_node; + ((DLListNodeObject *)new_node)->prev = self->last; + self->last = new_node; + + + Py_DECREF(item); + } + + if(self->last != Py_None) + ((DLListNodeObject *)self->last)->next = Py_None; - Py_DECREF(item); } + _middle_check_adjust_or_recalc(self); + return 1; } @@ -517,8 +676,8 @@ static PyObject* dllist_new(PyTypeObject* type, self->first = Py_None; self->last = Py_None; - self->last_accessed_node = Py_None; - self->last_accessed_idx = -1; + self->middle = Py_None; + self->middle_idx = -1; self->size = 0; self->weakref_list = NULL; @@ -562,14 +721,8 @@ static PyObject* dllist_node_at(PyObject* self, PyObject* indexObject) index = ((DLListObject*)self)->size + index; node = dllist_get_node_internal((DLListObject*)self, index); - if (node != NULL) - { - /* update last accessed node */ - ((DLListObject*)self)->last_accessed_node = (PyObject*)node; - ((DLListObject*)self)->last_accessed_idx = index; - + if ( node != NULL ) Py_INCREF(node); - } return (PyObject*)node; } @@ -584,8 +737,18 @@ static PyObject* dllist_str(DLListObject* self) return dllist_to_string(self, PyObject_Str); } +/* NOTE - THIS FUNCTION DOES NOT WORK!! +* +* dllist([1, 5, 9]) has the SAME hash as dllist([5, 1, 9]) +* and thus it is NOT a hash function +*/ +#if 0 static long dllist_hash(DLListObject* self) { + + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + long hash = 0; PyObject* iter_node_obj = self->first; @@ -604,6 +767,7 @@ static long dllist_hash(DLListObject* self) return hash; } +#endif static PyObject* dllist_richcompare(DLListObject* self, DLListObject* other, @@ -613,7 +777,7 @@ static PyObject* dllist_richcompare(DLListObject* self, DLListNodeObject* other_node; int satisfied = 1; - if (!PyObject_TypeCheck(other, &DLListType)) + if (!PyObject_TypeCheck(other, DLListType)) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; @@ -699,21 +863,21 @@ static PyObject* dllist_appendleft(DLListObject* self, PyObject* arg) { DLListNodeObject* new_node; - if (PyObject_TypeCheck(arg, &DLListNodeType)) + if (PyObject_TypeCheck(arg, DLListNodeType)) arg = ((DLListNodeObject*)arg)->value; new_node = dllistnode_create(NULL, self->first, arg, (PyObject*)self); self->first = (PyObject*)new_node; - if (self->last == Py_None) + if (self->last == Py_None) { self->last = (PyObject*)new_node; - - if (self->last_accessed_idx >= 0) - ++self->last_accessed_idx; + } ++self->size; + _middle_check_adjust_or_recalc(self); + Py_INCREF((PyObject*)new_node); return (PyObject*)new_node; } @@ -722,7 +886,7 @@ static PyObject* dllist_appendright(DLListObject* self, PyObject* arg) { DLListNodeObject* new_node; - if (PyObject_TypeCheck(arg, &DLListNodeType)) + if (PyObject_TypeCheck(arg, DLListNodeType)) arg = ((DLListNodeObject*)arg)->value; new_node = dllistnode_create(self->last, NULL, arg, (PyObject*)self); @@ -734,6 +898,8 @@ static PyObject* dllist_appendright(DLListObject* self, PyObject* arg) ++self->size; + _middle_check_adjust_or_recalc(self); + Py_INCREF((PyObject*)new_node); return (PyObject*)new_node; } @@ -747,7 +913,7 @@ static PyObject* dllist_insert(DLListObject* self, PyObject* args) if (!PyArg_UnpackTuple(args, "insert", 1, 2, &val, &ref_node)) return NULL; - if (PyObject_TypeCheck(val, &DLListNodeType)) + if (PyObject_TypeCheck(val, DLListNodeType)) val = ((DLListNodeObject*)val)->value; if (ref_node == NULL || ref_node == Py_None) @@ -765,7 +931,7 @@ static PyObject* dllist_insert(DLListObject* self, PyObject* args) PyObject* list_ref; /* insert item before ref_node */ - if (!PyObject_TypeCheck(ref_node, &DLListNodeType)) + if (!PyObject_TypeCheck(ref_node, DLListNodeType)) { PyErr_SetString(PyExc_TypeError, "ref_node argument must be a dllistnode"); @@ -798,12 +964,17 @@ static PyObject* dllist_insert(DLListObject* self, PyObject* args) if (self->last == Py_None) self->last = (PyObject*)new_node; - /* invalidate last accessed item */ - self->last_accessed_node = Py_None; - self->last_accessed_idx = -1; } ++self->size; + if( self->size > START_MIDDLE_AFTER ) { + /* TODO: Optimize by direction? + We can after the "insert" function is changed to support integer index, + but not with element insert + */ + _middle_do_recalc(self); + } + Py_INCREF((PyObject*)new_node); return (PyObject*)new_node; @@ -811,83 +982,121 @@ static PyObject* dllist_insert(DLListObject* self, PyObject* args) static PyObject* dllist_extendleft(DLListObject* self, PyObject* sequence) { - Py_ssize_t i; + Py_ssize_t i = 0; Py_ssize_t sequence_len; - if (PyObject_TypeCheck(sequence, &DLListType)) + if (PyObject_TypeCheck(sequence, DLListType) || PyObject_TypeCheck(sequence, SLListType)) { - /* Special path for extending with a DLList. - * It's not strictly required but it will maintain - * the last accessed item. */ - PyObject* iter_node_obj = ((DLListObject*)sequence)->first; - PyObject* last_node_obj = ((DLListObject*)sequence)->last; + /* Special path for extending with a LList. Better performance. */ + PyObject* iter_node_obj = ((LListObject*)sequence)->first; + LListNodeObject *iter_node; + PyObject *new_node; - while (iter_node_obj != Py_None) - { - DLListNodeObject* iter_node = (DLListNodeObject*)iter_node_obj; - PyObject* new_node; + sequence_len = ((LListObject*)sequence)->size; + if ( unlikely(sequence_len == 0) ) + Py_RETURN_NONE; - new_node = (PyObject*)dllistnode_create( - NULL, self->first, iter_node->value, (PyObject*)self); + self->size += sequence_len; - self->first = new_node; - if (self->last == Py_None) - self->last = new_node; + if ( self->size == sequence_len ) + { + iter_node = (LListNodeObject*)iter_node_obj; - if (iter_node_obj == last_node_obj) - { - /* This is needed to terminate loop if self == sequence. */ - break; - } + new_node = (PyObject *)dllistnode_make(self, iter_node->value); + self->first = new_node; + self->last = new_node; + ((DLListNodeObject *)new_node)->next = Py_None; iter_node_obj = iter_node->next; + + sequence_len -= 1; } - self->size += ((DLListObject*)sequence)->size; + while (sequence_len-- > 0) + { + iter_node = (LListNodeObject*)iter_node_obj; - /* update index of last accessed item */ - if (self->last_accessed_idx >= 0) - self->last_accessed_idx += ((DLListObject*)sequence)->size; + new_node = (PyObject *)dllistnode_make(self, iter_node->value); - Py_RETURN_NONE; - } + ((DLListNodeObject *)self->first)->prev = new_node; + ((DLListNodeObject *)new_node)->next = self->first; + self->first = new_node; - sequence_len = PySequence_Length(sequence); - if (sequence_len == -1) - { - PyErr_SetString(PyExc_ValueError, "Invalid sequence"); - return NULL; - } - for (i = 0; i < sequence_len; ++i) + iter_node_obj = iter_node->next; + } + if ( likely(self->first != Py_None) ) + ((DLListNodeObject*)self->first)->prev = Py_None; + + + } + else { - PyObject* item; - PyObject* new_node; - item = PySequence_GetItem(sequence, i); - if (item == NULL) + sequence_len = PySequence_Length(sequence); + if ( unlikely(sequence_len == -1) ) { - PyErr_SetString(PyExc_ValueError, - "Failed to get element from sequence"); + PyErr_SetString(PyExc_ValueError, "Invalid sequence"); return NULL; } - new_node = (PyObject*)dllistnode_create( - NULL, self->first, item, (PyObject*)self); + if ( unlikely(sequence_len == 0) ) + Py_RETURN_NONE; - self->first = new_node; - if (self->last == Py_None) + PyObject *item; + PyObject *new_node; + DLListNodeObject *first; + + if ( self->size == 0 ) + { + item = PySequence_GetItem(sequence, 0); + if ( unlikely(item == NULL) ) + { + PyErr_SetString(PyExc_ValueError, + "Failed to get element from sequence"); + return NULL; + } + + new_node = (PyObject *)dllistnode_make(self, item); + self->first = new_node; self->last = new_node; + ((DLListNodeObject *)new_node)->next = Py_None; - ++self->size; + Py_DECREF(item); + i = 1; + } + + self->size += sequence_len; + + first = (DLListNodeObject *)self->first; + for (; i < sequence_len; ++i) + { + + item = PySequence_GetItem(sequence, i); + if ( unlikely(item == NULL) ) + { + PyErr_SetString(PyExc_ValueError, + "Failed to get element from sequence"); + return NULL; + } - /* update index of last accessed item */ - if (self->last_accessed_idx >= 0) - ++self->last_accessed_idx; + new_node = (PyObject *)dllistnode_make(self, item); - Py_DECREF(item); + first->prev = new_node; + ((DLListNodeObject *)new_node)->next = (PyObject *)first; + self->first = new_node; + first = (DLListNodeObject *)new_node; + + + Py_DECREF(item); + } + if ( likely( (PyObject *)first != Py_None) ) + first->prev = Py_None; } + + _middle_check_adjust_or_recalc(self); + Py_RETURN_NONE; } @@ -911,12 +1120,10 @@ static PyObject* dllist_clear(DLListObject* self) dllistnode_delete(iter_node); } - /* invalidate last accessed item */ - self->last_accessed_node = Py_None; - self->last_accessed_idx = -1; - self->first = Py_None; self->last = Py_None; + self->middle = Py_None; + self->middle_idx = -1; self->size = 0; Py_RETURN_NONE; @@ -939,20 +1146,13 @@ static PyObject* dllist_popleft(DLListObject* self) if (self->last == (PyObject*)del_node) self->last = Py_None; - if (self->last_accessed_node != (PyObject*)del_node) - { - if (self->last_accessed_idx >= 0) - --self->last_accessed_idx; - } - else + --self->size; + if ( ! _middle_check_on_shrink(self) ) { - /* invalidate last accessed item */ - self->last_accessed_node = Py_None; - self->last_accessed_idx = -1; + self->middle_idx -= 1; + _middle_postappend_adjust(self, 0); } - --self->size; - Py_INCREF(del_node->value); value = del_node->value; @@ -978,20 +1178,143 @@ static PyObject* dllist_popright(DLListObject* self) if (self->first == (PyObject*)del_node) self->first = Py_None; - if (self->last_accessed_node == (PyObject*)del_node) + --self->size; + if( ! _middle_check_on_shrink(self) ) + { + _middle_postappend_adjust(self, 0); + } + + + Py_INCREF(del_node->value); + value = del_node->value; + + dllistnode_delete(del_node); + + return value; +} + + +static PyObject* dllist_pop(DLListObject* self, PyObject *arg) +{ + DLListNodeObject *del_node; + + PyObject *value; + PyObject *indexObject = NULL; + + Py_ssize_t index; + Py_ssize_t i; + + if (!PyArg_UnpackTuple(arg, "pop", 0, 1, &indexObject)) { - /* invalidate last accessed item */ - self->last_accessed_node = Py_None; - self->last_accessed_idx = -1; + return NULL; } + /* If we did not get passed the "index" arg, just return popright */ + if ( indexObject == NULL ) + { + return dllist_popright( self ); + } + + if (!Py23Int_Check(indexObject)) + { + PyErr_SetString(PyExc_TypeError, "Index must be an integer"); + return NULL; + } + + index = Py23Int_AsSsize_t(indexObject); + + + /* Negative index */ + if (index < 0) + index = ((DLListObject*)self)->size + index; + + /* If index is 0, popleft */ + if ( index == 0 ) + { + return dllist_popleft( self ); + } + else if ( index + 1 == ((DLListObject*)self)->size ) + { + return dllist_popright(self); + } + + if ( (PyObject*)self->first == Py_None) + { + PyErr_SetString(PyExc_ValueError, "List is empty"); + return NULL; + } + + /* Either a negative greater than index size, or a positive greater than size */ + if ( index < 0 || index >= ((DLListObject*)self)->size ) + { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + return NULL; + } + + if ( index == self->middle_idx ) + { + del_node = (DLListNodeObject*)self->middle; + } + else if ( index < ((DLListObject*)self)->size / 2 ) + { + if ( self->middle_idx != -1 && self->middle_idx - index < index ) + { + del_node = (DLListNodeObject*) ((DLListNodeObject*) self->middle)->prev; + for(i=1; i < self->middle_idx - index; i++) { + del_node = (DLListNodeObject*)del_node->prev; + } + } + else + { + /* Start at first node, and walk to the one we will pop */ + del_node = (DLListNodeObject*) ((DLListNodeObject*) self->first)->next; + for(i=1; i < index; i++) { + del_node = (DLListNodeObject*)del_node->next; + } + } + _middle_popped_left_of_middle(self); + + } + else + { + if ( self->middle_idx != -1 && index - self->middle_idx < self->size - index ) + { + del_node = (DLListNodeObject*) ((DLListNodeObject*) self->middle)->next; + for(i=1; i < index - self->middle_idx; i++) { + del_node = (DLListNodeObject*)del_node->next; + } + } + else + { + /* Start at last node, and walk back to the one we will pop */ + del_node = (DLListNodeObject*) ((DLListNodeObject*) self->last)->prev; + for(i=((DLListObject*)self)->size - 2; i > index; i--) { + del_node = (DLListNodeObject*)del_node->prev; + } + } + } + + --self->size; + Py_INCREF(del_node->value); value = del_node->value; dllistnode_delete(del_node); + if ( ! _middle_check_on_shrink(self) ) + { + if ( index != self->middle_idx ) + { + _middle_postappend_adjust(self, 0); + } + else + { + _middle_check_recalc(self); + } + } + return value; } @@ -1001,7 +1324,7 @@ static PyObject* dllist_remove(DLListObject* self, PyObject* arg) PyObject* list_ref; PyObject* value; - if (!PyObject_TypeCheck(arg, &DLListNodeType)) + if (!PyObject_TypeCheck(arg, DLListNodeType)) { PyErr_SetString(PyExc_TypeError, "Argument must be a dllistnode"); return NULL; @@ -1034,14 +1357,13 @@ static PyObject* dllist_remove(DLListObject* self, PyObject* arg) self->first = del_node->next; if (self->last == arg) self->last = del_node->prev; - if (self->last_accessed_node == arg) - self->last_accessed_node = del_node->prev; - - /* invalidate last accessed item */ - self->last_accessed_node = Py_None; - self->last_accessed_idx = -1; --self->size; + if ( ! _middle_check_on_shrink(self) ) + { + /* TODO: Optimize direction */ + _middle_do_recalc(self); + } Py_INCREF(del_node->value); value = del_node->value; @@ -1097,11 +1419,7 @@ static PyObject* dllist_rotate(DLListObject* self, PyObject* nObject) self->first = (PyObject*)new_first; self->last = (PyObject*)new_last; - if (self->last_accessed_idx >= 0) - { - self->last_accessed_idx = - (self->last_accessed_idx + self->size - split_idx) % self->size; - } + _middle_check_recalc(self); Py_RETURN_NONE; } @@ -1127,7 +1445,7 @@ static PyObject* dllist_iter(PyObject* self) return NULL; } - result = PyObject_CallObject((PyObject*)&DLListIteratorType, args); + result = PyObject_CallObject((PyObject*)DLListIteratorType, args); Py_DECREF(args); @@ -1145,7 +1463,7 @@ static PyObject* dllist_concat(PyObject* self, PyObject* other) DLListObject* new_list; new_list = (DLListObject*)PyObject_CallObject( - (PyObject*)&DLListType, NULL); + (PyObject*)DLListType, NULL); if (!dllist_extend_internal(new_list, self) || !dllist_extend_internal(new_list, other)) @@ -1172,7 +1490,7 @@ static PyObject* dllist_repeat(PyObject* self, Py_ssize_t count) Py_ssize_t i; new_list = (DLListObject*)PyObject_CallObject( - (PyObject*)&DLListType, NULL); + (PyObject*)DLListType, NULL); for (i = 0; i < count; ++i) { @@ -1197,10 +1515,6 @@ static PyObject* dllist_get_item(PyObject* self, Py_ssize_t index) Py_XINCREF(value); - /* update last accessed node */ - ((DLListObject*)self)->last_accessed_node = (PyObject*)node; - ((DLListObject*)self)->last_accessed_idx = index; - return value; } @@ -1222,19 +1536,10 @@ static int dllist_set_item(PyObject* self, Py_ssize_t index, PyObject* val) * del list[index] */ if (val == NULL) { - PyObject* prev = node->prev; PyObject* result; result = dllist_remove(list, (PyObject*)node); - if (prev != Py_None && index > 0) - { - /* Last accessed item was invalidated by dllist_remove. - * We restore it here as the preceding node. */ - list->last_accessed_node = prev; - list->last_accessed_idx = index - 1; - } - Py_XDECREF(result); return (result != NULL) ? 0 : -1; @@ -1242,7 +1547,7 @@ static int dllist_set_item(PyObject* self, Py_ssize_t index, PyObject* val) /* The rest of this function handles normal assignment: * list[index] = item */ - if (PyObject_TypeCheck(val, &DLListNodeType)) + if (PyObject_TypeCheck(val, DLListNodeType)) val = ((DLListNodeObject*)val)->value; oldval = node->value; @@ -1251,13 +1556,334 @@ static int dllist_set_item(PyObject* self, Py_ssize_t index, PyObject* val) node->value = val; Py_DECREF(oldval); - /* update last accessed node */ - list->last_accessed_node = (PyObject*)node; - list->last_accessed_idx = index; + return 0; +} + + +static int dllist_contains(PyObject *self, PyObject *value) +{ + DLListNodeObject *node; + + node = (DLListNodeObject *) ((DLListObject*)self)->first; + + while ( (PyObject *)node != Py_None ) + { + if( node->value == value ) + return 1; + + node = (DLListNodeObject *)node->next; + } return 0; } + + +static PyObject* dllist_index(DLListObject *self, PyObject *value) +{ + + DLListNodeObject *node; + Py_ssize_t idx; + + node = (DLListNodeObject *) self->first; + idx = 0; + + while ( (PyObject*)node != Py_None ) + { + if( node->value == value ) + return PyLong_FromSsize_t(idx); + + node = (DLListNodeObject *)node->next; + idx += 1; + } + PyErr_Format(PyExc_ValueError, "No such value in list"); + return NULL; +} + +static PyObject* dllist_rindex(DLListObject *self, PyObject *value) +{ + + DLListNodeObject *node; + Py_ssize_t idx; + + node = (DLListNodeObject *) self->last; + idx = self->size - 1; + + while ( (PyObject*)node != Py_None ) + { + if( node->value == value ) + return PyLong_FromSsize_t(idx); + + node = (DLListNodeObject *)node->prev; + idx -= 1; + } + PyErr_Format(PyExc_ValueError, "No such value in list"); + return NULL; +} + +/* + * dllist_slice - Slice function assuming normalized indexes. + * + * For potentially non-normalized, use dllist_simpleslice + * or call _normalize_indexes + * + * self - DLList to slice from + * start_idx - Start of slicing (normalized) + * end_idx - End of slicing (normalized) + * step - Slice step + * sliceLength - Length of resulting slice. Pass -1 to calculate. + */ +static PyObject *dllist_slice(DLListObject *self, Py_ssize_t start_idx, Py_ssize_t end_idx, Py_ssize_t step, Py_ssize_t sliceLength) +{ + DLListObject *ret; + DLListNodeObject *cur; + DLListNodeObject *new_node; + Py_ssize_t i; + Py_ssize_t remaining; + int direction = 0; + int pre_walk_direction; + + Py_ssize_t stepI; + + Py_ssize_t diff_start_left; + Py_ssize_t diff_end_right; + + ret = (DLListObject *)dllist_new(DLListType, Py_None, Py_None); + + if ( sliceLength == -1 ) + { + sliceLength = end_idx - start_idx; /* Reusing end_idx as max */ + if(step > 1 ) + { + sliceLength = sliceLength / step + ( end_idx % step ? 1 : 0 ); + } + } + + + if ( step > 1 ) + { + Py_ssize_t tmp_slice_resize; + tmp_slice_resize = start_idx + (step * sliceLength); + if ( tmp_slice_resize < end_idx ) + end_idx = tmp_slice_resize; + } + + + + if ( sliceLength == 0 ) + return (PyObject *)ret; + + + diff_start_left = start_idx; + diff_end_right = self->size - 1 - end_idx; + + remaining = py_ssize_min(diff_start_left, diff_end_right); + if (self->middle_idx != -1 ) + { + Py_ssize_t diff_mid_left; + Py_ssize_t diff_mid_right; + + diff_mid_left = py_ssize_t_abs( self->middle_idx - start_idx ); + diff_mid_right = py_ssize_t_abs ( end_idx - self->middle_idx ); + + remaining = py_ssize_min(remaining, diff_mid_left); + remaining = py_ssize_min(remaining, diff_mid_right); + + if ( remaining == diff_mid_left ) + { + direction = 1; + if ( start_idx < self->middle_idx ) + { + pre_walk_direction = -1; + } + else + { + pre_walk_direction = 1; + } + + cur = (DLListNodeObject *)self->middle; + } + else if( remaining == diff_mid_right ) + { + direction = -1; + if ( end_idx < self->middle_idx ) + { + pre_walk_direction = -1; + remaining += calc_end_difference_step(start_idx, end_idx, step); + } + else if ( end_idx > self->middle_idx ) + { + pre_walk_direction = 1; + remaining -= calc_end_difference_step(start_idx, end_idx, step); + if ( remaining < 0 ) + { + /* If here, we had to change direction because of step-end */ + pre_walk_direction = -1; + remaining *= -1; + } + } + else + { + pre_walk_direction = -1; + remaining += calc_end_difference_step(start_idx, end_idx, step); + + } + + cur = (DLListNodeObject *)self->middle; + } + } + + if ( direction == 0 ) + { + if ( remaining == diff_start_left ) + { + direction = 1; + pre_walk_direction = 1; + cur = (DLListNodeObject *)self->first; + } + else /* diff_end_right */ + { + direction = -1; + pre_walk_direction = -1; + remaining += calc_end_difference_step(start_idx, end_idx, step); + cur = (DLListNodeObject *)self->last; + } + } + + + if ( pre_walk_direction == 1 ) + { + for(i=0; i < remaining; i++) + cur = (DLListNodeObject*)cur->next; + } + else + { + for(i=remaining; i > 0; i--) + cur = (DLListNodeObject*)cur->prev; + } + + new_node = dllistnode_make(self, cur->value); + new_node->prev = new_node->next = Py_None; + + ret->first = ret->last = (PyObject *)new_node; + ret->size = 1; + + + if ( direction == 1 ) + { + DLListNodeObject *prev; + prev = new_node; + + + while ( ret->size < sliceLength ) + { + stepI = step; + while(stepI--) + cur = (DLListNodeObject*)cur->next; + + new_node = dllistnode_make(self, cur->value); + + new_node->prev = (PyObject *)prev; + prev->next = (PyObject *)new_node; + prev = new_node; + + ret->last = (PyObject *)new_node; + + ret->size += 1; + + + } + ((DLListNodeObject*)ret->last)->next = Py_None; + + } + else + { + DLListNodeObject *next; + + next = new_node; + + + while ( ret->size < sliceLength ) + { + stepI = step; + while(stepI--) + cur = (DLListNodeObject*)cur->prev; + + new_node = dllistnode_make(self, cur->value); + + new_node->next = (PyObject *)next; + next->prev = (PyObject *)new_node; + next = new_node; + + ret->first = (PyObject *)new_node; + + ret->size += 1; + + } + ((DLListNodeObject*)ret->last)->next = Py_None; + ((DLListNodeObject*)ret->first)->prev = Py_None; + + } + + _middle_check_recalc(ret); + + return (PyObject*) ret; + +} + +/* + * dllist_simpleslice - Simple (PySequence) slices. Can take non-normalized indexes (like negatives) + */ +static PyObject *dllist_simpleslice(DLListObject *self, Py_ssize_t idx_start, Py_ssize_t idx_end) +{ + debugmsg("Calling simpleslice: %p %ld %ld\n", self, idx_start, idx_end); + if( !_normalize_indexes(self->size, &idx_start, &idx_end) ) + { + DLListObject *ret = (DLListObject *)dllist_new(DLListType, NULL, NULL); + return (PyObject *)ret; + } + + return dllist_slice(self, idx_start, idx_end, 1, -1); +} + +static PyObject *dllist_subscript(DLListObject *self, PyObject *item) +{ + + if ( PyIndex_Check(item) ) + { + Py_ssize_t i; + + i = PyNumber_AsSsize_t(item, PyExc_IndexError); + + if ( i == -1 && PyErr_Occurred() ) + return NULL; + + if ( i < 0 ) + i += self->size; + + return dllist_get_item((PyObject *)self, i); + } + + if ( PySlice_Check(item) ) + { + Py_ssize_t start, stop, step, sliceLength; + + if ( PySlice_GetIndicesEx( (GET_INDICES_TYPE *)item, self->size, &start, &stop, &step, &sliceLength ) ) + return NULL; /* Error */ + + return dllist_slice(self, start, stop, step, sliceLength); + } + else + { + + PyErr_Format(PyExc_TypeError, "Indices must be integers, not %s", item->ob_type->tp_name); + return NULL; + } + +} + + + static PyMethodDef DLListMethods[] = { { "appendleft", (PyCFunction)dllist_appendleft, METH_O, @@ -1276,12 +1902,16 @@ static PyMethodDef DLListMethods[] = "Append elements from iterable at the right side of the list" }, { "insert", (PyCFunction)dllist_insert, METH_VARARGS, "Inserts element before node" }, + { "index", (PyCFunction)dllist_index, METH_O, + "Returns the first index of a value" }, + { "rindex", (PyCFunction)dllist_rindex, METH_O, + "Returns the last index of a value" }, { "nodeat", (PyCFunction)dllist_node_at, METH_O, "Return node at index" }, { "popleft", (PyCFunction)dllist_popleft, METH_NOARGS, "Remove first element from the list and return it" }, - { "pop", (PyCFunction)dllist_popright, METH_NOARGS, - "Remove last element from the list and return it" }, + { "pop", (PyCFunction)dllist_pop, METH_VARARGS, + "Remove an element by index from the list and return it, or last item if no index provided" }, { "popright", (PyCFunction)dllist_popright, METH_NOARGS, "Remove last element from the list and return it" }, { "remove", (PyCFunction)dllist_remove, METH_O, @@ -1297,26 +1927,41 @@ static PyMemberDef DLListMembers[] = "First node" }, { "last", T_OBJECT_EX, offsetof(DLListObject, last), READONLY, "Next node" }, + { "middle", T_OBJECT_EX, offsetof(DLListObject, middle), READONLY, + "Middle node" }, +/* { "middle_idx", T_INT, offsetof(DLListObject, middle_idx), READONLY, + "Middle node index" }, +*/ { "size", T_INT, offsetof(DLListObject, size), READONLY, "Number of elements in the list" }, { NULL }, /* sentinel */ }; -static PySequenceMethods DLListSequenceMethods[] = +static PySequenceMethods DLListSequenceMethods[] = { { dllist_len, /* sq_length */ dllist_concat, /* sq_concat */ dllist_repeat, /* sq_repeat */ dllist_get_item, /* sq_item */ - 0, /* sq_slice */ + (ssizessizeargfunc)dllist_simpleslice, /* sq_slice */ dllist_set_item, /* sq_ass_item */ 0, /* sq_ass_slice */ - 0, /* sq_contains */ + dllist_contains, /* sq_contains */ dllist_inplace_concat, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ +} +} +; + +static PyMappingMethods DLListMappingMethods[] = { +{ + dllist_len, /* mp_length */ + (binaryfunc)dllist_subscript, /* mp_subscript */ + 0, /* mp_ass_subscript */ +} }; -static PyTypeObject DLListType = +PyTypeObject DLListType[] = { { PyVarObject_HEAD_INIT(NULL, 0) "llist.dllist", /* tp_name */ @@ -1330,8 +1975,8 @@ static PyTypeObject DLListType = (reprfunc)dllist_repr, /* tp_repr */ 0, /* tp_as_number */ DLListSequenceMethods, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - (hashfunc)dllist_hash, /* tp_hash */ + DLListMappingMethods, /* tp_as_mapping */ + 0, /* tp_hash */ 0, /* tp_call */ (reprfunc)dllist_str, /* tp_str */ 0, /* tp_getattro */ @@ -1358,6 +2003,7 @@ static PyTypeObject DLListType = (initproc)dllist_init, /* tp_init */ 0, /* tp_alloc */ dllist_new, /* tp_new */ +} }; @@ -1388,7 +2034,7 @@ static PyObject* dllistiterator_new(PyTypeObject* type, if (!PyArg_UnpackTuple(args, "__new__", 1, 1, &owner_list)) return NULL; - if (!PyObject_TypeCheck(owner_list, &DLListType)) + if (!PyObject_TypeCheck(owner_list, DLListType)) { PyErr_SetString(PyExc_TypeError, "dllist argument expected"); return NULL; @@ -1432,7 +2078,7 @@ static PyObject* dllistiterator_iternext(PyObject* self) return value; } -static PyTypeObject DLListIteratorType = +PyTypeObject DLListIteratorType[] = { { PyVarObject_HEAD_INIT(NULL, 0) "llist.dllistiterator", /* tp_name */ @@ -1472,25 +2118,26 @@ static PyTypeObject DLListIteratorType = 0, /* tp_init */ 0, /* tp_alloc */ dllistiterator_new, /* tp_new */ +} }; int dllist_init_type(void) { return - ((PyType_Ready(&DLListType) == 0) && - (PyType_Ready(&DLListNodeType) == 0) && - (PyType_Ready(&DLListIteratorType) == 0)) + ((PyType_Ready(DLListType) == 0) && + (PyType_Ready(DLListNodeType) == 0) && + (PyType_Ready(DLListIteratorType) == 0)) ? 1 : 0; } void dllist_register(PyObject* module) { - Py_INCREF(&DLListType); - Py_INCREF(&DLListNodeType); - Py_INCREF(&DLListIteratorType); + Py_INCREF(DLListType); + Py_INCREF(DLListNodeType); + Py_INCREF(DLListIteratorType); - PyModule_AddObject(module, "dllist", (PyObject*)&DLListType); - PyModule_AddObject(module, "dllistnode", (PyObject*)&DLListNodeType); - PyModule_AddObject(module, "dllistiterator", (PyObject*)&DLListIteratorType); + PyModule_AddObject(module, "dllist", (PyObject*)DLListType); + PyModule_AddObject(module, "dllistnode", (PyObject*)DLListNodeType); + PyModule_AddObject(module, "dllistiterator", (PyObject*)DLListIteratorType); } diff --git a/src/dllist.h b/src/dllist.h index d67bca2..5cdd256 100644 --- a/src/dllist.h +++ b/src/dllist.h @@ -1,4 +1,5 @@ /* Copyright (c) 2011-2013 Adam Jakubek, Rafał Gałczyński + * Copyright (c) 2017 Timothy Savannah * Released under the MIT license (see attached LICENSE file). */ diff --git a/src/dllist_types.h b/src/dllist_types.h new file mode 100644 index 0000000..14dd36d --- /dev/null +++ b/src/dllist_types.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2017 Timothy Savannah + * Released under the MIT license (see attached LICENSE file). + */ + +#ifndef DLLIST_TYPES_H +#define DLLIST_TYPES_H + +extern PyTypeObject DLListType[]; +extern PyTypeObject DLListNodeType[]; +extern PyTypeObject DLListIteratorType[]; + + +/* DLListNode */ + +typedef struct +{ + PyObject_HEAD + PyObject *list_weakref; + PyObject *value; + PyObject *next; + PyObject *prev; +} DLListNodeObject; + +typedef struct +{ + PyObject_HEAD + PyObject *first; + PyObject *last; + Py_ssize_t size; + PyObject *weakref_list; + PyObject *middle; + Py_ssize_t middle_idx; +} DLListObject; + + +#endif /* DLLIST_TYPES_H */ diff --git a/src/llist.c b/src/llist.c deleted file mode 100644 index 0b06f9c..0000000 --- a/src/llist.c +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright (c) 2011-2013 Adam Jakubek, Rafał Gałczyński - * Released under the MIT license (see attached LICENSE file). - */ - -#include - -#include "sllist.h" -#include "dllist.h" - -static PyMethodDef llist_methods[] = -{ - { NULL } /* sentinel */ -}; - -#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ -#define PyMODINIT_FUNC void -#endif - -#if PY_MAJOR_VERSION >= 3 - -static struct PyModuleDef llist_moduledef = { - PyModuleDef_HEAD_INIT, - "llist", /* m_name */ - "Singly and doubly linked lists.", /* m_doc */ - -1, /* m_size */ - llist_methods, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ -}; - -PyMODINIT_FUNC -PyInit_llist(void) -{ - PyObject* m; - - if (!sllist_init_type()) - return NULL; - if (!dllist_init_type()) - return NULL; - - m = PyModule_Create(&llist_moduledef); - - sllist_register(m); - dllist_register(m); - - return m; -} - -#else - -PyMODINIT_FUNC -initllist(void) -{ - PyObject* m; - - if (!sllist_init_type()) - return; - if (!dllist_init_type()) - return; - - m = Py_InitModule3("llist", llist_methods, - "Singly and doubly linked lists."); - - sllist_register(m); - dllist_register(m); -} - -#endif /* PY_MAJOR_VERSION >= 3 */ diff --git a/src/llist.h b/src/llist.h new file mode 100644 index 0000000..96d8cd9 --- /dev/null +++ b/src/llist.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2017 Timothy Savannah, + * all rights reserved under terms of MIT license (see LICENSE) + */ + +#ifndef LLIST_H +#define LLIST_H + +#include + + +#define LLIST_VERSION_MAJOR 1 +#define LLIST_VERSION_MINOR 0 +#define LLIST_VERSION_PATCH 0 +#define LLIST_VERSION_STR ("1.0.0") + +extern PyObject *mod_version; +extern PyObject *mod_version_tuple; + + +#ifdef __GNUC__ + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +#else + +#define likely(x) (x) +#define unlikely(x) (x) + +#endif + +/* #define DO_DEBUG */ + + +#ifdef DO_DEBUG +static void debugmsg(char *format, ...) +{ + va_list args; + FILE *f; + + f = fopen("debug.txt", "a"); + + va_start(args, format); + vfprintf(f, format, args); + va_end(args); + + fclose(f); +} +#else + +#define debugmsg(...) +/*static inline void debugmsg(char *format, ...) +{ + +} +*/ +#endif + +#if PY_MAJOR_VERSION >= 3 + +#define GET_INDICES_TYPE PyObject + +#else + +#define GET_INDICES_TYPE PySliceObject + +#endif + +static inline Py_ssize_t py_ssize_t_abs(Py_ssize_t x) +{ + return (x >= 0) ? x : -x; +} + +static inline Py_ssize_t py_ssize_min(Py_ssize_t a, Py_ssize_t b) +{ + if ( a < b ) + return a; + return b; +} + + +/* Calc difference between given end and actual end with step */ +#define calc_end_difference_step(_start, _end, _step) (((_end - _start - 1) % _step) + 1) + + +static inline int _normalize_indexes(Py_ssize_t size, Py_ssize_t *idx_start, Py_ssize_t *idx_end) +{ + + if ( unlikely(*idx_start < 0 )) + { + *idx_start = size - *idx_start; + if ( unlikely(*idx_start < 0 )) + return 0; + } + if ( unlikely(*idx_end < 0 )) + { + *idx_end = size - *idx_end; + if ( unlikely(*idx_end < 0 )) + return 0; + } + + if ( unlikely(*idx_end >= size )) + *idx_end = size; + + if ( unlikely(*idx_start >= size || *idx_start >= *idx_end)) + return 0; + + if ( unlikely(*idx_start >= *idx_end )) + return 0; + + + return 1; +} + + + +#endif diff --git a/src/llist_types.h b/src/llist_types.h new file mode 100644 index 0000000..05a5315 --- /dev/null +++ b/src/llist_types.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2017 Timothy Savannah + * Released under the MIT license (see attached LICENSE file). + */ + +#ifndef LLIST_TYPES_H +#define LLIST_TYPES_H + +/* Common start to both dllist and sllist */ +typedef struct +{ + PyObject_HEAD + PyObject *list_weakref; + PyObject *value; + PyObject *next; +} LListNodeObject; + +typedef struct +{ + PyObject_HEAD + PyObject *first; + PyObject *last; + Py_ssize_t size; +} LListObject; + + +static inline PyObject *llistnode_make(PyTypeObject *llist_type, PyObject *llist, PyObject *value) +{ + LListNodeObject *ret; + + ret = (LListNodeObject *)llist_type->tp_alloc(llist_type, 0); + if ( ret == NULL ) + return NULL; + + /* A single reference to Py_None is held for the whole + * lifetime of a node. */ + Py_INCREF(Py_None); + + ret->value = value; + + Py_INCREF(ret->value); + + ret->list_weakref = PyWeakref_NewRef((PyObject *)llist, NULL); + Py_INCREF(ret->list_weakref); + + return (PyObject *)ret; +} + +#endif /* LLIST_TYPES_H */ diff --git a/src/sllist.c b/src/sllist.c index ac28304..90b52fe 100644 --- a/src/sllist.c +++ b/src/sllist.c @@ -1,10 +1,15 @@ /* Copyright (c) 2011-2013 Adam Jakubek, Rafał Gałczyński + * Copyright (c) 2017 Timothy Savannah * Released under the MIT license (see attached LICENSE file). */ #include #include #include "py23macros.h" +#include "llist.h" +#include "llist_types.h" +#include "sllist_types.h" +#include "dllist_types.h" #ifndef PyVarObject_HEAD_INIT #define PyVarObject_HEAD_INIT(type, size) \ @@ -12,22 +17,6 @@ #endif -static PyTypeObject SLListType; -static PyTypeObject SLListNodeType; -static PyTypeObject SLListIteratorType; - - -/* SLListNode */ - -typedef struct -{ - PyObject_HEAD - PyObject* value; - PyObject* next; - PyObject* list_weakref; -} SLListNodeObject; - - static SLListNodeObject* sllistnode_create(PyObject* next, PyObject* value, PyObject* owner_list) @@ -59,7 +48,7 @@ static SLListNodeObject* sllistnode_create(PyObject* next, } node = (SLListNodeObject*)PyObject_CallObject( - (PyObject*)&SLListNodeType, args); + (PyObject*)SLListNodeType, args); Py_XDECREF(args); @@ -131,6 +120,15 @@ static PyObject* sllistnode_new(PyTypeObject* type, } +/* + * sllistnode_make - A slightly cheaper version that doesn't set next + */ +static inline SLListNodeObject *sllistnode_make(SLListObject *sllist, PyObject *value) +{ + return (SLListNodeObject *)llistnode_make(SLListNodeType, (PyObject *)sllist, value); +} + + static PyObject* sllistnode_repr(SLListNodeObject* self) { @@ -211,7 +209,7 @@ static PyMemberDef SLListNodeMembers[] = }; -static PyTypeObject SLListNodeType = +PyTypeObject SLListNodeType[] = { { PyVarObject_HEAD_INIT(NULL, 0) "llist.sllistnode", /* tp_name */ @@ -251,6 +249,7 @@ static PyTypeObject SLListNodeType = (initproc)sllistnode_init, /* tp_init */ 0, /* tp_alloc */ sllistnode_new, /* tp_new */ +} }; @@ -259,15 +258,6 @@ static PyTypeObject SLListNodeType = /* SLLIST */ /* ****************************************************************************** */ -typedef struct -{ - PyObject_HEAD - PyObject* first; - PyObject* last; - Py_ssize_t size; - PyObject* weakref_list; -} SLListObject; - static void sllist_dealloc(SLListObject* self) { @@ -317,80 +307,111 @@ static int sllist_extend_internal(SLListObject* self, PyObject* sequence) Py_ssize_t i; Py_ssize_t sequence_len; - if (PyObject_TypeCheck(sequence, &SLListType)) + if (PyObject_TypeCheck(sequence, SLListType) || PyObject_TypeCheck(sequence, DLListType)) { - /* Special path for extending with a SLList. - * It's not strictly required but it will maintain - * the last accessed item. */ - PyObject* iter_node_obj = ((SLListObject *)sequence)->first; - PyObject* last_node_obj = self->last; + /* Special path for extending with a LList (double or single) + * It's not strictly required but greatly improves performance + */ + PyObject* iter_node_obj = ((LListObject *)sequence)->first; + + sequence_len = ((LListObject *)sequence)->size; + if ( unlikely(sequence_len == 0) ) + return 1; + + LListNodeObject *iter_node; + PyObject *new_node; - while (iter_node_obj != Py_None) + self->size += sequence_len; + if(self->size == sequence_len) { - SLListNodeObject* iter_node = (SLListNodeObject*)iter_node_obj; - PyObject* new_node; + iter_node = (LListNodeObject*)iter_node_obj; + + new_node = (PyObject *)sllistnode_make(self, iter_node->value); + self->first = new_node; + self->last = new_node; - new_node = (PyObject*)sllistnode_create( - Py_None, iter_node->value, (PyObject*)self); + iter_node_obj = iter_node->next; + sequence_len -= 1; + } + while (sequence_len-- > 0) + { + iter_node = (LListNodeObject*)iter_node_obj; - if (self->last != Py_None) - ((SLListNodeObject*)self->last)->next = new_node; + new_node = (PyObject *)sllistnode_make(self, iter_node->value); - if (self->first == Py_None) - self->first = new_node; + ((SLListNodeObject *)self->last)->next = new_node; self->last = new_node; - if (iter_node_obj == last_node_obj) - { - /* This is needed to terminate loop if self == sequence. */ - break; - } iter_node_obj = iter_node->next; } + if( likely(self->last != Py_None) ) + ((SLListNodeObject *)self->last)->next = Py_None; - self->size += ((SLListObject*)sequence)->size; - return 1; } - - sequence_len = PySequence_Length(sequence); - if (sequence_len == -1) - { - PyErr_SetString(PyExc_ValueError, "Invalid sequence"); - return 0; - } - - for (i = 0; i < sequence_len; ++i) + else { - PyObject* item; - PyObject* new_node; - - item = PySequence_GetItem(sequence, i); - if (item == NULL) + sequence_len = PySequence_Length(sequence); + if ( unlikely(sequence_len == -1) ) { - PyErr_SetString(PyExc_ValueError, - "Failed to get element from sequence"); + PyErr_SetString(PyExc_ValueError, "Invalid sequence"); return 0; } + if ( unlikely(sequence_len == 0) ) + return 1; + + PyObject *item; + PyObject *new_node; + + self->size += sequence_len; + i = 0; + + if ( self->size == sequence_len ) + { - new_node = (PyObject*)sllistnode_create(Py_None, - item, - (PyObject*)self); + item = PySequence_GetItem(sequence, 0); + if ( unlikely(item == NULL) ) + { + PyErr_SetString(PyExc_ValueError, + "Failed to get element from sequence"); + return 0; + } + new_node = (PyObject *)sllistnode_make(self, item); - if(self->first == Py_None) self->first = new_node; - else - ((SLListNodeObject*)self->last)->next = new_node; + self->last = new_node; - self->last = new_node; + Py_DECREF(item); + i += 1; + } - ++self->size; + for (; i < sequence_len; ++i) + { + PyObject* item; + PyObject* new_node; - Py_DECREF(item); - } + item = PySequence_GetItem(sequence, i); + if ( unlikely(item == NULL) ) + { + PyErr_SetString(PyExc_ValueError, + "Failed to get element from sequence"); + return 0; + } + + new_node = (PyObject *)sllistnode_make(self, item); + + ((SLListNodeObject *)self->last)->next = new_node; + self->last = new_node; + + Py_DECREF(item); + } + if( likely(self->last != Py_None) ) + ((SLListNodeObject *)self->last)->next = Py_None; + + } return 1; } @@ -425,7 +446,7 @@ static PyObject* sllist_richcompare(SLListObject* self, SLListNodeObject* other_node; int satisfied = 1; - if (!PyObject_TypeCheck(other, &SLListType)) + if (!PyObject_TypeCheck(other, SLListType)) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; @@ -516,7 +537,7 @@ static SLListNodeObject* sllist_get_prev(SLListObject* self, SLListNodeObject* node = (SLListNodeObject*)self->first; - if (!PyObject_TypeCheck(next, &SLListNodeType)) + if (!PyObject_TypeCheck(next, SLListNodeType)) { PyErr_SetString(PyExc_TypeError, "Argument is not an sllistnode"); return NULL; @@ -553,7 +574,7 @@ static PyObject* sllist_appendleft(SLListObject* self, PyObject* arg) { SLListNodeObject* new_node; - if (PyObject_TypeCheck(arg, &SLListNodeType)) + if (PyObject_TypeCheck(arg, SLListNodeType)) arg = ((SLListNodeObject*)arg)->value; new_node = sllistnode_create(self->first, @@ -576,7 +597,7 @@ static PyObject* sllist_appendright(SLListObject* self, PyObject* arg) { SLListNodeObject* new_node; - if (PyObject_TypeCheck(arg, &SLListNodeType)) + if (PyObject_TypeCheck(arg, SLListNodeType)) arg = ((SLListNodeObject*)arg)->value; new_node = sllistnode_create(Py_None, @@ -609,13 +630,13 @@ static PyObject* sllist_insertafter(SLListObject* self, PyObject* arg) if (!PyArg_UnpackTuple(arg, "insertafter", 2, 2, &value, &before)) return NULL; - if (!PyObject_TypeCheck(before, &SLListNodeType)) + if (!PyObject_TypeCheck(before, SLListNodeType)) { PyErr_SetString(PyExc_TypeError, "Argument is not an sllistnode"); return NULL; } - if (PyObject_TypeCheck(value, &SLListNodeType)) + if (PyObject_TypeCheck(value, SLListNodeType)) value = ((SLListNodeObject*)value)->value; if (((SLListNodeObject*)before)->list_weakref == Py_None) @@ -663,12 +684,12 @@ static PyObject* sllist_insertbefore(SLListObject* self, PyObject* arg) if (!PyArg_UnpackTuple(arg, "insertbefore", 2, 2, &value, &after)) return NULL; - if (!PyObject_TypeCheck(after, &SLListNodeType)) + if (!PyObject_TypeCheck(after, SLListNodeType)) { PyErr_SetString(PyExc_TypeError, "Argument is not an sllistnode"); return NULL; } - if (PyObject_TypeCheck(value, &SLListNodeType)) + if (PyObject_TypeCheck(value, SLListNodeType)) value = ((SLListNodeObject*)value)->value; if (after == Py_None) @@ -717,73 +738,103 @@ static PyObject* sllist_extendleft(SLListObject* self, PyObject* sequence) Py_ssize_t i; Py_ssize_t sequence_len; - if (PyObject_TypeCheck(sequence, &SLListType)) + if (PyObject_TypeCheck(sequence, SLListType) || PyObject_TypeCheck(sequence, DLListType)) { - /* Special path for extending with a SLList. - * It's not strictly required but it will maintain - * the last accessed item. */ - PyObject* iter_node_obj = ((SLListObject*)sequence)->first; - PyObject* last_node_obj = ((SLListObject*)sequence)->last; + /* Special path for extending with a LList. Better performance. */ + PyObject* iter_node_obj = ((LListObject*)sequence)->first; - while (iter_node_obj != Py_None) + sequence_len = ((LListObject*)sequence)->size; + + if ( unlikely( sequence_len == 0 ) ) + Py_RETURN_NONE; + + LListNodeObject *iter_node; + PyObject *new_node; + + self->size += sequence_len; + if ( self->size == sequence_len ) { - SLListNodeObject* iter_node = (SLListNodeObject*)iter_node_obj; - PyObject* new_node; + iter_node = (LListNodeObject*)iter_node_obj; - new_node = (PyObject*)sllistnode_create( - self->first, iter_node->value, (PyObject*)self); + new_node = (PyObject *)sllistnode_make(self, iter_node->value); self->first = new_node; - if (self->last == Py_None) - self->last = new_node; - - if (iter_node_obj == last_node_obj) - { - /* This is needed to terminate loop if self == sequence. */ - break; - } + self->last = new_node; + ((SLListNodeObject *)new_node)->next = Py_None; iter_node_obj = iter_node->next; + sequence_len -= 1; } - self->size += ((SLListObject*)sequence)->size; + while (sequence_len-- > 0) + { + iter_node = (LListNodeObject*)iter_node_obj; - Py_RETURN_NONE; - } + new_node = (PyObject *)sllistnode_make(self, iter_node->value); - sequence_len = PySequence_Length(sequence); - if (sequence_len == -1) - { - PyErr_SetString(PyExc_ValueError, "Invalid sequence"); - return NULL; - } + ((SLListNodeObject *)new_node)->next = self->first; + self->first = new_node; - for (i = 0; i < sequence_len; ++i) - { - PyObject* item; - PyObject* new_node; + iter_node_obj = iter_node->next; + } - item = PySequence_GetItem(sequence, i); - if (item == NULL) + } + else + { + sequence_len = PySequence_Length(sequence); + if ( unlikely( sequence_len == -1) ) { - PyErr_SetString(PyExc_ValueError, - "Failed to get element from sequence"); + PyErr_SetString(PyExc_ValueError, "Invalid sequence"); return NULL; } + if ( unlikely( sequence_len == 0 ) ) + Py_RETURN_NONE; - new_node = (PyObject*)sllistnode_create(self->first, - item, - (PyObject*)self); + PyObject *item; + PyObject *new_node; + i = 0; + + self->size += sequence_len; + if ( self->size == sequence_len ) + { + item = PySequence_GetItem(sequence, 0); + if ( unlikely(item == NULL) ) + { + PyErr_SetString(PyExc_ValueError, + "Failed to get element from sequence"); + return NULL; + } - self->first = new_node; - if (self->last == Py_None) + new_node = (PyObject *)sllistnode_make(self, item); + + self->first = new_node; self->last = new_node; + ((SLListNodeObject *)new_node)->next = Py_None; - ++self->size; + Py_DECREF(item); + i += 1; + } - Py_DECREF(item); - } + for (; i < sequence_len; ++i) + { + item = PySequence_GetItem(sequence, i); + if ( unlikely(item == NULL) ) + { + PyErr_SetString(PyExc_ValueError, + "Failed to get element from sequence"); + return NULL; + } + + new_node = (PyObject *)sllistnode_make(self, item); + ((SLListNodeObject *)new_node)->next = self->first; + self->first = new_node; + + + Py_DECREF(item); + } + + } Py_RETURN_NONE; } @@ -849,7 +900,7 @@ static PyObject* sllist_remove(SLListObject* self, PyObject* arg) PyObject* list_ref; PyObject* value; - if (!PyObject_TypeCheck(arg, &SLListNodeType)) + if (!PyObject_TypeCheck(arg, SLListNodeType)) { PyErr_SetString(PyExc_TypeError, "Argument is not an sllistnode"); return NULL; @@ -961,7 +1012,7 @@ static PyObject* sllist_concat(PyObject* self, PyObject* other) SLListObject* new_list; new_list = (SLListObject*)PyObject_CallObject( - (PyObject*)&SLListType, NULL); + (PyObject*)SLListType, NULL); if (!sllist_extend_internal(new_list, self) || !sllist_extend_internal(new_list, other)) @@ -990,7 +1041,7 @@ static PyObject* sllist_repeat(PyObject* self, Py_ssize_t count) Py_ssize_t i; new_list = (SLListObject*)PyObject_CallObject( - (PyObject*)&SLListType, NULL); + (PyObject*)SLListType, NULL); for (i = 0; i < count; ++i) { @@ -1055,7 +1106,7 @@ static int sllist_set_item(PyObject* self, Py_ssize_t index, PyObject* val) /* The rest of this function handles normal assignment: * list[index] = item */ - if (!PyObject_TypeCheck(val, &SLListNodeType)) { + if (!PyObject_TypeCheck(val, SLListNodeType)) { PyErr_SetString(PyExc_TypeError, "Argument is not an sllistnode"); return -1; } @@ -1161,6 +1212,86 @@ static PyObject* sllist_popright(SLListObject* self) return value; } +static PyObject* sllist_pop(SLListObject* self, PyObject *arg) +{ + SLListNodeObject *del_node; + SLListNodeObject *prev_node; + + PyObject *value; + PyObject *indexObject = NULL; + + Py_ssize_t index; + Py_ssize_t i; + + + if (!PyArg_UnpackTuple(arg, "pop", 0, 1, &indexObject)) + { + return NULL; + } + + /* If we did not get passed the "index" arg, just return popright */ + if ( indexObject == NULL ) + { + return sllist_popright( self ); + } + + if (!Py23Int_Check(indexObject)) + { + PyErr_SetString(PyExc_TypeError, "Index must be an integer"); + return NULL; + } + + index = Py23Int_AsSsize_t(indexObject); + + + /* Negative index */ + if ( index < 0 ) + index = ((SLListObject*)self)->size + index; + + if ( index == 0 ) + { + return sllist_popleft(self); + } + else if( index + 1 == ((SLListObject*)self)->size ) + { + return sllist_popright(self); + } + + if ( self->first == Py_None ) + { + PyErr_SetString(PyExc_ValueError, "List is empty"); + return NULL; + } + + /* Either a negative greater than index size, or a positive greater than size */ + if ( index < 0 || index >= ((SLListObject*)self)->size ) + { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + return NULL; + } + + /* Start at first node, and walk to the one we will pop */ + prev_node = (SLListNodeObject*)self->first; + del_node = (SLListNodeObject*)prev_node->next; + for(i=1; i < index; i++) { + prev_node = del_node; + del_node = (SLListNodeObject*)del_node->next; + } + + /* Unlink this node from the chain */ + prev_node->next = del_node->next; + + --self->size; + + + Py_INCREF(del_node->value); + value = del_node->value; + + del_node->next = Py_None; + Py_DECREF((PyObject*)del_node); + + return value; +} static PyObject* sllist_iter(PyObject* self) { @@ -1183,7 +1314,7 @@ static PyObject* sllist_iter(PyObject* self) return NULL; } - result = PyObject_CallObject((PyObject*)&SLListIteratorType, args); + result = PyObject_CallObject((PyObject*)SLListIteratorType, args); Py_DECREF(args); @@ -1248,6 +1379,71 @@ static PyObject* sllist_to_string(SLListObject* self, return NULL; } +static int sllist_contains(PyObject *self, PyObject *value) +{ + SLListNodeObject *node; + + node = (SLListNodeObject *) ((SLListObject*)self)->first; + + while ( (PyObject *)node != Py_None ) + { + if( node->value == value ) + return 1; + + node = (SLListNodeObject *)node->next; + } + + return 0; +} + + +static PyObject* sllist_index(SLListObject *self, PyObject *value) +{ + + SLListNodeObject *node; + Py_ssize_t idx; + + node = (SLListNodeObject *) self->first; + idx = 0; + + while ( (PyObject*)node != Py_None ) + { + if( node->value == value ) + return PyLong_FromSsize_t(idx); + + node = (SLListNodeObject *)node->next; + idx += 1; + } + PyErr_Format(PyExc_ValueError, "No such value in list"); + return NULL; +} + +static PyObject* sllist_rindex(SLListObject *self, PyObject *value) +{ + + SLListNodeObject *node; + Py_ssize_t idx; + Py_ssize_t matchedIdx; + + node = (SLListNodeObject *) self->first; + idx = 0; + matchedIdx = -1; + + while ( (PyObject*)node != Py_None ) + { + if( node->value == value ) + matchedIdx = idx; + + node = (SLListNodeObject *)node->next; + idx += 1; + } + + if ( matchedIdx != -1 ) + return PyLong_FromSsize_t(matchedIdx); + + PyErr_Format(PyExc_ValueError, "No such value in list"); + return NULL; +} static PyObject* sllist_repr(SLListObject* self) { @@ -1266,9 +1462,13 @@ static Py_ssize_t sllist_len(PyObject* self) return ((SLListObject*)self)->size; } - +#if 0 static long sllist_hash(SLListObject* self) { + /* NOT A VALID HASH METHOD! [1, 5, 8] gives SAME hash as [1, 8, 5], for example, among many others */ + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + long hash = 0; PyObject* iter_node_obj = self->first; @@ -1287,6 +1487,142 @@ static long sllist_hash(SLListObject* self) return hash; } +#endif + +/* + * sllist_slice - Slice function assuming normalized indexes. + * + * For potentially non-normalized, use sllist_simpleslice + * or call _normalize_indexes + * + * self - SLList to slice from + * start_idx - Start of slicing (normalized) + * end_idx - End of slicing (normalized) + * step - Slice step + * sliceLength - Length of resulting slice. Pass -1 to calculate. + */ +static PyObject *sllist_slice(SLListObject *self, Py_ssize_t start_idx, Py_ssize_t end_idx, Py_ssize_t step, Py_ssize_t sliceLength) +{ + SLListObject *ret; + SLListNodeObject *cur; + SLListNodeObject *new_node; + Py_ssize_t i; + + Py_ssize_t stepI; + + ret = (SLListObject *)sllist_new(SLListType, Py_None, Py_None); + + if ( sliceLength == -1 ) + { + sliceLength = end_idx - start_idx; /* Reusing end_idx as max */ + if(step > 1 ) + { + sliceLength = sliceLength / step + ( end_idx % step ? 1 : 0 ); + } + } + + + if ( step > 1 ) + { + Py_ssize_t tmp_slice_resize; + tmp_slice_resize = start_idx + (step * sliceLength); + if ( tmp_slice_resize < end_idx ) + end_idx = tmp_slice_resize; + } + + + + if ( sliceLength == 0 ) + return (PyObject *)ret; + + + cur = (SLListNodeObject *)self->first; + for(i=0; i < start_idx; i++) + cur = (SLListNodeObject*)cur->next; + + new_node = sllistnode_make(self, cur->value); + new_node->next = Py_None; + + ret->first = ret->last = (PyObject *)new_node; + ret->size = 1; + + + SLListNodeObject *prev; + prev = new_node; + + + while ( ret->size < sliceLength ) + { + stepI = step; + while(stepI--) + cur = (SLListNodeObject*)cur->next; + + new_node = sllistnode_make(self, cur->value); + + prev->next = (PyObject *)new_node; + prev = new_node; + + ret->last = (PyObject *)new_node; + + ret->size += 1; + + + } + ((SLListNodeObject*)ret->last)->next = Py_None; + + return (PyObject*) ret; + +} + +/* + * sllist_simpleslice - Simple (PySequence) slices. Can take non-normalized indexes (like negatives) + */ +static PyObject *sllist_simpleslice(SLListObject *self, Py_ssize_t idx_start, Py_ssize_t idx_end) +{ + if( !_normalize_indexes(self->size, &idx_start, &idx_end) ) + { + SLListObject *ret = (SLListObject *)sllist_new(SLListType, NULL, NULL); + return (PyObject *)ret; + } + + return sllist_slice(self, idx_start, idx_end, 1, -1); +} + +static PyObject *sllist_subscript(SLListObject *self, PyObject *item) +{ + + if ( PyIndex_Check(item) ) + { + Py_ssize_t i; + + i = PyNumber_AsSsize_t(item, PyExc_IndexError); + + if ( i == -1 && PyErr_Occurred() ) + return NULL; + + if ( i < 0 ) + i += self->size; + + return sllist_get_item((PyObject *)self, i); + } + + if ( PySlice_Check(item) ) + { + Py_ssize_t start, stop, step, sliceLength; + + if ( PySlice_GetIndicesEx( (GET_INDICES_TYPE *)item, self->size, &start, &stop, &step, &sliceLength ) ) + return NULL; /* Error */ + + return sllist_slice(self, start, stop, step, sliceLength); + } + else + { + + PyErr_Format(PyExc_TypeError, "Indices must be integers, not %s", item->ob_type->tp_name); + return NULL; + } + +} static PyMethodDef SLListMethods[] = @@ -1321,8 +1657,14 @@ static PyMethodDef SLListMethods[] = { "nodeat", (PyCFunction)sllist_node_at, METH_O, "Return node at index" }, - { "pop", (PyCFunction)sllist_popright, METH_NOARGS, - "Remove last element from the list and return it" }, + { "index", (PyCFunction)sllist_index, METH_O, + "Returns the first index of a value" }, + + { "rindex", (PyCFunction)sllist_rindex, METH_O, + "Returns the last index of a value" }, + + { "pop", (PyCFunction)sllist_pop, METH_VARARGS, + "Remove an element by index from the list and return it, or last item if no index provided" }, { "popleft", (PyCFunction)sllist_popleft, METH_NOARGS, "Remove first element from the list and return it" }, @@ -1358,15 +1700,24 @@ static PySequenceMethods SLListSequenceMethods = sllist_concat, /* sq_concat */ sllist_repeat, /* sq_repeat */ sllist_get_item, /* sq_item */ - 0, /* sq_slice; */ + (ssizessizeargfunc) sllist_simpleslice, /* sq_slice; */ sllist_set_item, /* sq_ass_item */ 0, /* sq_ass_slice */ - 0, /* sq_contains */ + sllist_contains, /* sq_contains */ sllist_inplace_concat, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }; -static PyTypeObject SLListType = +static PyMappingMethods SLListMappingMethods[] = { +{ + sllist_len, /* mp_length */ + (binaryfunc)sllist_subscript, /* mp_subscript */ + 0, /* mp_ass_subscript */ +} +}; + + +PyTypeObject SLListType[] = { { PyVarObject_HEAD_INIT(NULL, 0) "llist.sllist", /* tp_name */ @@ -1380,8 +1731,8 @@ static PyTypeObject SLListType = (reprfunc)sllist_repr, /* tp_repr */ 0, /* tp_as_number */ &SLListSequenceMethods, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - (hashfunc)sllist_hash, /* tp_hash */ + SLListMappingMethods, /* tp_as_mapping */ + 0, /* tp_hash */ 0, /* tp_call */ (reprfunc)sllist_str, /* tp_str */ 0, /* tp_getattro */ @@ -1408,6 +1759,7 @@ static PyTypeObject SLListType = (initproc)sllist_init, /* tp_init */ 0, /* tp_alloc */ sllist_new, /* tp_new */ +} }; @@ -1438,7 +1790,7 @@ static PyObject* sllistiterator_new(PyTypeObject* type, if (!PyArg_UnpackTuple(args, "__new__", 1, 1, &owner_list)) return NULL; - if (!PyObject_TypeCheck(owner_list, &SLListType)) + if (!PyObject_TypeCheck(owner_list, SLListType)) { PyErr_SetString(PyExc_TypeError, "sllist argument expected"); return NULL; @@ -1485,7 +1837,7 @@ static PyObject* sllistiterator_iternext(PyObject* self) -static PyTypeObject SLListIteratorType = +PyTypeObject SLListIteratorType[] = { { PyVarObject_HEAD_INIT(NULL, 0) "llist.sllistiterator", /* tp_name */ @@ -1525,6 +1877,7 @@ static PyTypeObject SLListIteratorType = 0, /* tp_init */ 0, /* tp_alloc */ sllistiterator_new, /* tp_new */ +} }; @@ -1532,19 +1885,19 @@ static PyTypeObject SLListIteratorType = int sllist_init_type(void) { return - ((PyType_Ready(&SLListType) == 0) && - (PyType_Ready(&SLListNodeType) == 0) && - (PyType_Ready(&SLListIteratorType) == 0)) + ((PyType_Ready(SLListType) == 0) && + (PyType_Ready(SLListNodeType) == 0) && + (PyType_Ready(SLListIteratorType) == 0)) ? 1 : 0; } void sllist_register(PyObject* module) { - Py_INCREF(&SLListType); - Py_INCREF(&SLListNodeType); - Py_INCREF(&SLListIteratorType); + Py_INCREF(SLListType); + Py_INCREF(SLListNodeType); + Py_INCREF(SLListIteratorType); - PyModule_AddObject(module, "sllist", (PyObject*)&SLListType); - PyModule_AddObject(module, "sllistnode", (PyObject*)&SLListNodeType); - PyModule_AddObject(module, "sllistiterator", (PyObject*)&SLListIteratorType); + PyModule_AddObject(module, "sllist", (PyObject*)SLListType); + PyModule_AddObject(module, "sllistnode", (PyObject*)SLListNodeType); + PyModule_AddObject(module, "sllistiterator", (PyObject*)SLListIteratorType); } diff --git a/src/sllist_types.h b/src/sllist_types.h new file mode 100644 index 0000000..05f89c2 --- /dev/null +++ b/src/sllist_types.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2017 Timothy Savannah + * Released under the MIT license (see attached LICENSE file). + */ + +#ifndef SLLIST_TYPES_H +#define SLLIST_TYPES_H + +int sllist_init_type(void); +void sllist_register(PyObject* module); + +extern PyTypeObject SLListType[]; +extern PyTypeObject SLListNodeType[]; +extern PyTypeObject SLListIteratorType[]; + + +/* SLListNode */ + +typedef struct +{ + PyObject_HEAD + PyObject *list_weakref; + PyObject *value; + PyObject *next; +} SLListNodeObject; + +typedef struct +{ + PyObject_HEAD + PyObject *first; + PyObject *last; + Py_ssize_t size; + PyObject *weakref_list; +} SLListObject; + +#endif /* SLLIST_TYPES_H */ diff --git a/tests/benchmark.py b/tests/benchmark.py new file mode 100755 index 0000000..78acc36 --- /dev/null +++ b/tests/benchmark.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +import os +import gc +import time +import random + +from cllist import dllist, sllist + +LIST_SIZE = 600 + +NUM_POPS = 400 + +NUM_ITERS = 200 + +def doPops(lst, popIdxs): + + for popIdx in popIdxs: + lst.pop(popIdx) + +def doTime(func): + t1 = time.time() + func() + t2 = time.time() + + return t2 - t1 + +if __name__ == '__main__': + + + if 'LIST_SIZE' in os.environ: + LIST_SIZE = int(os.environ['LIST_SIZE']) + if 'NUM_POPS' in os.environ: + NUM_POPS = int(os.environ['NUM_POPS']) + if 'NUM_ITERS' in os.environ: + NUM_POPS = int(os.environ['NUM_ITERS']) + + primeList = list(range(LIST_SIZE)) + + # Generate a list of random indexes to pop. + # We avoid key errors by making sure we don't have a possible + # maximum larger than the smallest our list will become + + randomPops = [] + for i in range(NUM_POPS): + nextNum = random.randint(0, LIST_SIZE - i - 1) # Cheaply make sure we don't get key errors + + randomPops.append(nextNum) + + headerLine = "Starting: LIST_SIZE=%d NUM_POPS=%d NUM_ITERS=%d" %(LIST_SIZE, NUM_POPS, NUM_ITERS,) + + print ( "%s\n%s\n\n" %(headerLine, '-' * len(headerLine) ) ) + + listTime = 0 + sllistTime = 0 + dllistTime = 0 + + gc.collect() + for i in range(NUM_ITERS): + lst1 = primeList[:] + listTime += doTime ( lambda : doPops(lst1, randomPops) ) + gc.collect() + + for i in range(NUM_ITERS): + slst = sllist(primeList[:]) + sllistTime += doTime ( lambda : doPops(slst, randomPops) ) + gc.collect() + + for i in range(NUM_ITERS): + dlst = dllist(primeList[:]) + dllistTime += doTime ( lambda : doPops(dlst, randomPops) ) + gc.collect() + + # csllistTime = doTime ( lambda : doPops(cslst, randomPops) ) + csllistTime = 0.0 + + print ( "List time:\t\t%f\t= %f\nSL time:\t\t%f\t= %f\nDL time:\t\t%f\t= %f\n" %(listTime, listTime / NUM_ITERS, sllistTime, sllistTime / NUM_ITERS, dllistTime, dllistTime / NUM_ITERS) ) diff --git a/tests/benchmark_contains.py b/tests/benchmark_contains.py new file mode 100755 index 0000000..00b037f --- /dev/null +++ b/tests/benchmark_contains.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +import os +import gc +import time +import random + +from cllist import dllist, sllist + +LIST_SIZE = 700 + +NUM_CONTAINS = 1500 + +NUM_ITERS = 200 + +def doContains(lst, containsItems): + + for containsItem in containsItems: + return containsItem in lst + +def doTime(func): + t1 = time.time() + func() + t2 = time.time() + + return t2 - t1 + +if __name__ == '__main__': + + + if 'LIST_SIZE' in os.environ: + LIST_SIZE = int(os.environ['LIST_SIZE']) + if 'NUM_CONTAINS' in os.environ: + NUM_CONTAINS = int(os.environ['NUM_CONTAINS']) + if 'NUM_ITERS' in os.environ: + NUM_CONTAINS = int(os.environ['NUM_ITERS']) + + primeList = list(range(LIST_SIZE)) + + # Generate a list of random indexes to pop. + # We avoid key errors by making sure we don't have a possible + # maximum larger than the smallest our list will become + + randomItems = [] + for j in range(NUM_ITERS): + theseItems = [] + for i in range(NUM_CONTAINS): + nextNum = random.randint(0, LIST_SIZE - 1) # Cheaply make sure we don't get key errors + + theseItems.append( primeList[nextNum] ) + randomItems.append(theseItems) + + headerLine = "Starting: LIST_SIZE=%d NUM_CONTAINS=%d NUM_ITERS=%d" %(LIST_SIZE, NUM_CONTAINS, NUM_ITERS,) + + print ( "%s\n%s\n\n" %(headerLine, '-' * len(headerLine) ) ) + + listTime = 0 + sllistTime = 0 + dllistTime = 0 + + gc.collect() + for i in range(NUM_ITERS): + dlst = dllist(primeList[:]) + dllistTime += doTime ( lambda : doContains(dlst, randomItems[i]) ) + gc.collect() + + for i in range(NUM_ITERS): + lst1 = primeList[:] + listTime += doTime ( lambda : doContains(lst1, randomItems[i]) ) + gc.collect() + + for i in range(NUM_ITERS): + slst = sllist(primeList[:]) + sllistTime += doTime ( lambda : doContains(slst, randomItems[i]) ) + gc.collect() + + + # csllistTime = doTime ( lambda : doPops(cslst, randomPops) ) + csllistTime = 0.0 + + print ( "List time:\t\t%f\t= %f\nSL time:\t\t%f\t= %f\nDL time:\t\t%f\t= %f\n" %(listTime, listTime / NUM_ITERS, sllistTime, sllistTime / NUM_ITERS, dllistTime, dllistTime / NUM_ITERS) ) diff --git a/tests/benchmark_extend.py b/tests/benchmark_extend.py new file mode 100755 index 0000000..9a3a26b --- /dev/null +++ b/tests/benchmark_extend.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python + +import os +import gc +import time +import random + +from cllist import dllist, sllist + +LIST1_SIZE = 600 + +LIST2_SIZE = 400 + +NUM_ITERS = 200 + +def doExtend(lst, otherList): + + return lst + otherList + +def doIExtend(lst, otherList): + + lst += otherList + return lst + +def doExtendLeft(lst, otherList): + + lst.extendleft(otherList) + return lst + +def doTime(func): + t1 = time.time() + func() + t2 = time.time() + + return t2 - t1 + +if __name__ == '__main__': + + + if 'LIST1_SIZE' in os.environ: + LIST1_SIZE = int(os.environ['LIST1_SIZE']) + if 'LIST2_SIZE' in os.environ: + LIST2_SIZE = int(os.environ['LIST2_SIZE']) + if 'NUM_ITERS' in os.environ: + LIST2_SIZE = int(os.environ['NUM_ITERS']) + + primeList = list(range(LIST1_SIZE)) + + # Generate a list of random indexes to pop. + # We avoid key errors by making sure we don't have a possible + # maximum larger than the smallest our list will become + + primeList2 = list(range(LIST2_SIZE)) + + headerLine = "Starting: LIST1_SIZE=%d LIST2_SIZE=%d NUM_ITERS=%d" %(LIST1_SIZE, LIST2_SIZE, NUM_ITERS,) + + print ( "%s\n%s\n\n" %(headerLine, '-' * len(headerLine) ) ) + + listTime = 0 + sllistTime = 0 + dllistTime = 0 + bllistTime = 0 + b2llistTime = 0 + + listITime = 0 + sllistITime = 0 + dllistITime = 0 + bllistITime = 0 + b2llistITime = 0 + + leftbllistTime = 0 + leftb2llistTime = 0 + + lst1 = primeList[:] + lst2 = primeList2[:] + gc.collect() + for i in range(NUM_ITERS): + listTime += doTime ( lambda : doExtend(lst1, lst2) ) + gc.collect() + + for i in range(NUM_ITERS): + lst1 = primeList[:] + lst2 = primeList2[:] + gc.collect() + listITime += doTime ( lambda : doIExtend(lst1, lst2) ) + gc.collect() + + slst1 = sllist(primeList[:]) + slst2 = sllist(primeList2[:]) + gc.collect() + for i in range(NUM_ITERS): + sllistTime += doTime ( lambda : doExtend(slst1, slst2) ) + gc.collect() + + for i in range(NUM_ITERS): + slst1 = sllist(primeList[:]) + slst2 = sllist(primeList2[:]) + gc.collect() + sllistITime += doTime ( lambda : doIExtend(slst1, slst2) ) + gc.collect() + + + dlst1 = dllist(primeList[:]) + dlst2 = dllist(primeList2[:]) + gc.collect() + for i in range(NUM_ITERS): + dllistTime += doTime ( lambda : doExtend(dlst1, dlst2) ) + gc.collect() + + for i in range(NUM_ITERS): + dlst1 = dllist(primeList[:]) + dlst2 = dllist(primeList2[:]) + gc.collect() + dllistITime += doTime ( lambda : doIExtend(dlst1, dlst2) ) + gc.collect() + + dlst1 = dllist(primeList[:]) + slst2 = sllist(primeList2[:]) + gc.collect() + for i in range(NUM_ITERS): + bllistTime += doTime ( lambda : doExtend(dlst1, slst2) ) + gc.collect() + + for i in range(NUM_ITERS): + dlst1 = dllist(primeList[:]) + slst2 = sllist(primeList2[:]) + gc.collect() + bllistITime += doTime ( lambda : doIExtend(dlst1, slst2) ) + gc.collect() + + slst1 = sllist(primeList[:]) + dlst2 = dllist(primeList2[:]) + gc.collect() + for i in range(NUM_ITERS): + b2llistTime += doTime ( lambda : doExtend(slst1, dlst2) ) + gc.collect() + + for i in range(NUM_ITERS): + slst1 = sllist(primeList[:]) + dlst2 = dllist(primeList2[:]) + gc.collect() + b2llistITime += doTime ( lambda : doIExtend(slst1, dlst2) ) + gc.collect() + + for i in range(NUM_ITERS): + dlst1 = dllist(primeList[:]) + slst2 = sllist(primeList2[:]) + gc.collect() + leftbllistTime += doTime ( lambda : doExtendLeft(dlst1, slst2) ) + gc.collect() + + for i in range(NUM_ITERS): + slst1 = sllist(primeList[:]) + dlst2 = dllist(primeList2[:]) + gc.collect() + leftb2llistTime += doTime ( lambda : doExtendLeft(slst1, dlst2) ) + gc.collect() + + print ( "List time:\t\t%f\t= %f\nSL time:\t\t%f\t= %f\nDL time:\t\t%f\t= %f\nBL time:\t\t%f\t= %f\nBL2 time:\t\t%f\t= %f\n" %(listTime, listTime / NUM_ITERS, sllistTime, sllistTime / NUM_ITERS, dllistTime, dllistTime / NUM_ITERS, bllistTime, bllistTime / NUM_ITERS, b2llistTime, b2llistTime / NUM_ITERS) ) + print ( "List itime:\t\t%f\t= %f\nSL itime:\t\t%f\t= %f\nDL itime:\t\t%f\t= %f\nBL time:\t\t%f\t= %f\nBL2 time:\t\t%f\t= %f\n" %(listITime, listITime / NUM_ITERS, sllistITime, sllistITime / NUM_ITERS, dllistITime, dllistITime / NUM_ITERS, bllistITime, bllistITime / NUM_ITERS, b2llistITime, b2llistITime / NUM_ITERS) ) + + print ( "leftB time:\t\t%f\t= %f\nleftB2 time:\t\t%f\t= %f\n" %(leftbllistTime, leftbllistTime / NUM_ITERS, leftb2llistTime, leftb2llistTime / NUM_ITERS) ) diff --git a/tests/benchmark_slice.py b/tests/benchmark_slice.py new file mode 100755 index 0000000..dccde5e --- /dev/null +++ b/tests/benchmark_slice.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +import os +import gc +import time +import random + +from cllist import dllist, sllist + +LIST_SIZE = 700 + +NUM_SLICES = 500 + +NUM_ITERS = 200 + +def doSlice(lst, slices): + for thisSlice in slices: + try: + x = lst[thisSlice[0] : thisSlice[1]] + except TypeError as e: + import pdb; pdb.set_trace() + +def doTime(func): + t1 = time.time() + func() + t2 = time.time() + + return t2 - t1 + +if __name__ == '__main__': + + + if 'LIST_SIZE' in os.environ: + LIST_SIZE = int(os.environ['LIST_SIZE']) + if 'NUM_SLICES' in os.environ: + NUM_SLICES = int(os.environ['NUM_SLICES']) + if 'NUM_ITERS' in os.environ: + NUM_SLICES = int(os.environ['NUM_ITERS']) + + primeList = list(range(LIST_SIZE)) + + # Generate a list of random indexes to pop. + # We avoid key errors by making sure we don't have a possible + # maximum larger than the smallest our list will become + + randomSlices = [] + for j in range(NUM_ITERS): + theseSlices = [] + for i in range(NUM_SLICES): + num1 = random.randint(0, LIST_SIZE - 1) + num2 = random.randint(0, LIST_SIZE - 1) + if num1 > num2: + thisSlice = (num2, num1) + else: + thisSlice = (num1, num2) + + theseSlices.append( thisSlice ) + randomSlices.append(theseSlices) + + headerLine = "Starting: LIST_SIZE=%d NUM_SLICES=%d NUM_ITERS=%d" %(LIST_SIZE, NUM_SLICES, NUM_ITERS,) + + print ( "%s\n%s\n\n" %(headerLine, '-' * len(headerLine) ) ) + + listTime = 0 + sllistTime = 0 + dllistTime = 0 + + gc.collect() + for i in range(NUM_ITERS): + lst1 = primeList[:] + listTime += doTime ( lambda : doSlice(lst1, randomSlices[i]) ) + gc.collect() + + gc.collect() + for i in range(NUM_ITERS): + dlst = dllist(primeList[:]) + dllistTime += doTime ( lambda : doSlice(dlst, randomSlices[i]) ) + gc.collect() + + + for i in range(NUM_ITERS): + slst = sllist(primeList[:]) + sllistTime += doTime ( lambda : doSlice(slst, randomSlices[i]) ) + gc.collect() + + + # csllistTime = doTime ( lambda : doPops(cslst, randomPops) ) + csllistTime = 0.0 + + print ( "List time:\t\t%f\t= %f\nSL time:\t\t%f\t= %f\nDL time:\t\t%f\t= %f\n" %(listTime, listTime / NUM_ITERS, sllistTime, sllistTime / NUM_ITERS, dllistTime, dllistTime / NUM_ITERS) ) diff --git a/tests/llist_test.py b/tests/llist_test.py old mode 100644 new mode 100755 index fe9822b..522c1f3 --- a/tests/llist_test.py +++ b/tests/llist_test.py @@ -2,11 +2,12 @@ # -*- coding: utf-8 -*- import gc import sys +import random import unittest -from llist import sllist -from llist import sllistnode -from llist import dllist -from llist import dllistnode +from cllist import sllist +from cllist import sllistnode +from cllist import dllist +from cllist import dllistnode gc.set_debug(gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_STATS) @@ -553,6 +554,94 @@ def test_pop(self): self.assertEqual(list(ll), ref[:-1]) self.assertEqual(del_node.next, None) + ref = py23_range(0, 1024, 4) + ll = sllist(ref) + + result = ll.pop(1) + self.assertEqual(result, ref[1]) + result = ll.pop(1) + self.assertEqual(result, ref[2]) + self.assertEqual(ll.size, len(ref)-2) + + ref = py23_range(0, 1024, 4) + ll = sllist(ref) + result = ll.pop(0) + self.assertEqual(result, ref[0]) + + self.assertEqual(ll.first.value, ref[1]) + for i in range(len(ll)): + result = ll.pop(0) + self.assertEqual(result, ref[i+1]) + + self.assertEqual(ll.first, None) + self.assertEqual(ll.last, None) + + ref = py23_range(0, 1024, 4) + ll = sllist(ref) + i = len(ll)-1 + while i >= 0: + result = ll.pop(i) + self.assertEqual(result, ref[i]) + i -= 1 + + self.assertEqual(ll.first, None) + self.assertEqual(ll.last, None) + + + def test_slice(self): + + lst = list(range(100)) + slst = sllist(lst) + + self.assertEqual(lst[0:20], list(slst[0:20])) + self.assertEqual(lst[40:60], list(slst[40:60])) + self.assertEqual(lst[60:40], list(slst[60:40])) + self.assertEqual(lst[:-1], list(slst[:-1])) + self.assertEqual(lst[-20:], list(slst[-20:])) + self.assertEqual(lst[-20:-5], list(slst[-20:-5])) + self.assertEqual(lst[-5:-20], list(slst[-5:-20])) + self.assertEqual(lst[-70:50], list(slst[-70:50])) + self.assertEqual(lst[5:500], list(slst[5:500])) + self.assertEqual(lst[:], list(slst[:])) + + smlst = list(range(8)) + smslst = sllist(smlst) + + self.assertEqual(smlst[2:5], list(smslst[2:5])) + self.assertEqual(smlst[-3:-1], list(smslst[-3:-1])) + + for i in range(100): + for j in range(100): + try: + self.assertEqual(lst[i:j], list(slst[i:j])) + except AssertionError as ae: + import pdb; pdb.set_trace() + sys.stderr.write("Failed on [ %d : %d ]\n" %(i, j)) + raise ae + + # Test if version of python (2.7+ , 3.? + ) supports step in slices + try: + lst[0:10:2] + except: + # If not supported, test is over + return + + self.assertEqual(lst[0:20:2], list(slst[0:20:2])) + self.assertEqual(lst[0:21:2], list(slst[0:21:2])) + self.assertEqual(lst[50:80:6], list(slst[50:80:6])) + + for i in range(30): + for j in range(30): + for s in range(1, 30, 1): + try: + self.assertEqual(lst[i:j:s], list(slst[i:j:s])) + except AssertionError as ae: + sys.stderr.write("Failed on [ %d : %d : %d ]\n" %(i, j, s)) + raise ae + + + + def test_popleft(self): ref = py23_range(0, 1024, 4) ll = sllist(ref) @@ -763,6 +852,36 @@ def test_concat_inplace_empty(self): self.assertEqual(filled, sllist(filled_ref + [])) self.assertEqual(len(filled), len(filled_ref)) + def test_index(self): + lst = [1, 5, 10, 5, 9] + + sl = sllist(lst) + + self.assertEqual(sl.index(1), 0) + self.assertEqual(sl.index(5), 1) + self.assertEqual(sl.rindex(5), 3) + self.assertEqual(sl.rindex(9), 4) + + gotException = False + try: + sl.index(2) + except ValueError: + gotException = True + + self.assertEqual(gotException, True) + + def test_contains(self): + + lst = [1, 5, 7] + + sl = sllist(lst) + + self.assertEqual(5 in sl, True) + self.assertEqual(1 in sl, True) + self.assertEqual(7 in sl, True) + self.assertEqual(8 in sl, False) + + def test_repeat(self): ref = py23_range(0, 1024, 4) ll = sllist(ref) @@ -803,11 +922,12 @@ def test_node_readonly_attributes(self): ll = sllistnode() self.assertRaises(expected_error, setattr, ll, 'next', None) - def test_list_hash(self): - self.assertEqual(hash(sllist()), hash(sllist())) - self.assertEqual(hash(sllist(py23_range(0, 1024, 4))), - hash(sllist(py23_range(0, 1024, 4)))) - self.assertEqual(hash(sllist([0, 2])), hash(sllist([0.0, 2.0]))) +# COMMENTED BECAUSE HASH DOES NOT WORK +# def test_list_hash(self): +# self.assertEqual(hash(sllist()), hash(sllist())) +# self.assertEqual(hash(sllist(py23_range(0, 1024, 4))), +# hash(sllist(py23_range(0, 1024, 4)))) +# self.assertEqual(hash(sllist([0, 2])), hash(sllist([0.0, 2.0]))) class testdllist(unittest.TestCase): @@ -1272,6 +1392,76 @@ def test_pop(self): self.assertEqual(del_node.prev, None) self.assertEqual(del_node.next, None) + ref = py23_range(0, 1024, 4) + ll = dllist(ref) + + #import pdb; pdb.set_trace() + result = ll.pop(1) + self.assertEqual(result, ref[1]) + result = ll.pop(1) + self.assertEqual(result, ref[2]) + self.assertEqual(ll.size, len(ref)-2) + + secondNode = ll.nodeat(1) + + self.assertEquals(secondNode.prev, ll.first) + self.assertEquals(ll.first.prev, None) + + ref = py23_range(0, 1024, 4) + ll = dllist(ref) + result = ll.pop(0) + self.assertEqual(result, ref[0]) + + self.assertEqual(ll.first.value, ref[1]) + for i in range(len(ll)): + result = ll.pop(0) + self.assertEqual(result, ref[i+1]) + + self.assertEqual(ll.first, None) + self.assertEqual(ll.last, None) + + ref = py23_range(0, 1024, 4) + ll = dllist(ref) + i = len(ll) - 1 + while i >= 0: + result = ll.pop(i) + self.assertEqual(result, ref[i]) + i -= 1 + + self.assertEqual(ll.first, None) + self.assertEqual(ll.last, None) + + + ref = py23_range(0, 1024, 4) + + + lastIdx = list(ref).index(ref[-1]) + + allIndexes = list(range(lastIdx+1)) + random.shuffle(allIndexes) + + ll = dllist(ref) + + while allIndexes: +# print ( "Popping %d out of %d indexes. Value: %s\n\tFirst=%s\n\tMiddle=%s\n\tLast=%s\n\tSize=%d\n" %(allIndexes[0], len(allIndexes), str(ll[allIndexes[0]]), ll.first, ll.middle, ll.last, ll.size)) + nextIndex = allIndexes.pop(0) + + listAccessValue = ll[nextIndex] + + poppedValue = ll.pop(nextIndex) + + self.assertEquals(listAccessValue, poppedValue) + + for i in range(len(allIndexes)): + if allIndexes[i] > nextIndex: + allIndexes[i] -= 1 + + self.assertEqual(ll.first, None) + self.assertEqual(ll.last, None) + + + + def test_popleft(self): ref = py23_range(0, 1024, 4) ll = dllist(ref) @@ -1502,6 +1692,83 @@ def test_concat_inplace_empty(self): self.assertEqual(filled, dllist(filled_ref + [])) self.assertEqual(len(filled), len(filled_ref)) + def test_index(self): + lst = [1, 5, 10, 5, 9] + + dl = dllist(lst) + + self.assertEqual(dl.index(1), 0) + self.assertEqual(dl.index(5), 1) + self.assertEqual(dl.rindex(5), 3) + self.assertEqual(dl.rindex(9), 4) + + gotException = False + try: + dl.index(2) + except ValueError: + gotException = True + + self.assertEqual(gotException, True) + + def test_contains(self): + + lst = [1, 5, 7] + + sl = dllist(lst) + + self.assertEqual(5 in sl, True) + self.assertEqual(1 in sl, True) + self.assertEqual(7 in sl, True) + self.assertEqual(8 in sl, False) + + def test_slice(self): + + lst = list(range(100)) + dlst = dllist(lst) + + self.assertEqual(lst[0:20], list(dlst[0:20])) + self.assertEqual(lst[40:60], list(dlst[40:60])) + self.assertEqual(lst[60:40], list(dlst[60:40])) + self.assertEqual(lst[:-1], list(dlst[:-1])) + self.assertEqual(lst[-20:], list(dlst[-20:])) + self.assertEqual(lst[-20:-5], list(dlst[-20:-5])) + self.assertEqual(lst[-5:-20], list(dlst[-5:-20])) + self.assertEqual(lst[-70:50], list(dlst[-70:50])) + self.assertEqual(lst[5:500], list(dlst[5:500])) + self.assertEqual(lst[:], list(dlst[:])) + + smlst = list(range(8)) + smdlst = dllist(smlst) + + self.assertEqual(smlst[2:5], list(smdlst[2:5])) + self.assertEqual(smlst[-3:-1], list(smdlst[-3:-1])) + + for i in range(100): + for j in range(100): + self.assertEqual(lst[i:j], list(dlst[i:j])) + + # Test if version of python (2.7+ , 3.? + ) supports step in slices + try: + lst[0:10:2] + except: + # If not supported, test is over + return + + self.assertEqual(lst[0:20:2], list(dlst[0:20:2])) + self.assertEqual(lst[0:21:2], list(dlst[0:21:2])) + self.assertEqual(lst[50:80:6], list(dlst[50:80:6])) + + for i in range(100): + for j in range(100): + for s in range(1, 100, 1): + try: + self.assertEqual(lst[i:j:s], list(dlst[i:j:s])) + except AssertionError as ae: + sys.stderr.write("Failed on [ %d : %d : %d ]\n" %(i, j, s)) + raise ae + + + def test_repeat(self): ref = py23_range(0, 1024, 4) ll = dllist(ref) @@ -1543,11 +1810,12 @@ def test_node_readonly_attributes(self): self.assertRaises(expected_error, setattr, ll, 'prev', None) self.assertRaises(expected_error, setattr, ll, 'next', None) - def test_list_hash(self): - self.assertEqual(hash(dllist()), hash(dllist())) - self.assertEqual(hash(dllist(py23_range(0, 1024, 4))), - hash(dllist(py23_range(0, 1024, 4)))) - self.assertEqual(hash(dllist([0, 2])), hash(dllist([0.0, 2.0]))) +# COMMENTED BECAUSE HASH DOES NOT WORK +# def test_list_hash(self): +# self.assertEqual(hash(dllist()), hash(dllist())) +# self.assertEqual(hash(dllist(py23_range(0, 1024, 4))), +# hash(dllist(py23_range(0, 1024, 4)))) +# self.assertEqual(hash(dllist([0, 2])), hash(dllist([0.0, 2.0]))) def suite(): diff --git a/tests/speed_test.py b/tests/speed_test.py old mode 100644 new mode 100755 index 521e48a..0e80193 --- a/tests/speed_test.py +++ b/tests/speed_test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from collections import deque -from llist import sllist, dllist +from cllist import sllist, dllist import time # import gc # gc.set_debug(gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_STATS) @@ -25,12 +25,8 @@ def pop(c): def popleft(c): - if isinstance(c, deque): - for i in range(num): - c.popleft() - else: - for i in range(num): - c.pop() + for i in range(num): + c.popleft() def remove(c): @@ -47,8 +43,7 @@ def remove(c): start = time.time() operation(c) elapsed = time.time() - start - print "Completed %s/%s in \t\t%.8f seconds:\t %.1f ops/sec" % ( - container.__name__, - operation.__name__, - elapsed, - num / elapsed) + + col1 = "Completed %s/%s in" %(container.__name__, operation.__name__) + col2 = "%.8f seconds: %.1f" %(elapsed, num / elapsed ) + print ( "%s%s %s%s ops/s" %(col1, (35 - len(col1)) * ' ', col2, (36 - len(col2)) * ' ') )