Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/sets/disjoint_union_enumerated_sets.py
8817 views
1
"""
2
Disjoint union of enumerated sets
3
4
AUTHORS:
5
6
- Florent Hivert (2009-07/09): initial implementation.
7
- Florent Hivert (2010-03): classcall related stuff.
8
- Florent Hivert (2010-12): fixed facade element construction.
9
"""
10
#****************************************************************************
11
# Copyright (C) 2009 Florent Hivert <[email protected]>
12
#
13
# Distributed under the terms of the GNU General Public License (GPL)
14
# http://www.gnu.org/licenses/
15
#****************************************************************************
16
17
# from sage.structure.element import Element
18
from sage.structure.parent import Parent
19
from sage.structure.element_wrapper import ElementWrapper
20
from sage.sets.family import Family
21
from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets
22
from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets
23
from sage.rings.infinity import Infinity
24
from sage.misc.all import cached_method, lazy_attribute
25
from sage.structure.unique_representation import UniqueRepresentation
26
27
class DisjointUnionEnumeratedSets(UniqueRepresentation, Parent):
28
"""
29
A class for disjoint unions of enumerated sets.
30
31
INPUT:
32
33
- ``family`` -- a list (or iterable or family) of enumerated sets
34
- ``keepkey`` -- a boolean
35
- ``facade`` -- a boolean
36
37
This models the enumerated set obtained by concatenating together
38
the specified ordered sets. The later are supposed to be pairwise
39
disjoint; otherwise, a multiset is created.
40
41
The argument ``family`` can be a list, a tuple, a dictionary, or a
42
family. If it is not a family it is first converted into a family
43
(see :func:`sage.sets.family.Family`).
44
45
Experimental options:
46
47
By default, there is no way to tell from which set of the union an
48
element is generated. The option ``keepkey=True`` keeps track of
49
those by returning pairs ``(key, el)`` where ``key`` is the index
50
of the set to which ``el`` belongs. When this option is specified,
51
the enumerated sets need not be disjoint anymore.
52
53
With the option ``facade=False`` the elements are wrapped in an
54
object whose parent is the disjoint union itself. The wrapped
55
object can then be recovered using the 'value' attribute.
56
57
The two options can be combined.
58
59
The names of those options is imperfect, and subject to change in
60
future versions. Feedback welcome.
61
62
EXAMPLES:
63
64
The input can be a list or a tuple of FiniteEnumeratedSets::
65
66
sage: U1 = DisjointUnionEnumeratedSets((
67
... FiniteEnumeratedSet([1,2,3]),
68
... FiniteEnumeratedSet([4,5,6])))
69
sage: U1
70
Disjoint union of Family ({1, 2, 3}, {4, 5, 6})
71
sage: U1.list()
72
[1, 2, 3, 4, 5, 6]
73
sage: U1.cardinality()
74
6
75
76
The input can also be a dictionary::
77
78
sage: U2 = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),
79
... 2: FiniteEnumeratedSet([4,5,6])})
80
sage: U2
81
Disjoint union of Finite family {1: {1, 2, 3}, 2: {4, 5, 6}}
82
sage: U2.list()
83
[1, 2, 3, 4, 5, 6]
84
sage: U2.cardinality()
85
6
86
87
However in that case the enumeration order is not specified.
88
89
In general the input can be any family::
90
91
sage: U3 = DisjointUnionEnumeratedSets(
92
... Family([2,3,4], Permutations, lazy=True))
93
sage: U3
94
Disjoint union of Lazy family (<class 'sage.combinat.permutation.Permutations'>(i))_{i in [2, 3, 4]}
95
sage: U3.cardinality()
96
32
97
sage: it = iter(U3)
98
sage: [it.next(), it.next(), it.next(), it.next(), it.next(), it.next()]
99
[[1, 2], [2, 1], [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1]]
100
sage: U3.unrank(18)
101
[2, 4, 1, 3]
102
103
This allows for infinite unions::
104
105
sage: U4 = DisjointUnionEnumeratedSets(
106
... Family(NonNegativeIntegers(), Permutations))
107
sage: U4
108
Disjoint union of Lazy family (<class 'sage.combinat.permutation.Permutations'>(i))_{i in Non negative integers}
109
sage: U4.cardinality()
110
+Infinity
111
sage: it = iter(U4)
112
sage: [it.next(), it.next(), it.next(), it.next(), it.next(), it.next()]
113
[[], [1], [1, 2], [2, 1], [1, 2, 3], [1, 3, 2]]
114
sage: U4.unrank(18)
115
[2, 3, 1, 4]
116
117
.. warning ::
118
119
Beware that some of the operations assume in that case that infinitely
120
many of the enumerated sets are non empty.
121
122
123
.. rubric:: Experimental options
124
125
We demonstrate the ``keepkey`` option::
126
127
sage: Ukeep = DisjointUnionEnumeratedSets(
128
... Family(range(4), Permutations), keepkey=True)
129
sage: it = iter(Ukeep)
130
sage: [it.next() for i in range(6)]
131
[(0, []), (1, [1]), (2, [1, 2]), (2, [2, 1]), (3, [1, 2, 3]), (3, [1, 3, 2])]
132
sage: type(it.next()[1])
133
<class 'sage.combinat.permutation.StandardPermutations_n_with_category.element_class'>
134
135
We now demonstrate the ``facade`` option::
136
137
sage: UNoFacade = DisjointUnionEnumeratedSets(
138
... Family(range(4), Permutations), facade=False)
139
sage: it = iter(UNoFacade)
140
sage: [it.next() for i in range(6)]
141
[[], [1], [1, 2], [2, 1], [1, 2, 3], [1, 3, 2]]
142
sage: el = it.next(); el
143
[2, 1, 3]
144
sage: type(el)
145
<type 'sage.structure.element_wrapper.ElementWrapper'>
146
sage: el.parent() == UNoFacade
147
True
148
sage: elv = el.value; elv
149
[2, 1, 3]
150
sage: type(elv)
151
<class 'sage.combinat.permutation.StandardPermutations_n_with_category.element_class'>
152
153
Possible extensions: the current enumeration order is not suitable
154
for unions of infinite enumerated sets (except possibly for the
155
last one). One could add options to specify alternative enumeration
156
orders (anti-diagonal, round robin, ...) to handle this case.
157
158
159
.. rubric:: Inheriting from ``DisjointUnionEnumeratedSets``
160
161
There are two different use cases for inheriting from
162
:class:`DisjointUnionEnumeratedSets`: writing a parent which
163
happens to be a disjoint union of some known parents, or writing
164
generic disjoint unions for some particular classes of
165
:class:`sage.categories.enumerated_sets.EnumeratedSets`.
166
167
- In the first use case, the input of the ``__init__`` method is
168
most likely different from that of
169
:class:`DisjointUnionEnumeratedSets`. Then, one simply
170
writes the ``__init__`` method as usual::
171
172
sage: class MyUnion(DisjointUnionEnumeratedSets):
173
... def __init__(self):
174
... DisjointUnionEnumeratedSets.__init__(self,
175
... Family([1,2], Permutations))
176
sage: pp = MyUnion()
177
sage: pp.list()
178
[[1], [1, 2], [2, 1]]
179
180
In case the :meth:`__init__` method takes optional arguments,
181
or does some normalization on them, a specific method
182
``__classcall_private__`` is required (see the
183
documentation of :class:`UniqueRepresentation`).
184
185
- In the second use case, the input of the ``__init__`` method
186
is the same as that of :class:`DisjointUnionEnumeratedSets`;
187
one therefore wants to inherit the :meth:`__classcall_private__`
188
method as well, which can be achieved as follows::
189
190
sage: class UnionOfSpecialSets(DisjointUnionEnumeratedSets):
191
... __classcall_private__ = staticmethod(DisjointUnionEnumeratedSets.__classcall_private__)
192
...
193
sage: psp = UnionOfSpecialSets(Family([1,2], Permutations))
194
sage: psp.list()
195
[[1], [1, 2], [2, 1]]
196
197
TESTS::
198
199
sage: TestSuite(U1).run()
200
sage: TestSuite(U2).run()
201
sage: TestSuite(U3).run()
202
sage: TestSuite(U4).run()
203
doctest:...: UserWarning: Disjoint union of Lazy family (<class 'sage.combinat.permutation.Permutations'>(i))_{i in Non negative integers} is an infinite union
204
The default implementation of __contains__ can loop forever. Please overload it.
205
sage: TestSuite(Ukeep).run()
206
sage: TestSuite(UNoFacade).run()
207
208
The following three lines are required for the pickling tests,
209
because the classes ``MyUnion`` and ``UnionOfSpecialSets`` have
210
been defined interactively::
211
212
sage: import __main__
213
sage: __main__.MyUnion = MyUnion
214
sage: __main__.UnionOfSpecialSets = UnionOfSpecialSets
215
216
sage: TestSuite(pp).run()
217
sage: TestSuite(psp).run()
218
219
"""
220
221
@staticmethod
222
def __classcall_private__(cls, fam, facade=True,
223
keepkey=False, category=None):
224
"""
225
Normalization of arguments; see :class:`UniqueRepresentation`.
226
227
TESTS:
228
229
We check that disjoint unions have unique representation::
230
231
sage: U1 = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),
232
... 2: FiniteEnumeratedSet([4,5,6])})
233
sage: U2 = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),
234
... 2: FiniteEnumeratedSet([4,5,6])})
235
sage: U1 == U2
236
True
237
sage: U1 is U2 # indirect doctest
238
True
239
sage: U3 = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),
240
... 2: FiniteEnumeratedSet([4,5])})
241
sage: U1 == U3
242
False
243
"""
244
# facade = options.pop('facade', True);
245
# keepkey = options.pop('keepkey', False);
246
assert(isinstance(facade, bool))
247
assert(isinstance(keepkey, bool))
248
return super(DisjointUnionEnumeratedSets, cls).__classcall__(
249
cls, Family(fam),
250
facade = facade, keepkey = keepkey, category=category)
251
252
253
def __init__(self, family, facade=True, keepkey=False, category = None):
254
"""
255
TESTS::
256
257
sage: U = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),
258
... 2: FiniteEnumeratedSet([4,5,6])})
259
sage: TestSuite(U).run()
260
"""
261
self._family = family
262
self._facade = facade
263
self._keepkey = keepkey
264
if self._is_category_initialized():
265
return
266
if category is None:
267
# try to guess if the result is infinite or not.
268
if self._family in InfiniteEnumeratedSets():
269
category = InfiniteEnumeratedSets()
270
elif self._family.last().cardinality() == Infinity:
271
category = InfiniteEnumeratedSets()
272
else:
273
category = FiniteEnumeratedSets()
274
Parent.__init__(self, category = category)
275
276
def _repr_(self):
277
"""
278
TESTS::
279
280
sage: U = DisjointUnionEnumeratedSets({1: FiniteEnumeratedSet([1,2,3]),
281
... 2: FiniteEnumeratedSet([4,5,6])})
282
sage: U # indirect doctest
283
Disjoint union of Finite family {1: {1, 2, 3}, 2: {4, 5, 6}}
284
"""
285
return "Disjoint union of %s"%self._family
286
287
288
def _is_a(self, x):
289
"""
290
Check if a Sage object belongs to self. This methods is a helper for
291
:meth:`__contains__` and the constructor :meth:`_element_constructor_`.
292
293
EXAMPLES::
294
295
sage: U4 = DisjointUnionEnumeratedSets(
296
... Family(NonNegativeIntegers(), Compositions))
297
sage: U4._is_a(Composition([3,2,1,1]))
298
doctest:...: UserWarning: Disjoint union of Lazy family (<class 'sage.combinat.composition.Compositions'>(i))_{i in Non negative integers} is an infinite union
299
The default implementation of __contains__ can loop forever. Please overload it.
300
True
301
"""
302
if self._keepkey:
303
return (isinstance(x, tuple) and
304
x[0] in self._family.keys() and
305
x[1] in self._family[x[0]])
306
else:
307
from warnings import warn
308
if self._family.cardinality() == Infinity:
309
warn("%s is an infinite union\nThe default implementation of __contains__ can loop forever. Please overload it."%(self))
310
return any(x in a for a in self._family)
311
312
313
def __contains__(self, x):
314
"""
315
.. warning::
316
317
If ``self`` is an infinite union and if the answer is
318
logically False, this will loop forever and never answer
319
``False``. Therefore, a warning is issued.
320
321
EXAMPLES::
322
323
sage: U4 = DisjointUnionEnumeratedSets(
324
... Family(NonNegativeIntegers(), Partitions))
325
sage: Partition([]) in U4
326
doctest:...: UserWarning: Disjoint union of Lazy family (<class 'sage.combinat.partition.Partitions'>(i))_{i in Non negative integers} is an infinite union
327
The default implementation of __contains__ can loop forever. Please overload it.
328
True
329
330
Note: one has to use a different family from the previous one in this
331
file otherwise the warning is not re-issued::
332
333
sage: Partition([3,2,1,1]) in U4
334
True
335
336
The following call will loop forever::
337
338
sage: 2 in U4 # not tested, loop forever
339
"""
340
if self._facade:
341
return self._is_a(x)
342
else:
343
if isinstance(x, self.element_class):
344
return True
345
else:
346
return self._is_a(x)
347
348
def __iter__(self):
349
"""
350
TESTS::
351
352
sage: U4 = DisjointUnionEnumeratedSets(
353
... Family(NonNegativeIntegers(), Permutations))
354
sage: it = iter(U4)
355
sage: [it.next(), it.next(), it.next(), it.next(), it.next(), it.next()]
356
[[], [1], [1, 2], [2, 1], [1, 2, 3], [1, 3, 2]]
357
358
sage: U4 = DisjointUnionEnumeratedSets(
359
... Family(NonNegativeIntegers(), Permutations),
360
... keepkey=True, facade=False)
361
sage: it = iter(U4)
362
sage: [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])]
364
sage: el = it.next(); el.parent() == U4
365
True
366
sage: el.value == (3, Permutation([2,1,3]))
367
True
368
"""
369
for k in self._family.keys():
370
for el in self._family[k]:
371
if self._keepkey:
372
el = (k, el)
373
if self._facade:
374
yield el
375
else:
376
yield self.element_class(self, el) # Bypass correctness tests
377
378
def an_element(self):
379
"""
380
Returns an element of this disjoint union, as per :meth:`Sets.ParentMethods.an_element`.
381
382
EXAMPLES::
383
384
sage: U4 = DisjointUnionEnumeratedSets(
385
... Family([3, 5, 7], Permutations))
386
sage: U4.an_element()
387
[1, 2, 3]
388
"""
389
return self._an_element_from_iterator()
390
391
@cached_method
392
def cardinality(self):
393
"""
394
Returns the cardinality of this disjoint union.
395
396
EXAMPLES:
397
398
For finite disjoint unions, the cardinality is computed by
399
summing the cardinalities of the enumerated sets::
400
401
sage: U = DisjointUnionEnumeratedSets(Family([0,1,2,3], Permutations))
402
sage: U.cardinality()
403
10
404
405
For infinite disjoint unions, this makes the assumption that
406
the result is infinite::
407
408
sage: U = DisjointUnionEnumeratedSets(
409
... Family(NonNegativeIntegers(), Permutations))
410
sage: U.cardinality()
411
+Infinity
412
413
.. warning::
414
415
as pointed out in the main documentation, it is
416
possible to construct examples where this is incorrect::
417
418
sage: U = DisjointUnionEnumeratedSets(
419
... Family(NonNegativeIntegers(), lambda x: []))
420
sage: U.cardinality() # Should be 0!
421
+Infinity
422
423
"""
424
if self._family.cardinality() == Infinity:
425
return Infinity
426
return sum(set.cardinality() for set in self._family)
427
428
@lazy_attribute
429
def _element_constructor_(self):
430
"""
431
TESTS::
432
433
sage: U = DisjointUnionEnumeratedSets(
434
... Family([1,2,3], Partitions), facade=False)
435
sage: 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}>
437
sage: U = DisjointUnionEnumeratedSets(
438
... Family([1,2,3], Partitions), facade=True)
439
sage: U._element_constructor_
440
Traceback (most recent call last):
441
...
442
AttributeError: 'DisjointUnionEnumeratedSets_with_category' object has no attribute '_element_constructor_'
443
"""
444
if not self._facade:
445
return self._element_constructor_default
446
else:
447
return NotImplemented
448
449
def _element_constructor_default(self, el):
450
r"""
451
TESTS::
452
453
sage: U = DisjointUnionEnumeratedSets(
454
... Family([1,2,3], Partitions), facade=False)
455
sage: U([1]) # indirect doctest
456
[1]
457
sage: U([2,1]) # indirect doctest
458
[2, 1]
459
sage: U([1,3,2]) # indirect doctest
460
Traceback (most recent call last):
461
...
462
ValueError: 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}
463
464
sage: U = DisjointUnionEnumeratedSets(
465
... Family([1,2,3], Partitions), keepkey=True, facade=False)
466
sage: U((1, [1])) # indirect doctest
467
(1, [1])
468
sage: U((3,[2,1])) # indirect doctest
469
(3, [2, 1])
470
sage: U((4,[2,1])) # indirect doctest
471
Traceback (most recent call last):
472
...
473
ValueError: 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
"""
475
if isinstance(el, self.element_class):
476
el = el.value
477
if self._is_a(el):
478
return self.element_class(self, el)
479
else:
480
raise ValueError("Value %s does not belong to %s"%(el, self))
481
482
@lazy_attribute
483
def Element(self):
484
"""
485
TESTS::
486
487
sage: U = DisjointUnionEnumeratedSets(
488
... Family([1,2,3], Partitions), facade=False)
489
sage: U.Element
490
<type 'sage.structure.element_wrapper.ElementWrapper'>
491
sage: U = DisjointUnionEnumeratedSets(
492
... Family([1,2,3], Partitions), facade=True)
493
sage: U.Element
494
Traceback (most recent call last):
495
...
496
AttributeError: 'DisjointUnionEnumeratedSets_with_category' object has no attribute 'Element'
497
"""
498
if not self._facade:
499
return ElementWrapper
500
else:
501
return NotImplemented
502
503