From dda8431e181ae24a78c300de0ae026ff00351a9b Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 10 Feb 2018 15:00:53 -0800 Subject: [PATCH 1/3] Add tests for Counter order. No behavior change. --- Lib/test/test_collections.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 74372d28ed050d5..b1aed178ee6a91b 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -1867,6 +1867,14 @@ def test_init(self): self.assertRaises(TypeError, Counter, (), ()) self.assertRaises(TypeError, Counter.__init__) + def test_order_preservation(self): + self.assertEqual(list(Counter('abracadabra').items()), + [('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)]) + # letters with same count: ^----------^ ^---------^ + + self.assertEqual(list(Counter('xyzpdqqdpzyx').items()), + [('x', 2), ('y', 2), ('z', 2), ('p', 2), ('d', 2), ('q', 2)]) + def test_update(self): c = Counter() c.update(self=42) From 58ebd9219bfe7f9ab10ad037059e84a756b971c0 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 20 Feb 2019 16:12:31 -0800 Subject: [PATCH 2/3] Update docs and tests --- Doc/library/collections.rst | 13 +++++++--- Lib/collections/__init__.py | 4 +-- Lib/test/test_collections.py | 49 ++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 3fa8b32ff006f53..d980dcdb7609dca 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -253,6 +253,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: @@ -260,8 +265,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()) @@ -272,9 +277,9 @@ 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 + >>> Counter('abracadabra').most_common(3) [('a', 5), ('r', 2), ('b', 2)] .. method:: subtract([iterable-or-mapping]) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index d88c4aaaee89210..632a509d316cb33 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -570,8 +570,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 diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index b1aed178ee6a91b..819fe2d222f53fd 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -1868,13 +1868,62 @@ def test_init(self): 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) From 661abcde71015473dbf79014856f4c54a40f74fb Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Wed, 20 Feb 2019 17:13:30 -0800 Subject: [PATCH 3/3] Fix doctest output and capitalization --- Doc/library/collections.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index d980dcdb7609dca..5f947da414a3a75 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -254,7 +254,7 @@ For example:: .. versionadded:: 3.1 .. versionchanged:: 3.7 As a :class:`dict` subclass, :class:`Counter` - inherited the capability to remember insertion order. Math operations + 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. @@ -280,7 +280,7 @@ For example:: Elements with equal counts are ordered in the order first encountered: >>> Counter('abracadabra').most_common(3) - [('a', 5), ('r', 2), ('b', 2)] + [('a', 5), ('b', 2), ('r', 2)] .. method:: subtract([iterable-or-mapping])