From 538878ca9c973adfe56b80212cf16e8101552a8b Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 23 May 2020 17:46:17 -0700 Subject: [PATCH 01/14] Add basic definitions and invariants --- Lib/collections/__init__.py | 64 +++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index c4bff592dc0e71..3989de4c35ca90 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -712,12 +712,25 @@ def __repr__(self): # To strip negative and zero counts, add-in an empty counter: # c += Counter() # - # Rich comparison operators for multiset subset and superset tests - # are deliberately omitted due to semantic conflicts with the - # existing inherited dict equality method. Subset and superset - # semantics ignore zero counts and require that p≤q ∧ p≥q → p=q; - # however, that would not be the case for p=Counter(a=1, b=0) - # and q=Counter(a=1) where the dictionaries are not equal. + # + # When the multiplicities are zero or one, multiset operations are + # guaranteed to be equivalent to the corresponding operations for + # regular sets. + # Given counter multisets such as: + # cp = Counter(a=1, b=0, c=1) + # cq = Counter(c=1, d=0, e=1) + # The corresponding regular sets would be: + # sp = {'a', 'c'} + # cq = {'c', 'e'} + # All of the following relations would hold: + # set(cp + cq) == sp | sq + # set(cp - cq) == sp - sq + # set(cp | cq) == sp | sq + # set(cp & cq) == sp & sq + # cp.isequal(cq) == (sp == sq) + # cp.issubset(cq) == sp.issubset(sq) + # cp.issuperset(cq) == sp.issuperset(sq) + # cp.isdisjoint(cq) == sp.isdisjoint(sq) def __add__(self, other): '''Add counts from two counters. @@ -876,6 +889,45 @@ def __iand__(self, other): self[elem] = other_count return self._keep_positive() + def isequal(self, other): + ''' Test whether positive counts agree exactly. + + Unlike the __eq__() method, this test ignores counts that + are zero or negative. + ''' + if not isinstance(other, Counter): + return NotImplemented + return +self == +other + + def issubset(self, other): + 'True if positive counts in self <= counts in other.' + if not isinstance(other, Counter): + return NotImplemented + return not self - other + + def issuperset(self, other): + 'True if positive counts in self >= counts in other.' + if not isinstance(other, Counter): + return NotImplemented + return not other - self + + def isdisjoint(self, other): + 'True is none of the elements in self overlap with other' + return not self & other + + # Rich comparison operators for multiset subset and superset tests + # have been deliberately omitted due to semantic conflicts with the + # existing inherited dict equality method. Subset and superset + # semantics ignore zero counts and require that p≤q ∧ p≥q → p=q; + # however, that would not be the case for p=Counter(a=1, b=0) + # and q=Counter(a=1) where the dictionaries are not equal. + + def __lt__(self, other): + raise TypeError( + 'Rich comparison operators have been deliberately omitted. ' + 'Use the isequal(), issubset(), and issuperset() methods instead.') + __le__ = __gt__ = __ge__ = __lt__ + ######################################################################## ### ChainMap From ce91225470df702eabc8813b94b9334668048477 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 23 May 2020 18:07:35 -0700 Subject: [PATCH 02/14] Neaten-up --- Lib/collections/__init__.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 3989de4c35ca90..f64586fe4c09f5 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -712,10 +712,9 @@ def __repr__(self): # To strip negative and zero counts, add-in an empty counter: # c += Counter() # - # - # When the multiplicities are zero or one, multiset operations are - # guaranteed to be equivalent to the corresponding operations for - # regular sets. + # When the multiplicities are all zero or one, multiset operations + # are guaranteed to be equivalent to the corresponding operations + # for regular sets. # Given counter multisets such as: # cp = Counter(a=1, b=0, c=1) # cq = Counter(c=1, d=0, e=1) @@ -918,7 +917,7 @@ def isdisjoint(self, other): # Rich comparison operators for multiset subset and superset tests # have been deliberately omitted due to semantic conflicts with the # existing inherited dict equality method. Subset and superset - # semantics ignore zero counts and require that p≤q ∧ p≥q → p=q; + # semantics ignore zero counts and require that p≤q ∧ p≥q ⇔ p=q; # however, that would not be the case for p=Counter(a=1, b=0) # and q=Counter(a=1) where the dictionaries are not equal. From c160f822a629cbab83452b9b0a56495a99e75a5e Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 23 May 2020 18:24:22 -0700 Subject: [PATCH 03/14] Add blurb --- .../next/Library/2020-05-23-18-24-13.bpo-22533.k64XGo.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2020-05-23-18-24-13.bpo-22533.k64XGo.rst diff --git a/Misc/NEWS.d/next/Library/2020-05-23-18-24-13.bpo-22533.k64XGo.rst b/Misc/NEWS.d/next/Library/2020-05-23-18-24-13.bpo-22533.k64XGo.rst new file mode 100644 index 00000000000000..737162f7e12b28 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-05-23-18-24-13.bpo-22533.k64XGo.rst @@ -0,0 +1,2 @@ +Add multiset comparison methods to collections.Counter(): isequal(), +issubset(), issuperset(), and isdisjoint(). From 987532ff02846ffb7c162b4d75d6079366823ac3 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 23 May 2020 18:28:13 -0700 Subject: [PATCH 04/14] Remove the NotImplemented return values --- Lib/collections/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index f64586fe4c09f5..5df2fc5ba821ca 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -894,20 +894,14 @@ def isequal(self, other): Unlike the __eq__() method, this test ignores counts that are zero or negative. ''' - if not isinstance(other, Counter): - return NotImplemented return +self == +other def issubset(self, other): 'True if positive counts in self <= counts in other.' - if not isinstance(other, Counter): - return NotImplemented return not self - other def issuperset(self, other): 'True if positive counts in self >= counts in other.' - if not isinstance(other, Counter): - return NotImplemented return not other - self def isdisjoint(self, other): From 6a471a1c021ed48163f2e0e8cfd0b943f36099b6 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 23 May 2020 20:28:31 -0700 Subject: [PATCH 05/14] Handle iterable inputs. Add some tests --- Lib/collections/__init__.py | 10 +++++++++- Lib/test/test_collections.py | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 5df2fc5ba821ca..6a1e7c5cd002a2 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -894,18 +894,26 @@ def isequal(self, other): Unlike the __eq__() method, this test ignores counts that are zero or negative. ''' + if not isinstance(other, Counter): + other = Counter(other) return +self == +other def issubset(self, other): 'True if positive counts in self <= counts in other.' + if not isinstance(other, Counter): + other = Counter(other) return not self - other def issuperset(self, other): 'True if positive counts in self >= counts in other.' + if not isinstance(other, Counter): + other = Counter(other) return not other - self def isdisjoint(self, other): 'True is none of the elements in self overlap with other' + if not isinstance(other, Counter): + other = Counter(other) return not self & other # Rich comparison operators for multiset subset and superset tests @@ -920,7 +928,7 @@ def __lt__(self, other): 'Rich comparison operators have been deliberately omitted. ' 'Use the isequal(), issubset(), and issuperset() methods instead.') __le__ = __gt__ = __ge__ = __lt__ - + ######################################################################## ### ChainMap diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index a8d3337ef5288d..bcf6fb56151c95 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -7,6 +7,7 @@ import operator import pickle from random import choice, randrange +from itertools import product, chain, combinations import string import sys from test import support @@ -2219,6 +2220,25 @@ def test_helper_function(self): self.assertTrue(c.called) self.assertEqual(dict(c), {'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r':2 }) + def test_multiset_operations_equivalent_to_set_operations(self): + # When the multiplicities are all zero or one, multiset operations + # are guaranteed to be equivalent to the corresponding operations + # for regular sets. + s = list(product('abc', range(2))) + powerset = chain.from_iterable(combinations(s, r) for r in range(len(s)+1)) + counters = [Counter(dict(groups)) for groups in powerset] + for cp, cq in product(counters, repeat=2): + sp = set(cp.elements()) + sq = set(cq.elements()) + self.assertEqual(set(cp + cq), sp | sq) + self.assertEqual(set(cp - cq), sp - sq) + self.assertEqual(set(cp | cq), sp | sq) + self.assertEqual(set(cp & cq), sp & sq) + self.assertEqual(cp.isequal(cq), sp == sq) + self.assertEqual(cp.issubset(cq), sp.issubset(sq)) + self.assertEqual(cp.issuperset(cq), sp.issuperset(sq)) + self.assertEqual(cp.isdisjoint(cq), sp.isdisjoint(sq)) + ################################################################################ ### Run tests From db9e0e6bbf4f36f88a92bc171c3bab4bf51bd428 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 23 May 2020 20:47:05 -0700 Subject: [PATCH 06/14] Clean-up section on omitted operations --- Lib/collections/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 6a1e7c5cd002a2..8af975ac5d714d 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -923,11 +923,12 @@ def isdisjoint(self, other): # however, that would not be the case for p=Counter(a=1, b=0) # and q=Counter(a=1) where the dictionaries are not equal. - def __lt__(self, other): + def _omitted(self, other): raise TypeError( 'Rich comparison operators have been deliberately omitted. ' 'Use the isequal(), issubset(), and issuperset() methods instead.') - __le__ = __gt__ = __ge__ = __lt__ + + __lt__ = __le__ = __gt__ = __ge__ = __lt__ = _omitted ######################################################################## From 160b36561aedc8cca4328bc7e5f69412cd7723fa Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 23 May 2020 20:49:42 -0700 Subject: [PATCH 07/14] Improve clarity by separating element values --- Lib/test/test_collections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index bcf6fb56151c95..d446319d4b7076 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -2224,7 +2224,7 @@ def test_multiset_operations_equivalent_to_set_operations(self): # When the multiplicities are all zero or one, multiset operations # are guaranteed to be equivalent to the corresponding operations # for regular sets. - s = list(product('abc', range(2))) + s = list(product(('a', 'b', 'c'), range(2))) powerset = chain.from_iterable(combinations(s, r) for r in range(len(s)+1)) counters = [Counter(dict(groups)) for groups in powerset] for cp, cq in product(counters, repeat=2): From 9c3e9a6f8c426115531d9b971a3865753e696085 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 24 May 2020 03:30:53 -0700 Subject: [PATCH 08/14] Add tests. Handle negative values. --- Lib/collections/__init__.py | 4 ++-- Lib/test/test_collections.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 8af975ac5d714d..b1af8ade8e6fa5 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -902,13 +902,13 @@ def issubset(self, other): 'True if positive counts in self <= counts in other.' if not isinstance(other, Counter): other = Counter(other) - return not self - other + return not self - (+other) def issuperset(self, other): 'True if positive counts in self >= counts in other.' if not isinstance(other, Counter): other = Counter(other) - return not other - self + return not other - (+self) def isdisjoint(self, other): 'True is none of the elements in self overlap with other' diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index d446319d4b7076..20d7b4e9b39c60 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -2239,6 +2239,26 @@ def test_multiset_operations_equivalent_to_set_operations(self): self.assertEqual(cp.issuperset(cq), sp.issuperset(sq)) self.assertEqual(cp.isdisjoint(cq), sp.isdisjoint(sq)) + def test_multiset_equal(self): + self.assertTrue(Counter(a=3, b=2, c=0).isequal('ababa')) + self.assertTrue(Counter(a=0, b=-1, c=0).isequal('')) + self.assertFalse(Counter(a=3, b=2).isequal('babab')) + + def test_multiset_subset(self): + self.assertTrue(Counter(a=3, b=2, c=0).issubset('ababa')) + self.assertTrue(Counter(a=0, b=-1, c=0).issubset('ababa')) + self.assertTrue(Counter(a=3, b=2, c=0).issubset('abcabcabcd')) + self.assertFalse(Counter(a=3, b=2).issubset('babab')) + + def test_multiset_superset(self): + self.assertTrue(Counter(a=3, b=2, c=0, d=-1).issuperset('aab')) + self.assertFalse(Counter(a=3, b=2, c=0, d=-1).issuperset('aabd')) + + def test_multiset_disjoint(self): + self.assertTrue(Counter(a=3, b=2, c=0, d=-1).isdisjoint('cde')) + self.assertTrue(Counter(a=3, b=2, c=0, d=-1).isdisjoint('efgg')) + self.assertFalse(Counter(a=3, b=2, c=0, d=-1).isdisjoint('bcd')) + ################################################################################ ### Run tests From 8fc2b875d817aa167cc8575ef029236714ef71d3 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 24 May 2020 05:44:44 -0700 Subject: [PATCH 09/14] Isolate the predicate tests with negative counts --- Lib/test/test_collections.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 20d7b4e9b39c60..8d80e88673b89c 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -2241,23 +2241,42 @@ def test_multiset_operations_equivalent_to_set_operations(self): def test_multiset_equal(self): self.assertTrue(Counter(a=3, b=2, c=0).isequal('ababa')) - self.assertTrue(Counter(a=0, b=-1, c=0).isequal('')) self.assertFalse(Counter(a=3, b=2).isequal('babab')) def test_multiset_subset(self): self.assertTrue(Counter(a=3, b=2, c=0).issubset('ababa')) - self.assertTrue(Counter(a=0, b=-1, c=0).issubset('ababa')) - self.assertTrue(Counter(a=3, b=2, c=0).issubset('abcabcabcd')) self.assertFalse(Counter(a=3, b=2).issubset('babab')) def test_multiset_superset(self): - self.assertTrue(Counter(a=3, b=2, c=0, d=-1).issuperset('aab')) - self.assertFalse(Counter(a=3, b=2, c=0, d=-1).issuperset('aabd')) + self.assertTrue(Counter(a=3, b=2, c=0).issuperset('aab')) + self.assertFalse(Counter(a=3, b=2, c=0).issuperset('aabd')) def test_multiset_disjoint(self): + self.assertTrue(Counter(a=3, b=2, c=0).isdisjoint('cde')) + self.assertFalse(Counter(a=3, b=2, c=0).isdisjoint('bcd')) + + def test_multiset_predicates_with_negative_counts(self): + # Multiset predicates run on the output of the elements() method, + # meaning that zero counts and negative counts are ignored. + # The tests below confirm that we get that same results as the + # tests above, even after a negative count has been included + # in either *self* or *other*. + self.assertTrue(Counter(a=3, b=2, c=0, d=-1).isequal('ababa')) + self.assertFalse(Counter(a=3, b=2, d=-1).isequal('babab')) + self.assertTrue(Counter(a=3, b=2, c=0, d=-1).issubset('ababa')) + self.assertFalse(Counter(a=3, b=2, d=-1).issubset('babab')) + self.assertTrue(Counter(a=3, b=2, c=0, d=-1).issuperset('aab')) + self.assertFalse(Counter(a=3, b=2, c=0, d=-1).issuperset('aabd')) self.assertTrue(Counter(a=3, b=2, c=0, d=-1).isdisjoint('cde')) - self.assertTrue(Counter(a=3, b=2, c=0, d=-1).isdisjoint('efgg')) self.assertFalse(Counter(a=3, b=2, c=0, d=-1).isdisjoint('bcd')) + self.assertTrue(Counter(a=3, b=2, c=0, d=-1).isequal(Counter(a=3, b=2, c=-1))) + self.assertFalse(Counter(a=3, b=2, d=-1).isequal(Counter(a=2, b=3, c=-1))) + self.assertTrue(Counter(a=3, b=2, c=0, d=-1).issubset(Counter(a=3, b=2, c=-1))) + self.assertFalse(Counter(a=3, b=2, d=-1).issubset(Counter(a=2, b=3, c=-1))) + self.assertTrue(Counter(a=3, b=2, c=0, d=-1).issuperset(Counter(a=2, b=1, c=-1))) + self.assertFalse(Counter(a=3, b=2, c=0, d=-1).issuperset(Counter(a=2, b=1, c=-1, d=1))) + self.assertTrue(Counter(a=3, b=2, c=0, d=-1).isdisjoint(Counter(c=1, d=2, e=3, f=-1))) + self.assertFalse(Counter(a=3, b=2, c=0, d=-1).isdisjoint(Counter(b=1, c=1, d=1, e=-1))) ################################################################################ From fdbe0c182c65923c7b60ee761e8c4286857fd3d5 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 24 May 2020 07:17:01 -0700 Subject: [PATCH 10/14] Optimized early-out versions. Beef-up docstrings. --- Lib/collections/__init__.py | 64 +++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index b1af8ade8e6fa5..a36f79b560653b 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -889,32 +889,76 @@ def __iand__(self, other): return self._keep_positive() def isequal(self, other): - ''' Test whether positive counts agree exactly. + ''' Test whether counts agree exactly. - Unlike the __eq__() method, this test ignores counts that - are zero or negative. + Negative or missing counts are treated as zero. + + This is different that the __eq__() method which would treat + negative or missing counts as distinct from zero: + + >>> Counter(a=1, b=0).isequal(Counter(a=1)) + True + >>> Counter(a=1, b=0) == Counter(a=1) + False + + Logically equivalent to: +self == +other ''' if not isinstance(other, Counter): other = Counter(other) - return +self == +other + for elem in set(self) | set(other): + left = self[elem] + if left < 0: + left = 0 + right = other[elem] + if right < 0: + right = 0 + if left != right: + return False + return True + + return all(self[elem] == other[elem] for elem in _chain(self, other)) def issubset(self, other): - 'True if positive counts in self <= counts in other.' + '''True if the counts in self are less than or equal to those in other. + + Negative or missing counts are treated as zero. + + Logically equivalent to: not self - (+other) + ''' if not isinstance(other, Counter): other = Counter(other) - return not self - (+other) + for elem, count in self.items(): + other_count = other[elem] + if other_count < 0: + other_count = 0 + if count > other_count: + return False + return True def issuperset(self, other): - 'True if positive counts in self >= counts in other.' + '''True if the counts in self are greater than or equal to those in other. + + Negative or missing counts are treated as zero. + + Logically equivalent to: not other - (+self) + ''' if not isinstance(other, Counter): other = Counter(other) - return not other - (+self) + return other.issubset(self) def isdisjoint(self, other): - 'True is none of the elements in self overlap with other' + '''True if none of the elements in self overlap with those other + + Negative or missing counts are ignored. + + Logically equivalent to: not (+self) & (+other) + ''' if not isinstance(other, Counter): other = Counter(other) - return not self & other + for elem, count in self.items(): + if count > 0 and other[elem] > 0: + return False + return True # Rich comparison operators for multiset subset and superset tests # have been deliberately omitted due to semantic conflicts with the From 5e20521516c59f83e19b685525a53b82848b03b6 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 24 May 2020 07:24:05 -0700 Subject: [PATCH 11/14] Clean-ups and optimization for the common case --- Lib/collections/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index a36f79b560653b..71e79f33bdd3a5 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -893,8 +893,8 @@ def isequal(self, other): Negative or missing counts are treated as zero. - This is different that the __eq__() method which would treat - negative or missing counts as distinct from zero: + This is different than the inherited __eq__() method which + treats negative or missing counts as distinct from zero: >>> Counter(a=1, b=0).isequal(Counter(a=1)) True @@ -907,17 +907,17 @@ def isequal(self, other): other = Counter(other) for elem in set(self) | set(other): left = self[elem] + right = other[elem] + if left == right: + continue if left < 0: left = 0 - right = other[elem] if right < 0: right = 0 if left != right: return False return True - return all(self[elem] == other[elem] for elem in _chain(self, other)) - def issubset(self, other): '''True if the counts in self are less than or equal to those in other. From 19a8b2779c0c33c88fcbb1433f6eb7585c882629 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 24 May 2020 08:05:18 -0700 Subject: [PATCH 12/14] Add documentation --- Doc/library/collections.rst | 41 +++++++++++++++++++++++++++++++++++++ Lib/collections/__init__.py | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 549ac1bccadf5d..ea2b420292eb0f 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -290,6 +290,47 @@ For example:: >>> sorted(c.elements()) ['a', 'a', 'a', 'a', 'b', 'b'] + .. method:: isdisjoint(other) + + True if none of the elements in *self* overlap with those in *other*. + Negative or missing counts are ignored. + Logically equivalent to: ``not (+self) & (+other)`` + + .. versionadded:: 3.10 + + .. method:: isequal(other) + + Test whether counts agree exactly. + Negative or missing counts are treated as zero. + + This method works differently than the inherited :meth:`__eq__` method + which treats negative or missing counts as distinct from zero:: + + >>> Counter(a=1, b=0).isequal(Counter(a=1)) + True + >>> Counter(a=1, b=0) == Counter(a=1) + False + + Logically equivalent to: ``+self == +other`` + + .. versionadded:: 3.10 + + .. method:: issubset(other) + + True if the counts in *self* are less than or equal to those in *other*. + Negative or missing counts are treated as zero. + Logically equivalent to: ``not self - (+other)`` + + .. versionadded:: 3.10 + + .. method:: issuperset(other) + + True if the counts in *self* are greater than or equal to those in *other*. + Negative or missing counts are treated as zero. + Logically equivalent to: ``not other - (+self)`` + + .. versionadded:: 3.10 + .. method:: most_common([n]) Return a list of the *n* most common elements and their counts from the diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 71e79f33bdd3a5..fab00f1607fa05 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -947,7 +947,7 @@ def issuperset(self, other): return other.issubset(self) def isdisjoint(self, other): - '''True if none of the elements in self overlap with those other + '''True if none of the elements in self overlap with those in other. Negative or missing counts are ignored. From 74c471d0662ad2d5ca964a80f01f09d68bacb0fb Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 24 May 2020 09:35:40 -0700 Subject: [PATCH 13/14] Use subset and superset symbols --- Lib/collections/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index fab00f1607fa05..e85eb381d27092 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -963,7 +963,7 @@ def isdisjoint(self, other): # Rich comparison operators for multiset subset and superset tests # have been deliberately omitted due to semantic conflicts with the # existing inherited dict equality method. Subset and superset - # semantics ignore zero counts and require that p≤q ∧ p≥q ⇔ p=q; + # semantics ignore zero counts and require that p⊆q ∧ p⊇q ⇔ p=q; # however, that would not be the case for p=Counter(a=1, b=0) # and q=Counter(a=1) where the dictionaries are not equal. From f26a84ad26d37720e04eb3867671f8e7a0591342 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Tue, 26 May 2020 09:45:00 -0700 Subject: [PATCH 14/14] Fix typo in docstring --- Lib/collections/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index e85eb381d27092..bd880163ee707f 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -720,7 +720,7 @@ def __repr__(self): # cq = Counter(c=1, d=0, e=1) # The corresponding regular sets would be: # sp = {'a', 'c'} - # cq = {'c', 'e'} + # sq = {'c', 'e'} # All of the following relations would hold: # set(cp + cq) == sp | sq # set(cp - cq) == sp - sq