From 9c01e441bb2589853446787d4150fe623050a1e1 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 3 Apr 2010 10:32:58 +0000 Subject: Add a subtract() method to collections.Counter() --- Doc/library/collections.rst | 11 +++++++++++ Lib/collections.py | 28 ++++++++++++++++++++++++++++ Lib/test/test_collections.py | 10 ++++++++++ Misc/NEWS | 2 ++ 4 files changed, 51 insertions(+) diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 176975b..aa17a55 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -214,6 +214,17 @@ For example:: >>> Counter('abracadabra').most_common(3) [('a', 5), ('r', 2), ('b', 2)] + .. method:: subtract([iterable-or-mapping]) + + Elements are subtracted from an *iterable* or from another *mapping* + (or counter). Like :meth:`dict.update` but subtracts counts instead + of replacing them. Both inputs and outputs may be zero or negative. + + >>> c = Counter(a=4, b=2, c=0, d=-2) + >>> d = Counter(a=1, b=2, c=3, d=4) + >>> c.subtract(d) + Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6}) + The usual dictionary methods are available for :class:`Counter` objects except for two which work differently for counters. diff --git a/Lib/collections.py b/Lib/collections.py index 6ec062f..53e78f9 100644 --- a/Lib/collections.py +++ b/Lib/collections.py @@ -436,6 +436,34 @@ class Counter(dict): if kwds: self.update(kwds) + def subtract(self, iterable=None, **kwds): + '''Like dict.update() but subtracts counts instead of replacing them. + Counts can be reduced below zero. Both the inputs and outputs are + allowed to contain zero and negative counts. + + Source can be an iterable, a dictionary, or another Counter instance. + + >>> c = Counter('which') + >>> c.subtract('witch') # subtract elements from another iterable + >>> c.subtract(Counter('watch')) # subtract elements from another counter + >>> c['h'] # 2 in which, minus 1 in witch, minus 1 in watch + 0 + >>> c['w'] # 1 in which, minus 1 in witch, minus 1 in watch + -1 + + ''' + if iterable is not None: + if isinstance(iterable, Mapping): + self_get = self.get + for elem, count in iterable.items(): + self[elem] = self_get(elem, 0) - count + else: + self_get = self.get + for elem in iterable: + self[elem] = self_get(elem, 0) - 1 + if kwds: + self.subtract(kwds) + def copy(self): 'Like dict.copy() but returns a Counter instance instead of a dict.' return Counter(self) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 797b350..1bd49d9 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -661,6 +661,16 @@ class TestCounter(unittest.TestCase): set_result = setop(set(p.elements()), set(q.elements())) self.assertEqual(counter_result, dict.fromkeys(set_result, 1)) + def test_subtract(self): + c = Counter(a=-5, b=0, c=5, d=10, e=15,g=40) + c.subtract(a=1, b=2, c=-3, d=10, e=20, f=30, h=-50) + self.assertEqual(c, Counter(a=-6, b=-2, c=8, d=0, e=-5, f=-30, g=40, h=50)) + c = Counter(a=-5, b=0, c=5, d=10, e=15,g=40) + c.subtract(Counter(a=1, b=2, c=-3, d=10, e=20, f=30, h=-50)) + self.assertEqual(c, Counter(a=-6, b=-2, c=8, d=0, e=-5, f=-30, g=40, h=50)) + c = Counter('aaabbcd') + c.subtract('aaaabbcce') + self.assertEqual(c, Counter(a=-1, b=0, c=-1, d=1, e=-1)) class TestOrderedDict(unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS index 164655a..561f492 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -301,6 +301,8 @@ C-API Library ------- +- Added a subtract() method to collections.Counter(). + - Issue #8233: When run as a script, py_compile.py optionally takes a single argument `-` which tells it to read files to compile from stdin. Each line is read on demand and the named file is compiled immediately. (Original -- cgit v0.12