Commit 9c01e441 authored by Raymond Hettinger's avatar Raymond Hettinger

Add a subtract() method to collections.Counter()

parent 03d788dc
...@@ -214,6 +214,17 @@ For example:: ...@@ -214,6 +214,17 @@ For example::
>>> Counter('abracadabra').most_common(3) >>> Counter('abracadabra').most_common(3)
[('a', 5), ('r', 2), ('b', 2)] [('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 The usual dictionary methods are available for :class:`Counter` objects
except for two which work differently for counters. except for two which work differently for counters.
......
...@@ -436,6 +436,34 @@ class Counter(dict): ...@@ -436,6 +436,34 @@ class Counter(dict):
if kwds: if kwds:
self.update(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): def copy(self):
'Like dict.copy() but returns a Counter instance instead of a dict.' 'Like dict.copy() but returns a Counter instance instead of a dict.'
return Counter(self) return Counter(self)
......
...@@ -661,6 +661,16 @@ class TestCounter(unittest.TestCase): ...@@ -661,6 +661,16 @@ class TestCounter(unittest.TestCase):
set_result = setop(set(p.elements()), set(q.elements())) set_result = setop(set(p.elements()), set(q.elements()))
self.assertEqual(counter_result, dict.fromkeys(set_result, 1)) 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): class TestOrderedDict(unittest.TestCase):
......
...@@ -301,6 +301,8 @@ C-API ...@@ -301,6 +301,8 @@ C-API
Library Library
------- -------
- Added a subtract() method to collections.Counter().
- Issue #8233: When run as a script, py_compile.py optionally takes a single - 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 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 is read on demand and the named file is compiled immediately. (Original
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment