From 96f3410ebeaf970d7f214d1be1527d8e7c1cc03b Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 15 Dec 2010 16:30:37 +0000 Subject: [PATCH] Issue 10667: Fast path for collections.Counter --- Lib/collections.py | 15 ++++++-- Misc/NEWS | 2 ++ Modules/_collectionsmodule.c | 68 +++++++++++++++++++++++++++++++++++- 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/Lib/collections.py b/Lib/collections.py index f05d7b43502..061106bd5ee 100644 --- a/Lib/collections.py +++ b/Lib/collections.py @@ -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) diff --git a/Misc/NEWS b/Misc/NEWS index da7e9d7dc5f..6ba1652ddfb 100644 --- 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. diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index 2216fa64c8d..684b8738c5d 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -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,