Commit e0d1b9f1 authored by Raymond Hettinger's avatar Raymond Hettinger

Simplify explanation of multiset operations by removing restrictions on negative inputs.

parent 63b3a97a
...@@ -253,16 +253,18 @@ Common patterns for working with :class:`Counter` objects:: ...@@ -253,16 +253,18 @@ Common patterns for working with :class:`Counter` objects::
c.items() # convert to a list of (elem, cnt) pairs c.items() # convert to a list of (elem, cnt) pairs
Counter(dict(list_of_pairs)) # convert from a list of (elem, cnt) pairs Counter(dict(list_of_pairs)) # convert from a list of (elem, cnt) pairs
c.most_common()[:-n:-1] # n least common elements c.most_common()[:-n:-1] # n least common elements
c += Counter() # remove zero and negative counts
Several multiset mathematical operations are provided for combining Several multiset mathematical operations are provided for combining
:class:`Counter` objects. Multisets are like regular sets but allowed to :class:`Counter` objects. Multisets are like regular sets but are allowed to
contain repeated elements (with counts of one or more). Addition and contain repeated elements (with counts of one or more). Addition and
subtraction combine counters by adding or subtracting the counts of subtraction combine counters by adding or subtracting the counts of
corresponding elements. Intersection and union return the minimum and maximum corresponding elements. Intersection and union return the minimum and maximum
of corresponding counts:: of corresponding counts. All four multiset operations exclude results with
zero or negative counts::
>>> c = Counter({'a': 3, 'b': 1}) >>> c = Counter(a=3, b=1)
>>> d = Counter({'a': 1, 'b': 2}) >>> d = Counter(a=1, b=2)
>>> c + d # add two counters together: c[x] + d[x] >>> c + d # add two counters together: c[x] + d[x]
Counter({'a': 4, 'b': 3}) Counter({'a': 4, 'b': 3})
>>> c - d # subtract (keeping only positive counts) >>> c - d # subtract (keeping only positive counts)
...@@ -272,16 +274,6 @@ of corresponding counts:: ...@@ -272,16 +274,6 @@ of corresponding counts::
>>> c | d # union: max(c[x], d[x]) >>> c | d # union: max(c[x], d[x])
Counter({'a': 3, 'b': 2}) Counter({'a': 3, 'b': 2})
All four multiset operations produce only positive counts (negative and zero
results are skipped). If inputs include negative counts, addition will sum
both counts and then exclude non-positive results. The other three operations
are undefined for negative inputs::
>>> e = Counter(a=8, b=-2, c=0)
>>> e += Counter() # remove zero and negative counts
>>> e
Counter({'a': 8})
.. seealso:: .. seealso::
* `Bag class <http://www.gnu.org/software/smalltalk/manual-base/html_node/Bag.html>`_ * `Bag class <http://www.gnu.org/software/smalltalk/manual-base/html_node/Bag.html>`_
......
...@@ -314,8 +314,8 @@ class Counter(dict): ...@@ -314,8 +314,8 @@ class Counter(dict):
if not isinstance(other, Counter): if not isinstance(other, Counter):
return NotImplemented return NotImplemented
result = Counter() result = Counter()
for elem, count in self.items(): for elem in set(self) | set(other):
newcount = count - other[elem] newcount = self[elem] - other[elem]
if newcount > 0: if newcount > 0:
result[elem] = newcount result[elem] = newcount
return result return result
......
...@@ -462,18 +462,19 @@ class TestCounter(unittest.TestCase): ...@@ -462,18 +462,19 @@ class TestCounter(unittest.TestCase):
for i in range(1000): for i in range(1000):
# test random pairs of multisets # test random pairs of multisets
p = Counter(dict((elem, randrange(-2,4)) for elem in elements)) p = Counter(dict((elem, randrange(-2,4)) for elem in elements))
p.update(e=1, f=-1, g=0)
q = Counter(dict((elem, randrange(-2,4)) for elem in elements)) q = Counter(dict((elem, randrange(-2,4)) for elem in elements))
for counterop, numberop, defneg in [ q.update(h=1, i=-1, j=0)
(Counter.__add__, lambda x, y: x+y if x+y>0 else 0, True), for counterop, numberop in [
(Counter.__sub__, lambda x, y: x-y if x-y>0 else 0, False), (Counter.__add__, lambda x, y: max(0, x+y)),
(Counter.__or__, max, False), (Counter.__sub__, lambda x, y: max(0, x-y)),
(Counter.__and__, min, False), (Counter.__or__, lambda x, y: max(0,x,y)),
(Counter.__and__, lambda x, y: max(0, min(x,y))),
]: ]:
result = counterop(p, q) result = counterop(p, q)
for x in elements: for x in elements:
# all except __add__ are undefined for negative inputs self.assertEqual(numberop(p[x], q[x]), result[x],
if defneg or (p[x] >= 0 and q[x] >= 0): (counterop, x, p, q))
self.assertEqual(numberop(p[x], q[x]), result[x])
# verify that results exclude non-positive counts # verify that results exclude non-positive counts
self.assert_(x>0 for x in result.values()) self.assert_(x>0 for x in result.values())
......
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