From 738ec90ca14154493a88fb38728537837a45eebf Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 29 Feb 2004 02:15:56 +0000 Subject: Improvements to collections.deque(): * Add doctests for the examples in the library reference. * Add two methods, left() and right(), modeled after deques in C++ STL. * Apply the new method to asynchat.py. * Add comparison operators to make deques more substitutable for lists. * Replace the LookupErrors with IndexErrors to more closely match lists. --- Doc/lib/libcollections.tex | 29 ++++++++--- Lib/asynchat.py | 6 +-- Lib/test/test_deque.py | 92 +++++++++++++++++++++++++++++++-- Modules/collectionsmodule.c | 120 ++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 229 insertions(+), 18 deletions(-) diff --git a/Doc/lib/libcollections.tex b/Doc/lib/libcollections.tex index 0378ea5..2793095 100644 --- a/Doc/lib/libcollections.tex +++ b/Doc/lib/libcollections.tex @@ -54,14 +54,24 @@ Deque objects support the following methods: reversing the order of elements in the iterable argument. \end{methoddesc} +\begin{methoddesc}{left}{} + Return leftmost element from the deque. + If no elements are present, raises a \exception{IndexError}. +\end{methoddesc} + \begin{methoddesc}{pop}{} Remove and return an element from the right side of the deque. - If no elements are present, raises a \exception{LookupError}. + If no elements are present, raises a \exception{IndexError}. \end{methoddesc} \begin{methoddesc}{popleft}{} Remove and return an element from the left side of the deque. - If no elements are present, raises a \exception{LookupError}. + If no elements are present, raises a \exception{IndexError}. +\end{methoddesc} + +\begin{methoddesc}{right}{} + Return the rightmost element from the deque. + If no elements are present, raises a \exception{IndexError}. \end{methoddesc} \begin{methoddesc}{rotate}{n} @@ -80,22 +90,27 @@ Example: >>> from collections import deque >>> d = deque('ghi') # make a new deque with three items >>> for elem in d: # iterate over the deque's elements - print elem.upper() - - +... print elem.upper() G H I + >>> d.append('j') # add a new entry to the right side >>> d.appendleft('f') # add a new entry to the left side >>> d # show the representation of the deque deque(['f', 'g', 'h', 'i', 'j']) + >>> d.pop() # return and remove the rightmost item 'j' >>> d.popleft() # return and remove the leftmost item 'f' >>> list(d) # list the contents of the deque ['g', 'h', 'i'] + +>>> d.left() # peek at leftmost item +'g' +>>> d.right() # peek at rightmost item +'i' >>> list(reversed(d)) # list the contents of a deque in reverse ['i', 'h', 'g'] >>> 'h' in d # search the deque @@ -109,15 +124,15 @@ deque(['l', 'g', 'h', 'i', 'j', 'k']) >>> d.rotate(-1) # left rotation >>> d deque(['g', 'h', 'i', 'j', 'k', 'l']) + >>> deque(reversed(d)) # make a new deque in reverse order deque(['l', 'k', 'j', 'i', 'h', 'g']) >>> d.clear() # empty the deque >>> d.pop() # cannot pop from an empty deque - Traceback (most recent call last): File "", line 1, in -toplevel- d.pop() -LookupError: pop from an empty deque +IndexError: pop from an empty deque >>> d.extendleft('abc') # extendleft() reverses the input order >>> d diff --git a/Lib/asynchat.py b/Lib/asynchat.py index 4bfab30..2c0bc3e 100644 --- a/Lib/asynchat.py +++ b/Lib/asynchat.py @@ -262,11 +262,7 @@ class fifo: return self.list == [] def first (self): - it = iter(self.list) - try: - return it.next() - except StopIteration: - raise IndexError + return self.list.left() def push (self, data): self.list.append(data) diff --git a/Lib/test/test_deque.py b/Lib/test/test_deque.py index f3bc59f..db9733e 100644 --- a/Lib/test/test_deque.py +++ b/Lib/test/test_deque.py @@ -28,6 +28,23 @@ class TestBasic(unittest.TestCase): self.assertEqual(right, range(150, 400)) self.assertEqual(list(d), range(50, 150)) + def test_comparisons(self): + d = deque('xabc'); d.popleft() + for e in [d, deque('abc'), deque('ab'), deque(), list(d)]: + self.assertEqual(d==e, type(d)==type(e) and list(d)==list(e)) + self.assertEqual(d!=e, not(type(d)==type(e) and list(d)==list(e))) + + args = map(deque, ('', 'a', 'b', 'ab', 'ba', 'abc', 'xba', 'xabc', 'cba')) + for x in args: + for y in args: + self.assertEqual(x == y, list(x) == list(y), (x,y)) + self.assertEqual(x != y, list(x) != list(y), (x,y)) + self.assertEqual(x < y, list(x) < list(y), (x,y)) + self.assertEqual(x <= y, list(x) <= list(y), (x,y)) + self.assertEqual(x > y, list(x) > list(y), (x,y)) + self.assertEqual(x >= y, list(x) >= list(y), (x,y)) + self.assertEqual(cmp(x,y), cmp(list(x),list(y)), (x,y)) + def test_extend(self): d = deque('a') self.assertRaises(TypeError, d.extend, 1) @@ -40,6 +57,14 @@ class TestBasic(unittest.TestCase): d.extendleft('bcd') self.assertEqual(list(d), list(reversed('abcd'))) + def test_leftright(self): + d = deque('superman') + self.assertEqual(d.left(), 's') + self.assertEqual(d.right(), 'n') + d = deque() + self.assertRaises(IndexError, d.left) + self.assertRaises(IndexError, d.right) + def test_rotate(self): s = tuple('abcde') n = len(s) @@ -93,7 +118,7 @@ class TestBasic(unittest.TestCase): self.assertEqual(len(d), 1) d.pop() self.assertEqual(len(d), 0) - self.assertRaises(LookupError, d.pop) + self.assertRaises(IndexError, d.pop) self.assertEqual(len(d), 0) d.append('c') self.assertEqual(len(d), 1) @@ -104,8 +129,8 @@ class TestBasic(unittest.TestCase): def test_underflow(self): d = deque() - self.assertRaises(LookupError, d.pop) - self.assertRaises(LookupError, d.popleft) + self.assertRaises(IndexError, d.pop) + self.assertRaises(IndexError, d.popleft) def test_clear(self): d = deque(xrange(100)) @@ -374,6 +399,63 @@ class TestSubclass(unittest.TestCase): #============================================================================== +libreftest = """ +Example from the Library Reference: Doc/lib/libcollections.tex + +>>> from collections import deque +>>> d = deque('ghi') # make a new deque with three items +>>> for elem in d: # iterate over the deque's elements +... print elem.upper() +G +H +I +>>> d.append('j') # add a new entry to the right side +>>> d.appendleft('f') # add a new entry to the left side +>>> d # show the representation of the deque +deque(['f', 'g', 'h', 'i', 'j']) +>>> d.pop() # return and remove the rightmost item +'j' +>>> d.popleft() # return and remove the leftmost item +'f' +>>> list(d) # list the contents of the deque +['g', 'h', 'i'] +>>> d.left() # peek at leftmost item +'g' +>>> d.right() # peek at rightmost item +'i' +>>> list(reversed(d)) # list the contents of a deque in reverse +['i', 'h', 'g'] +>>> 'h' in d # search the deque +True +>>> d.extend('jkl') # add multiple elements at once +>>> d +deque(['g', 'h', 'i', 'j', 'k', 'l']) +>>> d.rotate(1) # right rotation +>>> d +deque(['l', 'g', 'h', 'i', 'j', 'k']) +>>> d.rotate(-1) # left rotation +>>> d +deque(['g', 'h', 'i', 'j', 'k', 'l']) +>>> deque(reversed(d)) # make a new deque in reverse order +deque(['l', 'k', 'j', 'i', 'h', 'g']) +>>> d.clear() # empty the deque +>>> d.pop() # cannot pop from an empty deque +Traceback (most recent call last): + File "", line 1, in -toplevel- + d.pop() +IndexError: pop from an empty deque + +>>> d.extendleft('abc') # extendleft() reverses the input order +>>> d +deque(['c', 'b', 'a']) + +""" + + +#============================================================================== + +__test__ = {'libreftest' : libreftest} + def test_main(verbose=None): import sys from test import test_sets @@ -394,6 +476,10 @@ def test_main(verbose=None): gc.collect() counts[i] = sys.gettotalrefcount() print counts + + # doctests + from test import test_deque + test_support.run_doctest(test_deque, verbose) if __name__ == "__main__": test_main(verbose=True) diff --git a/Modules/collectionsmodule.c b/Modules/collectionsmodule.c index d534aa9..2b5cb10 100644 --- a/Modules/collectionsmodule.c +++ b/Modules/collectionsmodule.c @@ -34,6 +34,8 @@ typedef struct { int len; } dequeobject; +PyTypeObject deque_type; + static PyObject * deque_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { @@ -109,7 +111,7 @@ deque_pop(dequeobject *deque, PyObject *unused) block *prevblock; if (deque->len == 0) { - PyErr_SetString(PyExc_LookupError, "pop from an empty deque"); + PyErr_SetString(PyExc_IndexError, "pop from an empty deque"); return NULL; } item = deque->rightblock->data[deque->rightindex]; @@ -144,7 +146,7 @@ deque_popleft(dequeobject *deque, PyObject *unused) block *prevblock; if (deque->len == 0) { - PyErr_SetString(PyExc_LookupError, "pop from an empty deque"); + PyErr_SetString(PyExc_IndexError, "pop from an empty deque"); return NULL; } item = deque->leftblock->data[deque->leftindex]; @@ -175,6 +177,38 @@ deque_popleft(dequeobject *deque, PyObject *unused) PyDoc_STRVAR(popleft_doc, "Remove and return the leftmost element."); static PyObject * +deque_right(dequeobject *deque, PyObject *unused) +{ + PyObject *item; + + if (deque->len == 0) { + PyErr_SetString(PyExc_IndexError, "deque is empty"); + return NULL; + } + item = deque->rightblock->data[deque->rightindex]; + Py_INCREF(item); + return item; +} + +PyDoc_STRVAR(right_doc, "Return the rightmost element."); + +static PyObject * +deque_left(dequeobject *deque, PyObject *unused) +{ + PyObject *item; + + if (deque->len == 0) { + PyErr_SetString(PyExc_IndexError, "deque is empty"); + return NULL; + } + item = deque->leftblock->data[deque->leftindex]; + Py_INCREF(item); + return item; +} + +PyDoc_STRVAR(left_doc, "Return the leftmost element."); + +static PyObject * deque_extend(dequeobject *deque, PyObject *iterable) { PyObject *it, *item; @@ -467,6 +501,82 @@ deque_tp_print(PyObject *deque, FILE *fp, int flags) return 0; } +static PyObject * +deque_richcompare(PyObject *v, PyObject *w, int op) +{ + PyObject *it1=NULL, *it2=NULL, *x, *y; + int i, b, vs, ws, minlen, cmp=-1; + + if (v->ob_type != &deque_type || w->ob_type != &deque_type) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + /* Shortcuts */ + vs = ((dequeobject *)v)->len; + ws = ((dequeobject *)w)->len; + if (op == Py_EQ) { + if (v == w) + Py_RETURN_TRUE; + if (vs != ws) + Py_RETURN_FALSE; + } + if (op == Py_NE) { + if (v == w) + Py_RETURN_FALSE; + if (vs != ws) + Py_RETURN_TRUE; + } + + /* Search for the first index where items are different */ + it1 = PyObject_GetIter(v); + if (it1 == NULL) + goto done; + it2 = PyObject_GetIter(w); + if (it2 == NULL) + goto done; + minlen = (vs < ws) ? vs : ws; + for (i=0 ; i < minlen ; i++) { + x = PyIter_Next(it1); + if (x == NULL) + goto done; + y = PyIter_Next(it2); + if (y == NULL) { + Py_DECREF(x); + goto done; + } + b = PyObject_RichCompareBool(x, y, Py_EQ); + if (b == 0) { + cmp = PyObject_RichCompareBool(x, y, op); + Py_DECREF(x); + Py_DECREF(y); + goto done; + } + Py_DECREF(x); + Py_DECREF(y); + if (b == -1) + goto done; + } + /* Elements are equal through minlen. The longest input is the greatest */ + switch (op) { + case Py_LT: cmp = vs < ws; break; + case Py_LE: cmp = vs <= ws; break; + case Py_EQ: cmp = vs == ws; break; + case Py_NE: cmp = vs != ws; break; + case Py_GT: cmp = vs > ws; break; + case Py_GE: cmp = vs >= ws; break; + } + +done: + Py_XDECREF(it1); + Py_XDECREF(it2); + if (cmp == 1) + Py_RETURN_TRUE; + if (cmp == 0) + Py_RETURN_FALSE; + return NULL; +} + static int deque_init(dequeobject *deque, PyObject *args, PyObject *kwds) { @@ -509,6 +619,8 @@ static PyMethodDef deque_methods[] = { METH_O, extend_doc}, {"extendleft", (PyCFunction)deque_extendleft, METH_O, extendleft_doc}, + {"left", (PyCFunction)deque_left, + METH_NOARGS, left_doc}, {"pop", (PyCFunction)deque_pop, METH_NOARGS, pop_doc}, {"popleft", (PyCFunction)deque_popleft, @@ -517,6 +629,8 @@ static PyMethodDef deque_methods[] = { METH_NOARGS, reduce_doc}, {"__reversed__", (PyCFunction)deque_reviter, METH_NOARGS, reversed_doc}, + {"right", (PyCFunction)deque_right, + METH_NOARGS, right_doc}, {"rotate", (PyCFunction)deque_rotate, METH_VARARGS, rotate_doc}, {NULL, NULL} /* sentinel */ @@ -553,7 +667,7 @@ PyTypeObject deque_type = { deque_doc, /* tp_doc */ (traverseproc)set_traverse, /* tp_traverse */ (inquiry)deque_clear, /* tp_clear */ - 0, /* tp_richcompare */ + (richcmpfunc)deque_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset*/ (getiterfunc)deque_iter, /* tp_iter */ 0, /* tp_iternext */ -- cgit v0.12