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

This commit is contained in:
Raymond Hettinger 2009-01-21 20:36:27 +00:00
parent 63b3a97a2a
commit e0d1b9f11f
3 changed files with 17 additions and 24 deletions

View File

@ -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::
>>> 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>`_

View File

@ -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

View File

@ -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())