Index: Doc/lib/libcollections.tex =================================================================== --- Doc/lib/libcollections.tex (revision 54711) +++ Doc/lib/libcollections.tex (working copy) @@ -388,4 +388,24 @@ print EmployeeRecord(*tup) \end{verbatim} +\subsection{\function{counts} accumulator \label{counts-accumulator}} + +\begin{funcdesc}{counts}{iterable} + Returns a dict where each value in the \var{iterable} is mapped to the number of + times it appeared. + \versionadded{2.6} + + Example: + \begin{verbatim} +>>> items = 'acabbacba' +>>> item_counts = counts(items) +>>> for item in 'abcd': +... print item, item_counts[item] +... +a 4 +b 3 +c 2 +d 0 +\end{verbatim} + \end{funcdesc} Index: Lib/collections.py =================================================================== --- Lib/collections.py (revision 54711) +++ Lib/collections.py (working copy) @@ -49,7 +49,25 @@ return type(typename, (tuple,), m) +def counts(iterable): + """Returns a mapping from values to the number of times they appeared. + >>> items = 'acabbacba' + >>> item_counts = counts(items) + >>> for item in 'abcd': + ... print item, item_counts[item] + ... + a 4 + b 3 + c 2 + d 0 + """ + count_dict = defaultdict(int) + for item in iterable: + count_dict[item] += 1 + return count_dict + + if __name__ == '__main__': # verify that instances are pickable from cPickle import loads, dumps Index: Lib/test/test_collections.py =================================================================== --- Lib/test/test_collections.py (revision 54711) +++ Lib/test/test_collections.py (working copy) @@ -1,6 +1,6 @@ import unittest from test import test_support -from collections import NamedTuple +from collections import NamedTuple, counts class TestNamedTuple(unittest.TestCase): @@ -48,9 +48,32 @@ self.assertEqual(p.y, y) self.assertRaises(AttributeError, eval, 'p.z', locals()) +class TestCounts(unittest.TestCase): + def test_dict_equality(self): + tests = [ + ([], {}), + ([6.5], {6.5: 1}), + ('abcabaacb', dict(a=4, b=3, c=2)), + (range(10), dict.fromkeys(range(10), 1)), + ] + for iterable, expected in tests: + self.assertEqual(counts(iterable), expected) + self.assertEqual(counts(iter(iterable)), expected) + + def test_item_access(self): + d = counts([1, 42, 3, 1, 42, 42]) + self.assertEqual(d[0], 0) + self.assertEqual(d[1], 2) + self.assertEqual(d[2], 0) + d['foo'] += 1 + self.assertEqual(d['foo'], 1) + d['foo'] += 2 + self.assertEqual(d['foo'], 3) + + def test_main(verbose=None): - test_classes = [TestNamedTuple] + test_classes = [TestNamedTuple, TestCounts] test_support.run_unittest(*test_classes) if __name__ == "__main__":