]> granicus.if.org Git - python/commitdiff
Improvements to collections.deque():
authorRaymond Hettinger <python@rcn.com>
Sun, 29 Feb 2004 02:15:56 +0000 (02:15 +0000)
committerRaymond Hettinger <python@rcn.com>
Sun, 29 Feb 2004 02:15:56 +0000 (02:15 +0000)
* 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
Lib/asynchat.py
Lib/test/test_deque.py
Modules/collectionsmodule.c

index 0378ea5882ddeaa9d8b6b4e86bc0e439ffb6b003..279309578904c7115e460af2d6168cc8f35695e1 100644 (file)
@@ -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 "<pyshell#6>", 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
index 4bfab302a1cb12f973fa05b6ec400999f5f0ff53..2c0bc3e90221be18bcc84ae0494be1aac10a0249 100644 (file)
@@ -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)
index f3bc59f8ce7a28f88d3f93e8d60c7ea435649545..db9733e8eee80f78f1fff5f92f22bc3de95911e5 100644 (file)
@@ -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 "<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):
     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)
index d534aa9f699ab490b71b000e6394701d61dca265..2b5cb105f05f535e743cd5eb7daa152392c60628 100644 (file)
@@ -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];
@@ -174,6 +176,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)
 {
@@ -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 */