Path: blob/master/sage/sets/disjoint_union_enumerated_sets.py
4095 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 (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 (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.Permutation_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<class 'sage.structure.element_wrapper.DisjointUnionEnumeratedSets_with_category.element_class'>145sage: el.parent() == UNoFacade146True147sage: elv = el.value; elv148[2, 1, 3]149sage: type(elv)150<class 'sage.combinat.permutation.Permutation_class'>151152153Possible extensions: the current enumeration order is not suitable154for unions of infinite enumerated sets (except possibly for the155last one). One could add options to specify alternative enumeration156orders (anti-diagonal, round robin, ...) to handle this case.157158159.. rubric:: Inheriting from ``DisjointUnionEnumeratedSets``160161There are two different use cases for inheriting from162:class:`DisjointUnionEnumeratedSets`: writing a parent which163happens to be a disjoint union of some known parents, or writing164generic disjoint unions for some particular classes of165:class:`sage.categories.enumerated_sets.EnumeratedSets`.166167- In the first use case, the input of the ``__init__`` method is168most likely different from that of169:class:`DisjointUnionEnumeratedSets`. Then, one simply170writes the ``__init__`` method as usual::171172sage: class MyUnion(DisjointUnionEnumeratedSets):173... def __init__(self):174... DisjointUnionEnumeratedSets.__init__(self,175... Family([1,2], Permutations))176sage: pp = MyUnion()177sage: pp.list()178[[1], [1, 2], [2, 1]]179180In case the :meth:`__init__` method takes optional arguments,181or does some normalization on them, a specific method182``__classcall_private__`` is required (see the183documentation of :class:`UniqueRepresentation`).184185- In the second use case, the input of the ``__init__`` method186is the same as that of :class:`DisjointUnionEnumeratedSets`;187one therefore wants to inherit the :meth:`__classcall_private__`188method as well, which can be achieved as follows::189190sage: class UnionOfSpecialSets(DisjointUnionEnumeratedSets):191... __classcall_private__ = staticmethod(DisjointUnionEnumeratedSets.__classcall_private__)192...193sage: psp = UnionOfSpecialSets(Family([1,2], Permutations))194sage: psp.list()195[[1], [1, 2], [2, 1]]196197TESTS::198199sage: TestSuite(U1).run()200sage: TestSuite(U2).run()201sage: TestSuite(U3).run()202sage: TestSuite(U4).run()203doctest:...: UserWarning: Disjoint union of Lazy family (Permutations(i))_{i in Non negative integers} is an infinite union204The default implementation of __contains__ can loop forever. Please overload it.205sage: TestSuite(Ukeep).run()206sage: TestSuite(UNoFacade).run()207208The following three lines are required for the pickling tests,209because the classes ``MyUnion`` and ``UnionOfSpecialSets`` have210been defined interactively::211212sage: import __main__213sage: __main__.MyUnion = MyUnion214sage: __main__.UnionOfSpecialSets = UnionOfSpecialSets215216sage: TestSuite(pp).run()217sage: TestSuite(psp).run()218219"""220221@staticmethod222def __classcall_private__(cls, fam, facade=True,223keepkey=False, category=None):224"""225Normalization of arguments; see :class:`UniqueRepresentation`.226227TESTS:228229We check that disjoint unions have unique representation::230231sage: U1 = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),232... 2: FiniteEnumeratedSet([4,5,6])})233sage: U2 = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),234... 2: FiniteEnumeratedSet([4,5,6])})235sage: U1 == U2236True237sage: U1 is U2 # indirect doctest238True239sage: U3 = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),240... 2: FiniteEnumeratedSet([4,5])})241sage: U1 == U3242False243"""244# facade = options.pop('facade', True);245# keepkey = options.pop('keepkey', False);246assert(isinstance(facade, bool))247assert(isinstance(keepkey, bool))248return super(DisjointUnionEnumeratedSets, cls).__classcall__(249cls, Family(fam),250facade = facade, keepkey = keepkey, category=category)251252253def __init__(self, family, facade=True, keepkey=False, category = None):254"""255TESTS::256257sage: U = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),258... 2: FiniteEnumeratedSet([4,5,6])})259sage: TestSuite(U).run()260"""261self._family = family262self._facade = facade263self._keepkey = keepkey264if self._is_category_initialized():265return266if category is None:267# try to guess if the result is infinite or not.268if self._family in InfiniteEnumeratedSets():269category = InfiniteEnumeratedSets()270elif self._family.last().cardinality() == Infinity:271category = InfiniteEnumeratedSets()272else:273category = FiniteEnumeratedSets()274Parent.__init__(self, category = category)275276def _repr_(self):277"""278TESTS::279280sage: U = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),281... 2: FiniteEnumeratedSet([4,5,6])})282sage: U # indirect doctest283Disjoint union of Finite family {1: {1, 2, 3}, 2: {4, 5, 6}}284"""285return "Disjoint union of %s"%self._family286287288def _is_a(self, x):289"""290Check if a Sage object belongs to self. This methods is a helper for291:meth:`__contains__` and the constructor :meth:`_element_constructor_`.292293EXAMPLES::294295sage: U4 = DisjointUnionEnumeratedSets(296... Family(NonNegativeIntegers(), Compositions))297sage: U4._is_a(Composition([3,2,1,1]))298doctest:...: UserWarning: Disjoint union of Lazy family (Compositions(i))_{i in Non negative integers} is an infinite union299The default implementation of __contains__ can loop forever. Please overload it.300True301"""302if self._keepkey:303return (isinstance(x, tuple) and304x[0] in self._family.keys() and305x[1] in self._family[x[0]])306else:307from warnings import warn308if self._family.cardinality() == Infinity:309warn("%s is an infinite union\nThe default implementation of __contains__ can loop forever. Please overload it."%(self))310return any(x in a for a in self._family)311312313def __contains__(self, x):314"""315.. warning::316317If ``self`` is an infinite union and if the answer is318logically False, this will loop forever and never answer319``False``. Therefore, a warning is issued.320321EXAMPLES::322323sage: U4 = DisjointUnionEnumeratedSets(324... Family(NonNegativeIntegers(), Partitions))325sage: Partition([]) in U4326doctest:...: UserWarning: Disjoint union of Lazy family (Partitions(i))_{i in Non negative integers} is an infinite union327The default implementation of __contains__ can loop forever. Please overload it.328True329330Note: one has to use a different family from the previous one in this331file otherwise the warning is not re-issued::332333sage: Partition([3,2,1,1]) in U4334True335336The following call will loop forever::337338sage: 2 in U4 # not tested, loop forever339"""340if self._facade:341return self._is_a(x)342else:343if isinstance(x, self.element_class):344return True345else:346return self._is_a(x)347348def __iter__(self):349"""350TESTS::351352sage: U4 = DisjointUnionEnumeratedSets(353... Family(NonNegativeIntegers(), Permutations))354sage: it = iter(U4)355sage: [it.next(), it.next(), it.next(), it.next(), it.next(), it.next()]356[[], [1], [1, 2], [2, 1], [1, 2, 3], [1, 3, 2]]357358sage: U4 = DisjointUnionEnumeratedSets(359... Family(NonNegativeIntegers(), Permutations),360... keepkey=True, facade=False)361sage: it = iter(U4)362sage: [it.next(), it.next(), it.next(), it.next(), it.next(), it.next()]363[(0, []), (1, [1]), (2, [1, 2]), (2, [2, 1]), (3, [1, 2, 3]), (3, [1, 3, 2])]364sage: el = it.next(); el.parent() == U4365True366sage: el.value == (3, Permutation([2,1,3]))367True368"""369for k in self._family.keys():370for el in self._family[k]:371if self._keepkey:372el = (k, el)373if self._facade:374yield el375else:376yield self.element_class(el, parent=self) # Bypass correctness tests377378def an_element(self):379"""380Returns an element of this disjoint union, as per :meth:`Sets.ParentMethods.an_element`.381382EXAMPLES::383384sage: U4 = DisjointUnionEnumeratedSets(385... Family([3, 5, 7], Permutations))386sage: U4.an_element()387[1, 2, 3]388"""389return self._an_element_from_iterator()390391@cached_method392def cardinality(self):393"""394Returns the cardinality of this disjoint union.395396EXAMPLES:397398For finite disjoint unions, the cardinality is computed by399summing the cardinalities of the enumerated sets::400401sage: U = DisjointUnionEnumeratedSets(Family([0,1,2,3], Permutations))402sage: U.cardinality()40310404405For infinite disjoint unions, this makes the assumption that406the result is infinite::407408sage: U = DisjointUnionEnumeratedSets(409... Family(NonNegativeIntegers(), Permutations))410sage: U.cardinality()411+Infinity412413.. warning::414415as pointed out in the main documentation, it is416possible to construct examples where this is incorrect::417418sage: U = DisjointUnionEnumeratedSets(419... Family(NonNegativeIntegers(), lambda x: []))420sage: U.cardinality() # Should be 0!421+Infinity422423"""424if self._family.cardinality() == Infinity:425return Infinity426return sum(set.cardinality() for set in self._family)427428@lazy_attribute429def _element_constructor_(self):430"""431TESTS::432433sage: U = DisjointUnionEnumeratedSets(434... Family([1,2,3], Partitions), facade=False)435sage: U._element_constructor_436<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}>437sage: U = DisjointUnionEnumeratedSets(438... Family([1,2,3], Partitions), facade=True)439sage: U._element_constructor_440Traceback (most recent call last):441...442AttributeError: 'DisjointUnionEnumeratedSets_with_category' object has no attribute '_element_constructor_'443"""444if not self._facade:445return self._element_constructor_default446else:447return NotImplemented448449def _element_constructor_default(self, el):450r"""451TESTS::452453sage: U = DisjointUnionEnumeratedSets(454... Family([1,2,3], Partitions), facade=False)455sage: U([1]) # indirect doctest456[1]457sage: U([2,1]) # indirect doctest458[2, 1]459sage: U([1,3,2]) # indirect doctest460Traceback (most recent call last):461...462ValueError: 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}463464sage: U = DisjointUnionEnumeratedSets(465... Family([1,2,3], Partitions), keepkey=True, facade=False)466sage: U((1, [1])) # indirect doctest467(1, [1])468sage: U((3,[2,1])) # indirect doctest469(3, [2, 1])470sage: U((4,[2,1])) # indirect doctest471Traceback (most recent call last):472...473ValueError: 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}474"""475if isinstance(el, self.element_class):476el = el.value477if self._is_a(el):478return self.element_class(el, parent=self)479else:480raise ValueError, "Value %s does not belong to %s"%(el, self)481482@lazy_attribute483def Element(self):484"""485TESTS::486487sage: U = DisjointUnionEnumeratedSets(488... Family([1,2,3], Partitions), facade=False)489sage: U.Element490<class 'sage.structure.element_wrapper.ElementWrapper'>491sage: U = DisjointUnionEnumeratedSets(492... Family([1,2,3], Partitions), facade=True)493sage: U.Element494Traceback (most recent call last):495...496AttributeError: 'DisjointUnionEnumeratedSets_with_category' object has no attribute 'Element'497"""498if not self._facade:499return ElementWrapper500else:501return NotImplemented502503504