[Python-checkins] r68562 - in python/trunk: Doc/library/collections.rst Lib/collections.py Lib/test/test_collections.py

raymond.hettinger python-checkins at python.org
Tue Jan 13 02:05:04 CET 2009


Author: raymond.hettinger
Date: Tue Jan 13 02:05:03 2009
New Revision: 68562

Log:
Simplify Counter() API.  Replace items keyword argument
with a mapping.  Makes Counter() idempotent, makes update()
API the same as Counter.__init__(), makes a more readable
repr, makes the API more dict-like, and allows Steven
Bethard's update() example to work.



Modified:
   python/trunk/Doc/library/collections.rst
   python/trunk/Lib/collections.py
   python/trunk/Lib/test/test_collections.py

Modified: python/trunk/Doc/library/collections.rst
==============================================================================
--- python/trunk/Doc/library/collections.rst	(original)
+++ python/trunk/Doc/library/collections.rst	Tue Jan 13 02:05:03 2009
@@ -161,12 +161,12 @@
 For example::
 
     # Tally repeated words in a list
-    >>> words = ['red', 'blue', 'red', 'green', 'blue', blue']
+    >>> words = ['red', 'blue', 'red', 'green', 'blue', 'blue']
     >>> cnt = Counter()
     >>> for word in words:
     ...     cnt[word] += 1
     >>> cnt
-    Counter(items=[('blue', 3), ('red', 2), ('green', 1)])
+    Counter({'blue': 3, 'red': 2, 'green': 1})
 
     # Find the ten most common words in Hamlet
     >>> import re
@@ -175,21 +175,20 @@
     [('the', 1143), ('and', 966), ('to', 762), ('of', 669), ('i', 631),
      ('you', 554),  ('a', 546), ('my', 514), ('hamlet', 471), ('in', 451)]
 
-.. class:: Counter([iterable[, items]])
+.. class:: Counter([iterable])
 
    A :class:`Counter` is a :class:`dict` subclass for counting hashable items.
-   Elements are stored as dictionary keys and their counts are stored as
-   dictionary values.  Counts are allowed to be any integer value including
-   zero or negative counts.  The :class:`Counter` class is similar to bags
-   or multisets in other languages.
+   It is an unordered collection where elements are stored as dictionary keys
+   and their counts are stored as dictionary values.  Counts are allowed to be
+   any integer value including zero or negative counts.  The :class:`Counter`
+   class is similar to bags or multisets in other languages.
 
    Elements are counted from the *iterable* if given.  Also, the counts
-   can be initialized from an *items* list of *(element, count)* pairs.
-   If provided, *items* must be a keyword argument::
+   can be initialized from another mapping of elements to their counts::
 
        >>> c = Counter()                            # a new, empty counter
        >>> c = Counter('gallahad')                  # a new counter from an iterable
-       >>> c = Counter(items=[('a', 4), ('b', 2)])  # a new counter from an items list
+       >>> c = Counter({'a': 4, 'b': 2})            # a new counter from a mapping
 
    The returned object has a dictionary style interface except that it returns
    a zero count for missing items (instead of raising a :exc:`KeyError` like a
@@ -222,7 +221,7 @@
       Elements are returned in arbitrary order.  If an element's count has been
       set to zero or a negative number, :meth:`elements` will ignore it.
 
-            >>> c = Counter(items=[('a', 4), ('b', 2), ('d', 0), ('e', -2)])
+            >>> c = Counter({'a': 4, 'b': 2, 'd': 0, 'e': -2})
             >>> list(c.elements())
             ['a', 'a', 'a', 'a', 'b', 'b']
 
@@ -245,19 +244,20 @@
        There is no equivalent class method for :class:`Counter` objects.
        Raises a :exc:`NotImplementedError` when called.
 
-   .. method:: update(mapping)
+   .. method:: update(iterable)
 
        Like :meth:`dict.update` but adds-in counts instead of replacing them.
-       Used for combining two independent counts.  Accepts a *mapping* object
-       which can be another counter or can be a :class:`dict` that maps
-       elements to element counts::
+
+       Elements are counted from the *iterable* if given.  Also, the counts
+       can be taken from another counter or mapping of elements to their
+       counts::
 
             >>> c = Counter('which')        # count letters in a word
             >>> d = Counter('witch')        # count letters in another word
             >>> c.update(d)                 # add counts from d to those in c
             >>> c['h']                      # count of 'h' is now three
             3
-            >>> c.update(Counter('watch'))  # add in letters from another word
+            >>> c.update('watch')           # add in letters from another word
             >>> c['h']                      # count of 'h' is now four
             4
 

Modified: python/trunk/Lib/collections.py
==============================================================================
--- python/trunk/Lib/collections.py	(original)
+++ python/trunk/Lib/collections.py	Tue Jan 13 02:05:03 2009
@@ -167,21 +167,17 @@
     #   http://code.activestate.com/recipes/259174/
     #   Knuth, TAOCP Vol. II section 4.6.3
 
-    def __init__(self, iterable=None, items=None):
+    def __init__(self, iterable=None):
         '''Create a new, empty Counter object.  And if given, count elements
-        from an input iterable.  Or, initialize the count from an items list
-        of (element, count) pairs.
+        from an input iterable.  Or, initialize the count from another mapping
+        of elements to their counts.
 
-        >>> c = Counter('hocus pocus')              # count elements in an iterable
-        >>> c = Counter(items=[('a', 4), ('b', 2)]) # take counts from an items list
+        >>> c = Counter()                           # a new, empty counter
+        >>> c = Counter('hocus pocus')              # a new counter from an iterable
+        >>> c = Counter({'a': 4, 'b': 2})           # a new counter from a mapping
 
         '''
-        if iterable is not None:
-            for elem in iterable:
-                self[elem] += 1
-        if items is not None:
-            for elem, count in items:
-                self[elem] += count
+        self.update(iterable)
 
     def __missing__(self, key):
         'The count of elements not in the Counter is zero.'
@@ -210,7 +206,7 @@
 
         # Knuth's example of prime factors of 1836:  2**2 * 3**3 * 17**1
         >>> import operator
-        >>> prime_factors = Counter(items=[(2,2), (3,3), (17,1)])
+        >>> prime_factors = Counter(dict([(2,2), (3,3), (17,1)]))
         >>> sorted(prime_factors.elements())         # list individual factors
         [2, 2, 3, 3, 3, 17]
         >>> reduce(operator.mul, prime_factors.elements(), 1)  # multiply them
@@ -234,16 +230,19 @@
         raise NotImplementedError(
             'Counter.fromkeys() is undefined.  Use Counter(iterable) instead.')
 
-    def update(self, mapping):
+    def update(self, iterable=None):
         '''Like dict.update() but add counts instead of replacing them.
 
-        Source can be another dictionary or a Counter.instance().
+        Source can be an iterable, a dictionary, or another Counter.instance().
 
         >>> c = Counter('which')
         >>> d = Counter('witch')
-        >>> c.update(d)                 # Add counts from d to those in c
-        >>> c['h']                      # Count of 'h' is now three
+        >>> c.update(d)                 # add counts from d to those in c
+        >>> c['h']                      # count of 'h' is now three
         3
+        >>> c.update('watch')
+        >>> c['h']
+        4
 
         '''
         # The regular dict.update() operation makes no sense here because the
@@ -254,19 +253,24 @@
         # multisets and implement the union-add operation discussed in
         # TAOCP Volume II section 4.6.3 exercise 19.  The Wikipedia entry for
         # multisets calls that operation a sum or join.
-        for elem, count in mapping.iteritems():
-            self[elem] += count
+
+        if iterable is not None:
+            if isinstance(iterable, Mapping):
+                for elem, count in iterable.iteritems():
+                    self[elem] += count
+            else:
+                for elem in iterable:
+                    self[elem] += 1
 
     def copy(self):
         'Like dict.copy() but returns a Counter instance instead of a dict.'
-        c = Counter()
-        c.update(self)
-        return c
+        return Counter(self)
 
     def __repr__(self):
         if not self:
             return '%s()' % self.__class__.__name__
-        return '%s(items=%r)' % (self.__class__.__name__, self.most_common())
+        items = ', '.join('%r: %r' % item for item in self.most_common())
+        return '%s({%s})' % (self.__class__.__name__, items)
 
 
 

Modified: python/trunk/Lib/test/test_collections.py
==============================================================================
--- python/trunk/Lib/test/test_collections.py	(original)
+++ python/trunk/Lib/test/test_collections.py	Tue Jan 13 02:05:03 2009
@@ -370,8 +370,7 @@
         self.assertEqual(c.get('b', 10), 2)
         self.assertEqual(c.get('z', 10), 10)
         self.assertEqual(c, dict(a=3, b=2, c=1))
-        self.assertEqual(repr(c),
-                         "Counter(items=[('a', 3), ('b', 2), ('c', 1)])")
+        self.assertEqual(repr(c), "Counter({'a': 3, 'b': 2, 'c': 1})")
         self.assertEqual(c.most_common(), [('a', 3), ('b', 2), ('c', 1)])
         for i in range(5):
             self.assertEqual(c.most_common(i),
@@ -396,8 +395,8 @@
         self.assertRaises(NotImplementedError, Counter.fromkeys, 'abc')
         self.assertRaises(TypeError, hash, c)
         c.update(dict(a=5, b=3, c=1))
-        c.update(Counter(items=[('a', 50), ('b', 30)]))
-        c.__init__(items=[('a', 500), ('b', 300)])
+        c.update(Counter('a' * 50 + 'b' * 30))
+        c.__init__('a' * 500 + 'b' * 300)
         c.__init__('cdc')
         self.assertEqual(c, dict(a=555, b=333, c=3, d=1))
         self.assertEqual(c.setdefault('d', 5), 1)
@@ -425,6 +424,7 @@
                     cPickle.loads(cPickle.dumps(words, -1)),
                     eval(repr(words)),
                     update_test,
+                    Counter(words),
                     ]):
             msg = (i, dup, words)
             self.assert_(dup is not words)


More information about the Python-checkins mailing list