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
This commit is contained in:
Raymond Hettinger 2019-02-21 09:19:00 -08:00 committed by GitHub
parent 86f093f71a
commit 407c734326
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 7 deletions

View File

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

View File

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

View File

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