summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaymond Hettinger <rhettinger@users.noreply.github.com>2019-02-21 17:19:00 (GMT)
committerGitHub <noreply@github.com>2019-02-21 17:19:00 (GMT)
commit407c7343266eb3e5a2f5c1f4913082c84f8dd8a0 (patch)
treeec7a7cf9dc5bbcd4ecdd1b92475e6953632c67fd
parent86f093f71a594dcaf21b67ba13dda72863e9bde9 (diff)
downloadcpython-407c7343266eb3e5a2f5c1f4913082c84f8dd8a0.zip
cpython-407c7343266eb3e5a2f5c1f4913082c84f8dd8a0.tar.gz
cpython-407c7343266eb3e5a2f5c1f4913082c84f8dd8a0.tar.bz2
bpo-36057 Update docs and tests for ordering in collections.Counter [no behavior change] (#11962)
* Add tests for Counter order. No behavior change. * Update docs and tests * Fix doctest output and capitalization
-rw-r--r--Doc/library/collections.rst15
-rw-r--r--Lib/collections/__init__.py4
-rw-r--r--Lib/test/test_collections.py57
3 files changed, 69 insertions, 7 deletions
diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst
index ca2f116..1a09379 100644
--- a/Doc/library/collections.rst
+++ b/Doc/library/collections.rst
@@ -268,6 +268,11 @@ For example::
.. versionadded:: 3.1
+ .. versionchanged:: 3.7 As a :class:`dict` subclass, :class:`Counter`
+ Inherited the capability to remember insertion order. Math operations
+ on *Counter* objects also preserve order. Results are ordered
+ according to when an element is first encountered in the left operand
+ and then by the order encountered in the right operand.
Counter objects support three methods beyond those available for all
dictionaries:
@@ -275,8 +280,8 @@ For example::
.. method:: elements()
Return an iterator over elements repeating each as many times as its
- count. Elements are returned in arbitrary order. If an element's count
- is less than one, :meth:`elements` will ignore it.
+ count. Elements are returned in the order first encountered. If an
+ element's count is less than one, :meth:`elements` will ignore it.
>>> c = Counter(a=4, b=2, c=0, d=-2)
>>> sorted(c.elements())
@@ -287,10 +292,10 @@ For example::
Return a list of the *n* most common elements and their counts from the
most common to the least. If *n* is omitted or ``None``,
:meth:`most_common` returns *all* elements in the counter.
- Elements with equal counts are ordered arbitrarily:
+ Elements with equal counts are ordered in the order first encountered:
- >>> Counter('abracadabra').most_common(3) # doctest: +SKIP
- [('a', 5), ('r', 2), ('b', 2)]
+ >>> Counter('abracadabra').most_common(3)
+ [('a', 5), ('b', 2), ('r', 2)]
.. method:: subtract([iterable-or-mapping])
diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py
index d88c4aa..632a509 100644
--- a/Lib/collections/__init__.py
+++ b/Lib/collections/__init__.py
@@ -570,8 +570,8 @@ class Counter(dict):
'''List the n most common elements and their counts from the most
common to the least. If n is None, then list all element counts.
- >>> Counter('abcdeabcdabcaba').most_common(3)
- [('a', 5), ('b', 4), ('c', 3)]
+ >>> Counter('abracadabra').most_common(3)
+ [('a', 5), ('b', 2), ('r', 2)]
'''
# Emulate Bag.sortedByCount from Smalltalk
diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py
index 2d5a266..4f8a841 100644
--- a/Lib/test/test_collections.py
+++ b/Lib/test/test_collections.py
@@ -1881,6 +1881,63 @@ class TestCounter(unittest.TestCase):
self.assertRaises(TypeError, Counter, (), ())
self.assertRaises(TypeError, Counter.__init__)
+ def test_order_preservation(self):
+ # Input order dictates items() order
+ self.assertEqual(list(Counter('abracadabra').items()),
+ [('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)])
+ # letters with same count: ^----------^ ^---------^
+
+ # Verify retention of order even when all counts are equal
+ self.assertEqual(list(Counter('xyzpdqqdpzyx').items()),
+ [('x', 2), ('y', 2), ('z', 2), ('p', 2), ('d', 2), ('q', 2)])
+
+ # Input order dictates elements() order
+ self.assertEqual(list(Counter('abracadabra simsalabim').elements()),
+ ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'b', 'b', 'b','r',
+ 'r', 'c', 'd', ' ', 's', 's', 'i', 'i', 'm', 'm', 'l'])
+
+ # Math operations order first by the order encountered in the left
+ # operand and then by the order encounted in the right operand.
+ ps = 'aaabbcdddeefggghhijjjkkl'
+ qs = 'abbcccdeefffhkkllllmmnno'
+ order = {letter: i for i, letter in enumerate(dict.fromkeys(ps + qs))}
+ def correctly_ordered(seq):
+ 'Return true if the letters occur in the expected order'
+ positions = [order[letter] for letter in seq]
+ return positions == sorted(positions)
+
+ p, q = Counter(ps), Counter(qs)
+ self.assertTrue(correctly_ordered(+p))
+ self.assertTrue(correctly_ordered(-p))
+ self.assertTrue(correctly_ordered(p + q))
+ self.assertTrue(correctly_ordered(p - q))
+ self.assertTrue(correctly_ordered(p | q))
+ self.assertTrue(correctly_ordered(p & q))
+
+ p, q = Counter(ps), Counter(qs)
+ p += q
+ self.assertTrue(correctly_ordered(p))
+
+ p, q = Counter(ps), Counter(qs)
+ p -= q
+ self.assertTrue(correctly_ordered(p))
+
+ p, q = Counter(ps), Counter(qs)
+ p |= q
+ self.assertTrue(correctly_ordered(p))
+
+ p, q = Counter(ps), Counter(qs)
+ p &= q
+ self.assertTrue(correctly_ordered(p))
+
+ p, q = Counter(ps), Counter(qs)
+ p.update(q)
+ self.assertTrue(correctly_ordered(p))
+
+ p, q = Counter(ps), Counter(qs)
+ p.subtract(q)
+ self.assertTrue(correctly_ordered(p))
+
def test_update(self):
c = Counter()
c.update(self=42)