]> granicus.if.org Git - python/commitdiff
Issue 10667: Fast path for collections.Counter
authorRaymond Hettinger <python@rcn.com>
Wed, 15 Dec 2010 16:30:37 +0000 (16:30 +0000)
committerRaymond Hettinger <python@rcn.com>
Wed, 15 Dec 2010 16:30:37 +0000 (16:30 +0000)
Lib/collections.py
Misc/NEWS
Modules/_collectionsmodule.c

index f05d7b4350256bf3be97dcfd14c7eb3e77780901..061106bd5ee9d2fcc85d35dc5a997ba8288e79b0 100644 (file)
@@ -334,6 +334,17 @@ def namedtuple(typename, field_names, verbose=False, rename=False):
 ###  Counter
 ########################################################################
 
+def _count_elements(mapping, iterable):
+    'Tally elements from the iterable.'
+    mapping_get = mapping.get
+    for elem in iterable:
+        mapping[elem] = mapping_get(elem, 0) + 1
+
+try:                                    # Load C helper function if available
+    from _collections import _count_elements
+except ImportError:
+    pass
+
 class Counter(dict):
     '''Dict subclass for counting hashable items.  Sometimes called a bag
     or multiset.  Elements are stored as dictionary keys and their counts
@@ -476,9 +487,7 @@ class Counter(dict):
                 else:
                     dict.update(self, iterable) # fast path when counter is empty
             else:
-                self_get = self.get
-                for elem in iterable:
-                    self[elem] = 1 + self_get(elem, 0)
+                _count_elements(self, iterable)
         if kwds:
             self.update(kwds)
 
index da7e9d7dc5f9adf629dc75f73b8ed82ba5419a71..6ba1652ddfbf2dff8f068c0976cdc697e2429b15 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -17,6 +17,8 @@ Core and Builtins
 Library
 -------
 
+- Issue #10667: Fast path for collections.Counter().
+
 - Issue #10695: passing the port as a string value to telnetlib no longer
   causes debug mode to fail.
 
index 2216fa64c8d514fd6ed1aa1e60ba68b3c17aaecf..684b8738c5df819240d99c1517db5034309e80f9 100644 (file)
@@ -1518,6 +1518,68 @@ static PyTypeObject defdict_type = {
     PyObject_GC_Del,                    /* tp_free */
 };
 
+/* helper function for Counter  *********************************************/
+
+PyDoc_STRVAR(_count_elements_doc,
+"_count_elements(mapping, iterable) -> None\n\
+\n\
+Count elements in the iterable, updating the mappping");
+
+static PyObject *
+_count_elements(PyObject *self, PyObject *args)
+{
+    PyObject *it, *iterable, *mapping, *oldval;
+    PyObject *newval = NULL;
+    PyObject *key = NULL;
+    PyObject *one = NULL;
+
+    if (!PyArg_UnpackTuple(args, "_count_elements", 2, 2, &mapping, &iterable))
+        return NULL;
+
+    if (!PyDict_Check(mapping)) {
+        PyErr_SetString(PyExc_TypeError,
+            "Expected mapping argument to be a dictionary");
+        return NULL;
+    }
+
+    it = PyObject_GetIter(iterable);
+    if (it == NULL)
+        return NULL;
+    one = PyLong_FromLong(1);
+    if (one == NULL) {
+        Py_DECREF(it);
+        return NULL;
+    }
+    while (1) {
+        key = PyIter_Next(it);
+        if (key == NULL) {
+            if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_StopIteration))
+                PyErr_Clear();
+            break;
+        }
+        oldval = PyDict_GetItem(mapping, key);
+        if (oldval == NULL) {
+            if (PyDict_SetItem(mapping, key, one) == -1)
+                break;
+        } else {
+            newval = PyNumber_Add(oldval, one);
+            if (newval == NULL)
+                break;
+            if (PyDict_SetItem(mapping, key, newval) == -1)
+                break;
+            Py_CLEAR(newval);
+        }
+        Py_DECREF(key);
+    }
+    Py_DECREF(it);
+    Py_XDECREF(key);
+    Py_XDECREF(newval);
+    Py_DECREF(one);
+    if (PyErr_Occurred())
+        return NULL;
+    Py_RETURN_NONE;
+}
+
 /* module level code ********************************************************/
 
 PyDoc_STRVAR(module_doc,
@@ -1526,13 +1588,17 @@ PyDoc_STRVAR(module_doc,
 - defaultdict:  dict subclass with a default value factory\n\
 ");
 
+static struct PyMethodDef module_functions[] = {
+    {"_count_elements", _count_elements,    METH_VARARGS,   _count_elements_doc},
+    {NULL,       NULL}          /* sentinel */
+};
 
 static struct PyModuleDef _collectionsmodule = {
     PyModuleDef_HEAD_INIT,
     "_collections",
     module_doc,
     -1,
-    NULL,
+    module_functions,
     NULL,
     NULL,
     NULL,