Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/combinat/derangements.py
8817 views
1
"""
2
Derangements
3
4
AUTHORS:
5
6
- Alasdair McAndrew (2010-05): Initial version
7
- Travis Scrimshaw (2013-03-30): Put derangements into category framework
8
"""
9
10
#*****************************************************************************
11
# Copyright (C) 2010 Alasdair McAndrew <[email protected]>,
12
# 2013 Travis Scrimshaw <[email protected]>
13
#
14
# Distributed under the terms of the GNU General Public License (GPL)
15
#
16
# This code is distributed in the hope that it will be useful,
17
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19
# General Public License for more details.
20
#
21
# The full text of the GPL is available at:
22
#
23
# http://www.gnu.org/licenses/
24
#*****************************************************************************
25
26
from sage.structure.parent import Parent
27
from sage.structure.unique_representation import UniqueRepresentation
28
from sage.structure.element import Element
29
from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets
30
from sage.misc.misc import prod
31
from sage.misc.prandom import random, randint
32
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
33
from sage.rings.all import ZZ, QQ
34
from sage.rings.integer import Integer
35
from sage.combinat.combinat import CombinatorialObject
36
from sage.combinat.permutation import Permutation, Permutations
37
38
class Derangement(CombinatorialObject, Element):
39
r"""
40
A derangement.
41
42
A derangement on a set `S` is a permutation `\sigma` such that `\sigma(x)
43
\neq x` for all `x \in S`, i.e. `\sigma` is a permutation of `S` with no
44
fixed points.
45
"""
46
def __init__(self, parent, lst):
47
"""
48
Initialize ``self``.
49
50
EXAMPLES::
51
52
sage: D = Derangements(4)
53
sage: elt = D([4,3,2,1])
54
sage: TestSuite(elt).run()
55
"""
56
CombinatorialObject.__init__(self, lst)
57
Element.__init__(self, parent)
58
59
def to_permutation(self):
60
"""
61
Return the permutation corresponding to ``self``.
62
63
EXAMPLES::
64
65
sage: D = Derangements(4)
66
sage: p = D([4,3,2,1]).to_permutation(); p
67
[4, 3, 2, 1]
68
sage: type(p)
69
<class 'sage.combinat.permutation.StandardPermutations_all_with_category.element_class'>
70
sage: D = Derangements([1, 3, 3, 4])
71
sage: D[0].to_permutation()
72
Traceback (most recent call last):
73
...
74
ValueError: Can only convert to a permutation for derangements of [1, 2, ..., n]
75
"""
76
if self.parent()._set != tuple(range(1, len(self)+1)):
77
raise ValueError("Can only convert to a permutation for derangements of [1, 2, ..., n]")
78
return Permutation(list(self))
79
80
class Derangements(Parent, UniqueRepresentation):
81
r"""
82
The class of all derangements of a set or multiset.
83
84
A derangement on a set `S` is a permutation `\sigma` such that `\sigma(x)
85
\neq x` for all `x \in S`, i.e. `\sigma` is a permutation of `S` with no
86
fixed points.
87
88
For an integer, or a list or string with all elements
89
distinct, the derangements are obtained by a standard result described
90
in [DerUB]_. For a list or string with repeated elements, the derangements
91
are formed by computing all permutations of the input and discarding all
92
non-derangements.
93
94
INPUT:
95
96
- ``x`` -- Can be an integer which corresponds to derangements of
97
`\{1, 2, 3, \ldots, x\}`, a list, or a string
98
99
REFERENCES:
100
101
.. [DerUB] http://www.u-bourgogne.fr/LE2I/jl.baril/derange.pdf
102
103
- :wikipedia:`Derangement`
104
105
EXAMPLES::
106
107
sage: D1 = Derangements([2,3,4,5])
108
sage: D1.list()
109
[[3, 4, 5, 2],
110
[5, 4, 2, 3],
111
[3, 5, 2, 4],
112
[4, 5, 3, 2],
113
[4, 2, 5, 3],
114
[5, 2, 3, 4],
115
[5, 4, 3, 2],
116
[4, 5, 2, 3],
117
[3, 2, 5, 4]]
118
sage: D1.cardinality()
119
9
120
sage: D1.random_element() # random
121
[4, 2, 5, 3]
122
sage: D2 = Derangements([1,2,3,1,2,3])
123
sage: D2.cardinality()
124
10
125
sage: D2.list()
126
[[2, 1, 1, 3, 3, 2],
127
[2, 1, 2, 3, 3, 1],
128
[2, 3, 1, 2, 3, 1],
129
[2, 3, 1, 3, 1, 2],
130
[2, 3, 2, 3, 1, 1],
131
[3, 1, 1, 2, 3, 2],
132
[3, 1, 2, 2, 3, 1],
133
[3, 1, 2, 3, 1, 2],
134
[3, 3, 1, 2, 1, 2],
135
[3, 3, 2, 2, 1, 1]]
136
sage: D2.random_element() # random
137
[2, 3, 1, 3, 1, 2]
138
"""
139
@staticmethod
140
def __classcall_private__(cls, x):
141
"""
142
Normalize ``x`` to ensure a unique representation.
143
144
EXAMPLES::
145
146
sage: D = Derangements(4)
147
sage: D2 = Derangements([1, 2, 3, 4])
148
sage: D3 = Derangements((1, 2, 3, 4))
149
sage: D is D2
150
True
151
sage: D is D3
152
True
153
"""
154
if x in ZZ:
155
x = range(1, x+1)
156
return super(Derangements, cls).__classcall__(cls, tuple(x))
157
158
def __init__(self, x):
159
"""
160
Initalize ``self``.
161
162
EXAMPLES::
163
164
sage: D = Derangements(4)
165
sage: TestSuite(D).run()
166
sage: D = Derangements('abcd')
167
sage: TestSuite(D).run()
168
sage: D = Derangements([2, 2, 1, 1])
169
sage: TestSuite(D).run()
170
"""
171
Parent.__init__(self, category=FiniteEnumeratedSets())
172
self._set = x
173
self.__multi = len(set(x)) < len(x)
174
175
def _repr_(self):
176
"""
177
Return a string representation of ``self``.
178
179
EXAMPLES::
180
181
sage: Derangements(4)
182
Derangements of the set [1, 2, 3, 4]
183
sage: Derangements('abcd')
184
Derangements of the set ['a', 'b', 'c', 'd']
185
sage: Derangements([2,2,1,1])
186
Derangements of the multiset [2, 2, 1, 1]
187
"""
188
if self.__multi:
189
return "Derangements of the multiset %s"%list(self._set)
190
return "Derangements of the set %s"%list(self._set)
191
192
def _element_constructor_(self, der):
193
"""
194
Construct an element of ``self`` from ``der``.
195
196
EXAMPLES::
197
198
sage: D = Derangements(4)
199
sage: elt = D([3,1,4,2]); elt
200
[3, 1, 4, 2]
201
sage: elt.parent() is D
202
True
203
"""
204
if isinstance(der, Derangement):
205
if der.parent() is self:
206
return der
207
raise ValueError("Cannot convert %s to an element of %s"%(der, self))
208
return self.element_class(self, der)
209
210
Element = Derangement
211
212
def __iter__(self):
213
"""
214
Iterate through ``self``.
215
216
EXAMPLES::
217
218
sage: D = Derangements(4)
219
sage: D.list() # indirect doctest
220
[[2, 3, 4, 1],
221
[4, 3, 1, 2],
222
[2, 4, 1, 3],
223
[3, 4, 2, 1],
224
[3, 1, 4, 2],
225
[4, 1, 2, 3],
226
[4, 3, 2, 1],
227
[3, 4, 1, 2],
228
[2, 1, 4, 3]]
229
sage: D = Derangements([1,44,918,67])
230
sage: D.list()
231
[[44, 918, 67, 1],
232
[67, 918, 1, 44],
233
[44, 67, 1, 918],
234
[918, 67, 44, 1],
235
[918, 1, 67, 44],
236
[67, 1, 44, 918],
237
[67, 918, 44, 1],
238
[918, 67, 1, 44],
239
[44, 1, 67, 918]]
240
sage: D = Derangements(['A','AT','CAT','CATS'])
241
sage: D.list()
242
[['AT', 'CAT', 'CATS', 'A'],
243
['CATS', 'CAT', 'A', 'AT'],
244
['AT', 'CATS', 'A', 'CAT'],
245
['CAT', 'CATS', 'AT', 'A'],
246
['CAT', 'A', 'CATS', 'AT'],
247
['CATS', 'A', 'AT', 'CAT'],
248
['CATS', 'CAT', 'AT', 'A'],
249
['CAT', 'CATS', 'A', 'AT'],
250
['AT', 'A', 'CATS', 'CAT']]
251
sage: D = Derangements('CART')
252
sage: D.list()
253
[['A', 'R', 'T', 'C'],
254
['T', 'R', 'C', 'A'],
255
['A', 'T', 'C', 'R'],
256
['R', 'T', 'A', 'C'],
257
['R', 'C', 'T', 'A'],
258
['T', 'C', 'A', 'R'],
259
['T', 'R', 'A', 'C'],
260
['R', 'T', 'C', 'A'],
261
['A', 'C', 'T', 'R']]
262
sage: D = Derangements([1,1,2,2,3,3])
263
sage: D.list()
264
[[2, 2, 3, 3, 1, 1],
265
[2, 3, 1, 3, 1, 2],
266
[2, 3, 1, 3, 2, 1],
267
[2, 3, 3, 1, 1, 2],
268
[2, 3, 3, 1, 2, 1],
269
[3, 2, 1, 3, 1, 2],
270
[3, 2, 1, 3, 2, 1],
271
[3, 2, 3, 1, 1, 2],
272
[3, 2, 3, 1, 2, 1],
273
[3, 3, 1, 1, 2, 2]]
274
sage: D = Derangements('SATTAS')
275
sage: D.list()
276
[['A', 'S', 'S', 'A', 'T', 'T'],
277
['A', 'S', 'A', 'S', 'T', 'T'],
278
['A', 'T', 'S', 'S', 'T', 'A'],
279
['A', 'T', 'S', 'A', 'S', 'T'],
280
['A', 'T', 'A', 'S', 'S', 'T'],
281
['T', 'S', 'S', 'A', 'T', 'A'],
282
['T', 'S', 'A', 'S', 'T', 'A'],
283
['T', 'S', 'A', 'A', 'S', 'T'],
284
['T', 'T', 'S', 'A', 'S', 'A'],
285
['T', 'T', 'A', 'S', 'S', 'A']]
286
sage: D = Derangements([1,1,2,2,2])
287
sage: D.list()
288
[]
289
"""
290
if self.__multi:
291
for p in Permutations(self._set):
292
if not self._fixed_point(p):
293
yield self.element_class(self, list(p))
294
else:
295
for d in self._iter_der(len(self._set)):
296
yield self.element_class(self, [self._set[i-1] for i in d])
297
298
def _iter_der(self, n):
299
r"""
300
Iterate through all derangements of the list `[1, 2, 3, \ldots, n]`
301
using the method given in [DerUB]_.
302
303
EXAMPLES::
304
305
sage: D = Derangements(4)
306
sage: list(D._iter_der(4))
307
[[2, 3, 4, 1],
308
[4, 3, 1, 2],
309
[2, 4, 1, 3],
310
[3, 4, 2, 1],
311
[3, 1, 4, 2],
312
[4, 1, 2, 3],
313
[4, 3, 2, 1],
314
[3, 4, 1, 2],
315
[2, 1, 4, 3]]
316
"""
317
if n <= 1:
318
return
319
elif n == 2:
320
yield [2,1]
321
elif n == 3:
322
yield [2,3,1]
323
yield [3,1,2]
324
elif n >= 4:
325
for d in self._iter_der(n-1):
326
for i in xrange(1, n):
327
s = d[:]
328
ii = d.index(i)
329
s[ii] = n
330
yield s + [i]
331
for d in self._iter_der(n-2):
332
for i in xrange(1, n):
333
s = d[:]
334
s = map(lambda x: x >= i and x+1 or x,s)
335
s.insert(i-1, n)
336
yield s + [i]
337
338
def _fixed_point(self, a):
339
"""
340
Return ``True`` if ``a`` has a point in common with ``self._set``.
341
342
EXAMPLES::
343
344
sage: D = Derangements(5)
345
sage: D._fixed_point([3,1,2,5,4])
346
False
347
sage: D._fixed_point([5,4,3,2,1])
348
True
349
"""
350
return any([x == y for (x, y) in zip(a, self._set)])
351
352
def _count_der(self, n):
353
"""
354
Count the number of derangements of `n` using the recursion
355
`D_2 = 1, D_3 = 2, D_n = (n-1) (D_{n-1} + D_{n-2})`.
356
357
EXAMPLES::
358
359
sage: D = Derangements(5)
360
sage: D._count_der(2)
361
1
362
sage: D._count_der(3)
363
2
364
sage: D._count_der(5)
365
44
366
"""
367
if n <= 1:
368
return Integer(0)
369
if n == 2:
370
return Integer(1)
371
if n == 3:
372
return Integer(2)
373
# n >= 4
374
last = Integer(2)
375
second_last = Integer(1)
376
for i in range(4, n+1):
377
current = (i-1) * (last + second_last)
378
second_last = last
379
last = current
380
return last
381
382
def cardinality(self):
383
r"""
384
Counts the number of derangements of a positive integer, a
385
list, or a string. The list or string may contain repeated
386
elements. If an integer `n` is given, the the value returned
387
is the number of derangements of `[1, 2, 3, \ldots, n]`.
388
389
For an integer, or a list or string with all elements
390
distinct, the value is obtained by the standard result
391
`D_2 = 1, D_3 = 2, D_n = (n-1) (D_{n-1} + D_{n-2})`.
392
393
For a list or string with repeated elements, the number of
394
derangements is computed by Macmahon's theorem. If the numbers
395
of repeated elements are `a_1, a_2, \ldots, a_k` then the number
396
of derangements is given by the coefficient of `x_1 x_2 \cdots
397
x_k` in the expansion of `\prod_{i=0}^k (S - s_i)^{a_i}` where
398
`S = x_1 + x_2 + \cdots + x_k`.
399
400
EXAMPLES::
401
402
sage: D = Derangements(5)
403
sage: D.cardinality()
404
44
405
sage: D = Derangements([1,44,918,67,254])
406
sage: D.cardinality()
407
44
408
sage: D = Derangements(['A','AT','CAT','CATS','CARTS'])
409
sage: D.cardinality()
410
44
411
sage: D = Derangements('UNCOPYRIGHTABLE')
412
sage: D.cardinality()
413
481066515734
414
sage: D = Derangements([1,1,2,2,3,3])
415
sage: D.cardinality()
416
10
417
sage: D = Derangements('SATTAS')
418
sage: D.cardinality()
419
10
420
sage: D = Derangements([1,1,2,2,2])
421
sage: D.cardinality()
422
0
423
"""
424
if self.__multi:
425
sL = set(self._set)
426
A = [self._set.count(i) for i in sL]
427
R = PolynomialRing(QQ, 'x', len(A))
428
S = sum(i for i in R.gens())
429
e = prod((S-x)**y for (x, y) in zip(R.gens(), A))
430
return Integer(e.coefficient(dict([(x, y) for (x, y) in zip(R.gens(), A)])))
431
return self._count_der(len(self._set))
432
433
def _rand_der(self):
434
"""
435
Produces a random derangement of `[1, 2, \ldots, n]` This is an
436
implementention of the algorithm described by Martinez et. al. in
437
[Martinez08]_.
438
439
EXAMPLES::
440
441
sage: D = Derangements(4)
442
sage: D._rand_der()
443
[2, 3, 4, 1]
444
"""
445
n = len(self._set)
446
A = range(1, n+1)
447
mark = [x<0 for x in A]
448
i,u = n,n
449
while u >= 2:
450
if not(mark[i-1]):
451
while True:
452
j = randint(1,i-1)
453
if not(mark[j-1]):
454
A[i-1], A[j-1] = A[j-1], A[i-1]
455
break
456
p = random()
457
if p < (u-1) * self._count_der(u-2) // self._count_der(u):
458
mark[j-1] = True
459
u -= 1
460
u -= 1
461
i -= 1
462
return A
463
464
def random_element(self):
465
r"""
466
Produces all derangements of a positive integer, a list, or
467
a string. The list or string may contain repeated elements.
468
If an integer `n` is given, then a random
469
derangements of `[1, 2, 3, \ldots, n]` is returned
470
471
For an integer, or a list or string with all elements
472
distinct, the value is obtained by an algorithm described in
473
[Martinez08]_. For a list or string with repeated elements the
474
derangement is formed by choosing an element at random from the list of
475
all possible derangements.
476
477
OUTPUT:
478
479
A single list or string containing a derangement, or an
480
empty list if there are no derangements.
481
482
REFERENCES:
483
484
.. [Martinez08]
485
http://www.siam.org/proceedings/analco/2008/anl08_022martinezc.pdf
486
487
EXAMPLES::
488
489
sage: D = Derangements(4)
490
sage: D.random_element() # random
491
[2, 3, 4, 1]
492
sage: D = Derangements(['A','AT','CAT','CATS','CARTS','CARETS'])
493
sage: D.random_element() # random
494
['AT', 'CARTS', 'A', 'CAT', 'CARETS', 'CATS']
495
sage: D = Derangements('UNCOPYRIGHTABLE')
496
sage: D.random_element() # random
497
['C', 'U', 'I', 'H', 'O', 'G', 'N', 'B', 'E', 'L', 'A', 'R', 'P', 'Y', 'T']
498
sage: D = Derangements([1,1,1,1,2,2,2,2,3,3,3,3])
499
sage: D.random_element() # random
500
[3, 2, 2, 3, 1, 3, 1, 3, 2, 1, 1, 2]
501
sage: D = Derangements('ESSENCES')
502
sage: D.random_element() # random
503
['N', 'E', 'E', 'C', 'S', 'S', 'S', 'E']
504
sage: D = Derangements([1,1,2,2,2])
505
sage: D.random_element()
506
[]
507
"""
508
if self.__multi:
509
L = list(self)
510
if len(L) == 0:
511
return self.element_class(self, [])
512
i = randint(0, len(L))
513
return L[i]
514
temp = self._rand_der()
515
return self.element_class(self, [self._set[i-1] for i in temp])
516
517
518