Path: blob/master/src/sage/sets/disjoint_union_enumerated_sets.py
8817 views
"""1Disjoint union of enumerated sets23AUTHORS:45- Florent Hivert (2009-07/09): initial implementation.6- Florent Hivert (2010-03): classcall related stuff.7- Florent Hivert (2010-12): fixed facade element construction.8"""9#****************************************************************************10# Copyright (C) 2009 Florent Hivert <[email protected]>11#12# Distributed under the terms of the GNU General Public License (GPL)13# http://www.gnu.org/licenses/14#****************************************************************************1516# from sage.structure.element import Element17from sage.structure.parent import Parent18from sage.structure.element_wrapper import ElementWrapper19from sage.sets.family import Family20from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets21from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets22from sage.rings.infinity import Infinity23from sage.misc.all import cached_method, lazy_attribute24from sage.structure.unique_representation import UniqueRepresentation2526class DisjointUnionEnumeratedSets(UniqueRepresentation, Parent):27"""28A class for disjoint unions of enumerated sets.2930INPUT:3132- ``family`` -- a list (or iterable or family) of enumerated sets33- ``keepkey`` -- a boolean34- ``facade`` -- a boolean3536This models the enumerated set obtained by concatenating together37the specified ordered sets. The later are supposed to be pairwise38disjoint; otherwise, a multiset is created.3940The argument ``family`` can be a list, a tuple, a dictionary, or a41family. If it is not a family it is first converted into a family42(see :func:`sage.sets.family.Family`).4344Experimental options:4546By default, there is no way to tell from which set of the union an47element is generated. The option ``keepkey=True`` keeps track of48those by returning pairs ``(key, el)`` where ``key`` is the index49of the set to which ``el`` belongs. When this option is specified,50the enumerated sets need not be disjoint anymore.5152With the option ``facade=False`` the elements are wrapped in an53object whose parent is the disjoint union itself. The wrapped54object can then be recovered using the 'value' attribute.5556The two options can be combined.5758The names of those options is imperfect, and subject to change in59future versions. Feedback welcome.6061EXAMPLES:6263The input can be a list or a tuple of FiniteEnumeratedSets::6465sage: U1 = DisjointUnionEnumeratedSets((66... FiniteEnumeratedSet([1,2,3]),67... FiniteEnumeratedSet([4,5,6])))68sage: U169Disjoint union of Family ({1, 2, 3}, {4, 5, 6})70sage: U1.list()71[1, 2, 3, 4, 5, 6]72sage: U1.cardinality()7367475The input can also be a dictionary::7677sage: U2 = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),78... 2: FiniteEnumeratedSet([4,5,6])})79sage: U280Disjoint union of Finite family {1: {1, 2, 3}, 2: {4, 5, 6}}81sage: U2.list()82[1, 2, 3, 4, 5, 6]83sage: U2.cardinality()8468586However in that case the enumeration order is not specified.8788In general the input can be any family::8990sage: U3 = DisjointUnionEnumeratedSets(91... Family([2,3,4], Permutations, lazy=True))92sage: U393Disjoint union of Lazy family (<class 'sage.combinat.permutation.Permutations'>(i))_{i in [2, 3, 4]}94sage: U3.cardinality()953296sage: it = iter(U3)97sage: [it.next(), it.next(), it.next(), it.next(), it.next(), it.next()]98[[1, 2], [2, 1], [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1]]99sage: U3.unrank(18)100[2, 4, 1, 3]101102This allows for infinite unions::103104sage: U4 = DisjointUnionEnumeratedSets(105... Family(NonNegativeIntegers(), Permutations))106sage: U4107Disjoint union of Lazy family (<class 'sage.combinat.permutation.Permutations'>(i))_{i in Non negative integers}108sage: U4.cardinality()109+Infinity110sage: it = iter(U4)111sage: [it.next(), it.next(), it.next(), it.next(), it.next(), it.next()]112[[], [1], [1, 2], [2, 1], [1, 2, 3], [1, 3, 2]]113sage: U4.unrank(18)114[2, 3, 1, 4]115116.. warning ::117118Beware that some of the operations assume in that case that infinitely119many of the enumerated sets are non empty.120121122.. rubric:: Experimental options123124We demonstrate the ``keepkey`` option::125126sage: Ukeep = DisjointUnionEnumeratedSets(127... Family(range(4), Permutations), keepkey=True)128sage: it = iter(Ukeep)129sage: [it.next() for i in range(6)]130[(0, []), (1, [1]), (2, [1, 2]), (2, [2, 1]), (3, [1, 2, 3]), (3, [1, 3, 2])]131sage: type(it.next()[1])132<class 'sage.combinat.permutation.StandardPermutations_n_with_category.element_class'>133134We now demonstrate the ``facade`` option::135136sage: UNoFacade = DisjointUnionEnumeratedSets(137... Family(range(4), Permutations), facade=False)138sage: it = iter(UNoFacade)139sage: [it.next() for i in range(6)]140[[], [1], [1, 2], [2, 1], [1, 2, 3], [1, 3, 2]]141sage: el = it.next(); el142[2, 1, 3]143sage: type(el)144<type 'sage.structure.element_wrapper.ElementWrapper'>145sage: el.parent() == UNoFacade146True147sage: elv = el.value; elv148[2, 1, 3]149sage: type(elv)150<class 'sage.combinat.permutation.StandardPermutations_n_with_category.element_class'>151152Possible extensions: the current enumeration order is not suitable153for unions of infinite enumerated sets (except possibly for the154last one). One could add options to specify alternative enumeration155orders (anti-diagonal, round robin, ...) to handle this case.156157158.. rubric:: Inheriting from ``DisjointUnionEnumeratedSets``159160There are two different use cases for inheriting from161:class:`DisjointUnionEnumeratedSets`: writing a parent which162happens to be a disjoint union of some known parents, or writing163generic disjoint unions for some particular classes of164:class:`sage.categories.enumerated_sets.EnumeratedSets`.165166- In the first use case, the input of the ``__init__`` method is167most likely different from that of168:class:`DisjointUnionEnumeratedSets`. Then, one simply169writes the ``__init__`` method as usual::170171sage: class MyUnion(DisjointUnionEnumeratedSets):172... def __init__(self):173... DisjointUnionEnumeratedSets.__init__(self,174... Family([1,2], Permutations))175sage: pp = MyUnion()176sage: pp.list()177[[1], [1, 2], [2, 1]]178179In case the :meth:`__init__` method takes optional arguments,180or does some normalization on them, a specific method181``__classcall_private__`` is required (see the182documentation of :class:`UniqueRepresentation`).183184- In the second use case, the input of the ``__init__`` method185is the same as that of :class:`DisjointUnionEnumeratedSets`;186one therefore wants to inherit the :meth:`__classcall_private__`187method as well, which can be achieved as follows::188189sage: class UnionOfSpecialSets(DisjointUnionEnumeratedSets):190... __classcall_private__ = staticmethod(DisjointUnionEnumeratedSets.__classcall_private__)191...192sage: psp = UnionOfSpecialSets(Family([1,2], Permutations))193sage: psp.list()194[[1], [1, 2], [2, 1]]195196TESTS::197198sage: TestSuite(U1).run()199sage: TestSuite(U2).run()200sage: TestSuite(U3).run()201sage: TestSuite(U4).run()202doctest:...: UserWarning: Disjoint union of Lazy family (<class 'sage.combinat.permutation.Permutations'>(i))_{i in Non negative integers} is an infinite union203The default implementation of __contains__ can loop forever. Please overload it.204sage: TestSuite(Ukeep).run()205sage: TestSuite(UNoFacade).run()206207The following three lines are required for the pickling tests,208because the classes ``MyUnion`` and ``UnionOfSpecialSets`` have209been defined interactively::210211sage: import __main__212sage: __main__.MyUnion = MyUnion213sage: __main__.UnionOfSpecialSets = UnionOfSpecialSets214215sage: TestSuite(pp).run()216sage: TestSuite(psp).run()217218"""219220@staticmethod221def __classcall_private__(cls, fam, facade=True,222keepkey=False, category=None):223"""224Normalization of arguments; see :class:`UniqueRepresentation`.225226TESTS:227228We check that disjoint unions have unique representation::229230sage: U1 = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),231... 2: FiniteEnumeratedSet([4,5,6])})232sage: U2 = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),233... 2: FiniteEnumeratedSet([4,5,6])})234sage: U1 == U2235True236sage: U1 is U2 # indirect doctest237True238sage: U3 = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),239... 2: FiniteEnumeratedSet([4,5])})240sage: U1 == U3241False242"""243# facade = options.pop('facade', True);244# keepkey = options.pop('keepkey', False);245assert(isinstance(facade, bool))246assert(isinstance(keepkey, bool))247return super(DisjointUnionEnumeratedSets, cls).__classcall__(248cls, Family(fam),249facade = facade, keepkey = keepkey, category=category)250251252def __init__(self, family, facade=True, keepkey=False, category = None):253"""254TESTS::255256sage: U = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),257... 2: FiniteEnumeratedSet([4,5,6])})258sage: TestSuite(U).run()259"""260self._family = family261self._facade = facade262self._keepkey = keepkey263if self._is_category_initialized():264return265if category is None:266# try to guess if the result is infinite or not.267if self._family in InfiniteEnumeratedSets():268category = InfiniteEnumeratedSets()269elif self._family.last().cardinality() == Infinity:270category = InfiniteEnumeratedSets()271else:272category = FiniteEnumeratedSets()273Parent.__init__(self, category = category)274275def _repr_(self):276"""277TESTS::278279sage: U = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),280... 2: FiniteEnumeratedSet([4,5,6])})281sage: U # indirect doctest282Disjoint union of Finite family {1: {1, 2, 3}, 2: {4, 5, 6}}283"""284return "Disjoint union of %s"%self._family285286287def _is_a(self, x):288"""289Check if a Sage object belongs to self. This methods is a helper for290:meth:`__contains__` and the constructor :meth:`_element_constructor_`.291292EXAMPLES::293294sage: U4 = DisjointUnionEnumeratedSets(295... Family(NonNegativeIntegers(), Compositions))296sage: U4._is_a(Composition([3,2,1,1]))297doctest:...: UserWarning: Disjoint union of Lazy family (<class 'sage.combinat.composition.Compositions'>(i))_{i in Non negative integers} is an infinite union298The default implementation of __contains__ can loop forever. Please overload it.299True300"""301if self._keepkey:302return (isinstance(x, tuple) and303x[0] in self._family.keys() and304x[1] in self._family[x[0]])305else:306from warnings import warn307if self._family.cardinality() == Infinity:308warn("%s is an infinite union\nThe default implementation of __contains__ can loop forever. Please overload it."%(self))309return any(x in a for a in self._family)310311312def __contains__(self, x):313"""314.. warning::315316If ``self`` is an infinite union and if the answer is317logically False, this will loop forever and never answer318``False``. Therefore, a warning is issued.319320EXAMPLES::321322sage: U4 = DisjointUnionEnumeratedSets(323... Family(NonNegativeIntegers(), Partitions))324sage: Partition([]) in U4325doctest:...: UserWarning: Disjoint union of Lazy family (<class 'sage.combinat.partition.Partitions'>(i))_{i in Non negative integers} is an infinite union326The default implementation of __contains__ can loop forever. Please overload it.327True328329Note: one has to use a different family from the previous one in this330file otherwise the warning is not re-issued::331332sage: Partition([3,2,1,1]) in U4333True334335The following call will loop forever::336337sage: 2 in U4 # not tested, loop forever338"""339if self._facade:340return self._is_a(x)341else:342if isinstance(x, self.element_class):343return True344else:345return self._is_a(x)346347def __iter__(self):348"""349TESTS::350351sage: U4 = DisjointUnionEnumeratedSets(352... Family(NonNegativeIntegers(), Permutations))353sage: it = iter(U4)354sage: [it.next(), it.next(), it.next(), it.next(), it.next(), it.next()]355[[], [1], [1, 2], [2, 1], [1, 2, 3], [1, 3, 2]]356357sage: U4 = DisjointUnionEnumeratedSets(358... Family(NonNegativeIntegers(), Permutations),359... keepkey=True, facade=False)360sage: it = iter(U4)361sage: [it.next(), it.next(), it.next(), it.next(), it.next(), it.next()]362[(0, []), (1, [1]), (2, [1, 2]), (2, [2, 1]), (3, [1, 2, 3]), (3, [1, 3, 2])]363sage: el = it.next(); el.parent() == U4364True365sage: el.value == (3, Permutation([2,1,3]))366True367"""368for k in self._family.keys():369for el in self._family[k]:370if self._keepkey:371el = (k, el)372if self._facade:373yield el374else:375yield self.element_class(self, el) # Bypass correctness tests376377def an_element(self):378"""379Returns an element of this disjoint union, as per :meth:`Sets.ParentMethods.an_element`.380381EXAMPLES::382383sage: U4 = DisjointUnionEnumeratedSets(384... Family([3, 5, 7], Permutations))385sage: U4.an_element()386[1, 2, 3]387"""388return self._an_element_from_iterator()389390@cached_method391def cardinality(self):392"""393Returns the cardinality of this disjoint union.394395EXAMPLES:396397For finite disjoint unions, the cardinality is computed by398summing the cardinalities of the enumerated sets::399400sage: U = DisjointUnionEnumeratedSets(Family([0,1,2,3], Permutations))401sage: U.cardinality()40210403404For infinite disjoint unions, this makes the assumption that405the result is infinite::406407sage: U = DisjointUnionEnumeratedSets(408... Family(NonNegativeIntegers(), Permutations))409sage: U.cardinality()410+Infinity411412.. warning::413414as pointed out in the main documentation, it is415possible to construct examples where this is incorrect::416417sage: U = DisjointUnionEnumeratedSets(418... Family(NonNegativeIntegers(), lambda x: []))419sage: U.cardinality() # Should be 0!420+Infinity421422"""423if self._family.cardinality() == Infinity:424return Infinity425return sum(set.cardinality() for set in self._family)426427@lazy_attribute428def _element_constructor_(self):429"""430TESTS::431432sage: U = DisjointUnionEnumeratedSets(433... Family([1,2,3], Partitions), facade=False)434sage: U._element_constructor_435<bound method DisjointUnionEnumeratedSets_with_category._element_constructor_default of Disjoint union of Finite family {1: Partitions of the integer 1, 2: Partitions of the integer 2, 3: Partitions of the integer 3}>436sage: U = DisjointUnionEnumeratedSets(437... Family([1,2,3], Partitions), facade=True)438sage: U._element_constructor_439Traceback (most recent call last):440...441AttributeError: 'DisjointUnionEnumeratedSets_with_category' object has no attribute '_element_constructor_'442"""443if not self._facade:444return self._element_constructor_default445else:446return NotImplemented447448def _element_constructor_default(self, el):449r"""450TESTS::451452sage: U = DisjointUnionEnumeratedSets(453... Family([1,2,3], Partitions), facade=False)454sage: U([1]) # indirect doctest455[1]456sage: U([2,1]) # indirect doctest457[2, 1]458sage: U([1,3,2]) # indirect doctest459Traceback (most recent call last):460...461ValueError: Value [1, 3, 2] does not belong to Disjoint union of Finite family {1: Partitions of the integer 1, 2: Partitions of the integer 2, 3: Partitions of the integer 3}462463sage: U = DisjointUnionEnumeratedSets(464... Family([1,2,3], Partitions), keepkey=True, facade=False)465sage: U((1, [1])) # indirect doctest466(1, [1])467sage: U((3,[2,1])) # indirect doctest468(3, [2, 1])469sage: U((4,[2,1])) # indirect doctest470Traceback (most recent call last):471...472ValueError: Value (4, [2, 1]) does not belong to Disjoint union of Finite family {1: Partitions of the integer 1, 2: Partitions of the integer 2, 3: Partitions of the integer 3}473"""474if isinstance(el, self.element_class):475el = el.value476if self._is_a(el):477return self.element_class(self, el)478else:479raise ValueError("Value %s does not belong to %s"%(el, self))480481@lazy_attribute482def Element(self):483"""484TESTS::485486sage: U = DisjointUnionEnumeratedSets(487... Family([1,2,3], Partitions), facade=False)488sage: U.Element489<type 'sage.structure.element_wrapper.ElementWrapper'>490sage: U = DisjointUnionEnumeratedSets(491... Family([1,2,3], Partitions), facade=True)492sage: U.Element493Traceback (most recent call last):494...495AttributeError: 'DisjointUnionEnumeratedSets_with_category' object has no attribute 'Element'496"""497if not self._facade:498return ElementWrapper499else:500return NotImplemented501502503