Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions Doc/library/collections.rst
Original file line number Diff line number Diff line change
Expand Up @@ -253,15 +253,20 @@ 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:

.. 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())
Expand All @@ -272,10 +277,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])

Expand Down
4 changes: 2 additions & 2 deletions Lib/collections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,8 +574,8 @@ def most_common(self, n=None):
'''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
Expand Down
57 changes: 57 additions & 0 deletions Lib/test/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -1813,6 +1813,63 @@ def test_init(self):
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)
Expand Down