Commit 738ec90c authored by Raymond Hettinger's avatar Raymond Hettinger

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.
parent fe999276
...@@ -54,14 +54,24 @@ Deque objects support the following methods: ...@@ -54,14 +54,24 @@ Deque objects support the following methods:
reversing the order of elements in the iterable argument. reversing the order of elements in the iterable argument.
\end{methoddesc} \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}{} \begin{methoddesc}{pop}{}
Remove and return an element from the right side of the deque. 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} \end{methoddesc}
\begin{methoddesc}{popleft}{} \begin{methoddesc}{popleft}{}
Remove and return an element from the left side of the deque. 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} \end{methoddesc}
\begin{methoddesc}{rotate}{n} \begin{methoddesc}{rotate}{n}
...@@ -80,22 +90,27 @@ Example: ...@@ -80,22 +90,27 @@ Example:
>>> from collections import deque >>> from collections import deque
>>> d = deque('ghi') # make a new deque with three items >>> d = deque('ghi') # make a new deque with three items
>>> for elem in d: # iterate over the deque's elements >>> for elem in d: # iterate over the deque's elements
print elem.upper() ... print elem.upper()
G G
H H
I I
>>> d.append('j') # add a new entry to the right side >>> d.append('j') # add a new entry to the right side
>>> d.appendleft('f') # add a new entry to the left side >>> d.appendleft('f') # add a new entry to the left side
>>> d # show the representation of the deque >>> d # show the representation of the deque
deque(['f', 'g', 'h', 'i', 'j']) deque(['f', 'g', 'h', 'i', 'j'])
>>> d.pop() # return and remove the rightmost item >>> d.pop() # return and remove the rightmost item
'j' 'j'
>>> d.popleft() # return and remove the leftmost item >>> d.popleft() # return and remove the leftmost item
'f' 'f'
>>> list(d) # list the contents of the deque >>> list(d) # list the contents of the deque
['g', 'h', 'i'] ['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 >>> list(reversed(d)) # list the contents of a deque in reverse
['i', 'h', 'g'] ['i', 'h', 'g']
>>> 'h' in d # search the deque >>> 'h' in d # search the deque
...@@ -109,15 +124,15 @@ deque(['l', 'g', 'h', 'i', 'j', 'k']) ...@@ -109,15 +124,15 @@ deque(['l', 'g', 'h', 'i', 'j', 'k'])
>>> d.rotate(-1) # left rotation >>> d.rotate(-1) # left rotation
>>> d >>> d
deque(['g', 'h', 'i', 'j', 'k', 'l']) deque(['g', 'h', 'i', 'j', 'k', 'l'])
>>> deque(reversed(d)) # make a new deque in reverse order >>> deque(reversed(d)) # make a new deque in reverse order
deque(['l', 'k', 'j', 'i', 'h', 'g']) deque(['l', 'k', 'j', 'i', 'h', 'g'])
>>> d.clear() # empty the deque >>> d.clear() # empty the deque
>>> d.pop() # cannot pop from an empty deque >>> d.pop() # cannot pop from an empty deque
Traceback (most recent call last): Traceback (most recent call last):
File "<pyshell#6>", line 1, in -toplevel- File "<pyshell#6>", line 1, in -toplevel-
d.pop() d.pop()
LookupError: pop from an empty deque IndexError: pop from an empty deque
>>> d.extendleft('abc') # extendleft() reverses the input order >>> d.extendleft('abc') # extendleft() reverses the input order
>>> d >>> d
......
...@@ -262,11 +262,7 @@ class fifo: ...@@ -262,11 +262,7 @@ class fifo:
return self.list == [] return self.list == []
def first (self): def first (self):
it = iter(self.list) return self.list.left()
try:
return it.next()
except StopIteration:
raise IndexError
def push (self, data): def push (self, data):
self.list.append(data) self.list.append(data)
......
...@@ -28,6 +28,23 @@ class TestBasic(unittest.TestCase): ...@@ -28,6 +28,23 @@ class TestBasic(unittest.TestCase):
self.assertEqual(right, range(150, 400)) self.assertEqual(right, range(150, 400))
self.assertEqual(list(d), range(50, 150)) 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): def test_extend(self):
d = deque('a') d = deque('a')
self.assertRaises(TypeError, d.extend, 1) self.assertRaises(TypeError, d.extend, 1)
...@@ -40,6 +57,14 @@ class TestBasic(unittest.TestCase): ...@@ -40,6 +57,14 @@ class TestBasic(unittest.TestCase):
d.extendleft('bcd') d.extendleft('bcd')
self.assertEqual(list(d), list(reversed('abcd'))) 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): def test_rotate(self):
s = tuple('abcde') s = tuple('abcde')
n = len(s) n = len(s)
...@@ -93,7 +118,7 @@ class TestBasic(unittest.TestCase): ...@@ -93,7 +118,7 @@ class TestBasic(unittest.TestCase):
self.assertEqual(len(d), 1) self.assertEqual(len(d), 1)
d.pop() d.pop()
self.assertEqual(len(d), 0) self.assertEqual(len(d), 0)
self.assertRaises(LookupError, d.pop) self.assertRaises(IndexError, d.pop)
self.assertEqual(len(d), 0) self.assertEqual(len(d), 0)
d.append('c') d.append('c')
self.assertEqual(len(d), 1) self.assertEqual(len(d), 1)
...@@ -104,8 +129,8 @@ class TestBasic(unittest.TestCase): ...@@ -104,8 +129,8 @@ class TestBasic(unittest.TestCase):
def test_underflow(self): def test_underflow(self):
d = deque() d = deque()
self.assertRaises(LookupError, d.pop) self.assertRaises(IndexError, d.pop)
self.assertRaises(LookupError, d.popleft) self.assertRaises(IndexError, d.popleft)
def test_clear(self): def test_clear(self):
d = deque(xrange(100)) d = deque(xrange(100))
...@@ -374,6 +399,63 @@ class TestSubclass(unittest.TestCase): ...@@ -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 "<pyshell#6>", 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): def test_main(verbose=None):
import sys import sys
from test import test_sets from test import test_sets
...@@ -394,6 +476,10 @@ def test_main(verbose=None): ...@@ -394,6 +476,10 @@ def test_main(verbose=None):
gc.collect() gc.collect()
counts[i] = sys.gettotalrefcount() counts[i] = sys.gettotalrefcount()
print counts print counts
# doctests
from test import test_deque
test_support.run_doctest(test_deque, verbose)
if __name__ == "__main__": if __name__ == "__main__":
test_main(verbose=True) test_main(verbose=True)
...@@ -34,6 +34,8 @@ typedef struct { ...@@ -34,6 +34,8 @@ typedef struct {
int len; int len;
} dequeobject; } dequeobject;
PyTypeObject deque_type;
static PyObject * static PyObject *
deque_new(PyTypeObject *type, PyObject *args, PyObject *kwds) deque_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{ {
...@@ -109,7 +111,7 @@ deque_pop(dequeobject *deque, PyObject *unused) ...@@ -109,7 +111,7 @@ deque_pop(dequeobject *deque, PyObject *unused)
block *prevblock; block *prevblock;
if (deque->len == 0) { if (deque->len == 0) {
PyErr_SetString(PyExc_LookupError, "pop from an empty deque"); PyErr_SetString(PyExc_IndexError, "pop from an empty deque");
return NULL; return NULL;
} }
item = deque->rightblock->data[deque->rightindex]; item = deque->rightblock->data[deque->rightindex];
...@@ -144,7 +146,7 @@ deque_popleft(dequeobject *deque, PyObject *unused) ...@@ -144,7 +146,7 @@ deque_popleft(dequeobject *deque, PyObject *unused)
block *prevblock; block *prevblock;
if (deque->len == 0) { if (deque->len == 0) {
PyErr_SetString(PyExc_LookupError, "pop from an empty deque"); PyErr_SetString(PyExc_IndexError, "pop from an empty deque");
return NULL; return NULL;
} }
item = deque->leftblock->data[deque->leftindex]; item = deque->leftblock->data[deque->leftindex];
...@@ -174,6 +176,38 @@ deque_popleft(dequeobject *deque, PyObject *unused) ...@@ -174,6 +176,38 @@ deque_popleft(dequeobject *deque, PyObject *unused)
PyDoc_STRVAR(popleft_doc, "Remove and return the leftmost element."); 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 * static PyObject *
deque_extend(dequeobject *deque, PyObject *iterable) deque_extend(dequeobject *deque, PyObject *iterable)
{ {
...@@ -467,6 +501,82 @@ deque_tp_print(PyObject *deque, FILE *fp, int flags) ...@@ -467,6 +501,82 @@ deque_tp_print(PyObject *deque, FILE *fp, int flags)
return 0; 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 static int
deque_init(dequeobject *deque, PyObject *args, PyObject *kwds) deque_init(dequeobject *deque, PyObject *args, PyObject *kwds)
{ {
...@@ -509,6 +619,8 @@ static PyMethodDef deque_methods[] = { ...@@ -509,6 +619,8 @@ static PyMethodDef deque_methods[] = {
METH_O, extend_doc}, METH_O, extend_doc},
{"extendleft", (PyCFunction)deque_extendleft, {"extendleft", (PyCFunction)deque_extendleft,
METH_O, extendleft_doc}, METH_O, extendleft_doc},
{"left", (PyCFunction)deque_left,
METH_NOARGS, left_doc},
{"pop", (PyCFunction)deque_pop, {"pop", (PyCFunction)deque_pop,
METH_NOARGS, pop_doc}, METH_NOARGS, pop_doc},
{"popleft", (PyCFunction)deque_popleft, {"popleft", (PyCFunction)deque_popleft,
...@@ -517,6 +629,8 @@ static PyMethodDef deque_methods[] = { ...@@ -517,6 +629,8 @@ static PyMethodDef deque_methods[] = {
METH_NOARGS, reduce_doc}, METH_NOARGS, reduce_doc},
{"__reversed__", (PyCFunction)deque_reviter, {"__reversed__", (PyCFunction)deque_reviter,
METH_NOARGS, reversed_doc}, METH_NOARGS, reversed_doc},
{"right", (PyCFunction)deque_right,
METH_NOARGS, right_doc},
{"rotate", (PyCFunction)deque_rotate, {"rotate", (PyCFunction)deque_rotate,
METH_VARARGS, rotate_doc}, METH_VARARGS, rotate_doc},
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
...@@ -553,7 +667,7 @@ PyTypeObject deque_type = { ...@@ -553,7 +667,7 @@ PyTypeObject deque_type = {
deque_doc, /* tp_doc */ deque_doc, /* tp_doc */
(traverseproc)set_traverse, /* tp_traverse */ (traverseproc)set_traverse, /* tp_traverse */
(inquiry)deque_clear, /* tp_clear */ (inquiry)deque_clear, /* tp_clear */
0, /* tp_richcompare */ (richcmpfunc)deque_richcompare, /* tp_richcompare */
0, /* tp_weaklistoffset*/ 0, /* tp_weaklistoffset*/
(getiterfunc)deque_iter, /* tp_iter */ (getiterfunc)deque_iter, /* tp_iter */
0, /* tp_iternext */ 0, /* tp_iternext */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment