Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/combinat/necklace.py
4057 views
1
"""
2
Necklaces
3
4
The algorithm used in this file comes from
5
6
- Sawada, Joe. "A fast algorithm to generate necklaces with fixed content", Source
7
Theoretical Computer Science archive Volume 301 , Issue 1-3 (May
8
2003)
9
"""
10
#*****************************************************************************
11
# Copyright (C) 2007 Mike Hansen <[email protected]>,
12
#
13
# Distributed under the terms of the GNU General Public License (GPL)
14
#
15
# This code is distributed in the hope that it will be useful,
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18
# General Public License for more details.
19
#
20
# The full text of the GPL is available at:
21
#
22
# http://www.gnu.org/licenses/
23
#*****************************************************************************
24
from sage.combinat.composition import Composition, Composition_class
25
from combinat import CombinatorialClass
26
from sage.rings.arith import euler_phi,factorial, divisors, gcd
27
from sage.rings.integer import Integer
28
from sage.misc.misc import prod
29
from sage.combinat.misc import DoublyLinkedList
30
31
def Necklaces(e):
32
"""
33
Returns the combinatorial class of necklaces with evaluation e.
34
35
EXAMPLES::
36
37
sage: Necklaces([2,1,1])
38
Necklaces with evaluation [2, 1, 1]
39
sage: Necklaces([2,1,1]).cardinality()
40
3
41
sage: Necklaces([2,1,1]).first()
42
[1, 1, 2, 3]
43
sage: Necklaces([2,1,1]).last()
44
[1, 2, 1, 3]
45
sage: Necklaces([2,1,1]).list()
46
[[1, 1, 2, 3], [1, 1, 3, 2], [1, 2, 1, 3]]
47
"""
48
return Necklaces_evaluation(e)
49
50
class Necklaces_evaluation(CombinatorialClass):
51
def __init__(self, e):
52
"""
53
TESTS::
54
55
sage: N = Necklaces([2,2,2])
56
sage: N == loads(dumps(N))
57
True
58
"""
59
if isinstance(e, Composition_class):
60
self.e = e
61
else:
62
self.e = Composition(e)
63
64
65
def __repr__(self):
66
"""
67
TESTS::
68
69
sage: repr(Necklaces([2,1,1]))
70
'Necklaces with evaluation [2, 1, 1]'
71
"""
72
return "Necklaces with evaluation %s"%self.e
73
74
def __contains__(self, x):
75
"""
76
EXAMPLES::
77
78
sage: [2,1,2,1] in Necklaces([2,2])
79
False
80
sage: [1,2,1,2] in Necklaces([2,2])
81
True
82
sage: [1,1,2,2] in Necklaces([2,2])
83
True
84
sage: all([ n in Necklaces([2,1,3,1]) for n in Necklaces([2,1,3,1])])
85
True
86
"""
87
xl = list(x)
88
n = sum(self.e)
89
e = [0]*len(self.e)
90
if len(xl) != n:
91
return False
92
93
#Check to make sure xl is a list of integers
94
for i in xl:
95
if not isinstance(i, (int, Integer)):
96
return False
97
if i <= 0:
98
return False
99
if i > n:
100
return False
101
e[i-1] += 1
102
103
#Check to make sure the evaluation is the same
104
if e != self.e:
105
return False
106
107
#Check to make sure that x is lexicographically less
108
#than all of its cyclic shifts
109
cyclic_shift = xl[:]
110
for i in range(n - 1):
111
cyclic_shift = cyclic_shift[1:] + cyclic_shift[:1]
112
if cyclic_shift < xl:
113
return False
114
115
return True
116
117
def cardinality(self):
118
"""
119
Returns the number of integer necklaces with the evaluation e.
120
121
EXAMPLES::
122
123
sage: Necklaces([]).cardinality()
124
0
125
sage: Necklaces([2,2]).cardinality()
126
2
127
sage: Necklaces([2,3,2]).cardinality()
128
30
129
130
Check to make sure that the count matches up with the number of
131
Lyndon words generated.
132
133
::
134
135
sage: comps = [[],[2,2],[3,2,7],[4,2]]+Compositions(4).list()
136
sage: ns = [ Necklaces(comp) for comp in comps]
137
sage: all( [ n.cardinality() == len(n.list()) for n in ns] )
138
True
139
"""
140
evaluation = self.e
141
le = list(evaluation)
142
if len(le) == 0:
143
return 0
144
145
n = sum(le)
146
147
return sum([euler_phi(j)*factorial(n/j) / prod([factorial(ni/j) for ni in evaluation]) for j in divisors(gcd(le))])/n
148
149
150
def __iter__(self):
151
"""
152
An iterator for the integer necklaces with evaluation e.
153
154
EXAMPLES::
155
156
sage: Necklaces([]).list() #indirect test
157
[]
158
sage: Necklaces([1]).list() #indirect test
159
[[1]]
160
sage: Necklaces([2]).list() #indirect test
161
[[1, 1]]
162
sage: Necklaces([3]).list() #indirect test
163
[[1, 1, 1]]
164
sage: Necklaces([3,3]).list() #indirect test
165
[[1, 1, 1, 2, 2, 2],
166
[1, 1, 2, 1, 2, 2],
167
[1, 1, 2, 2, 1, 2],
168
[1, 2, 1, 2, 1, 2]]
169
sage: Necklaces([2,1,3]).list() #indirect test
170
[[1, 1, 2, 3, 3, 3],
171
[1, 1, 3, 2, 3, 3],
172
[1, 1, 3, 3, 2, 3],
173
[1, 1, 3, 3, 3, 2],
174
[1, 2, 1, 3, 3, 3],
175
[1, 2, 3, 1, 3, 3],
176
[1, 2, 3, 3, 1, 3],
177
[1, 3, 1, 3, 2, 3],
178
[1, 3, 1, 3, 3, 2],
179
[1, 3, 2, 1, 3, 3]]
180
"""
181
if self.e == []:
182
return
183
for z in _sfc(self.e):
184
yield map(lambda x: x+1, z)
185
186
187
188
##############################
189
#Fast Fixed Content Algorithm#
190
##############################
191
def _ffc(content, equality=False):
192
"""
193
EXAMPLES::
194
195
sage: from sage.combinat.necklace import _ffc
196
sage: list(_ffc([3,3])) #necklaces
197
[[0, 1, 0, 1, 0, 1],
198
[0, 0, 1, 1, 0, 1],
199
[0, 0, 1, 0, 1, 1],
200
[0, 0, 0, 1, 1, 1]]
201
sage: list(_ffc([3,3], equality=True)) #Lyndon words
202
[[0, 0, 1, 1, 0, 1], [0, 0, 1, 0, 1, 1], [0, 0, 0, 1, 1, 1]]
203
"""
204
e = list(content)
205
a = [len(e)-1]*sum(e)
206
r = [0] * sum(e)
207
a[0] = 0
208
e[0] -= 1
209
k = len(e)
210
211
rng_k = range(k)
212
rng_k.reverse()
213
dll = DoublyLinkedList(rng_k)
214
if e[0] == 0:
215
dll.hide(0)
216
217
for x in _fast_fixed_content(a, e, 2, 1, k, r, 2, dll, equality=equality):
218
yield x
219
220
def _fast_fixed_content(a, content, t, p, k, r, s, dll, equality=False):
221
"""
222
EXAMPLES::
223
224
sage: from sage.combinat.necklace import _fast_fixed_content
225
sage: from sage.combinat.misc import DoublyLinkedList
226
sage: e = [3,3]
227
sage: a = [len(e)-1]*sum(e)
228
sage: r = [0]*sum(e)
229
sage: a[0] = 0
230
sage: e[0] -= 1
231
sage: k = len(e)
232
sage: dll = DoublyLinkedList(list(reversed(range(k))))
233
sage: if e[0] == 0: dll.hide(0)
234
sage: list(_fast_fixed_content(a,e,2,1,k,r,2,dll))
235
[[0, 1, 0, 1, 0, 1],
236
[0, 0, 1, 1, 0, 1],
237
[0, 0, 1, 0, 1, 1],
238
[0, 0, 0, 1, 1, 1]]
239
sage: list(_fast_fixed_content(a,e,2,1,k,r,2,dll,True))
240
[[0, 0, 1, 1, 0, 1], [0, 0, 1, 0, 1, 1], [0, 0, 0, 1, 1, 1]]
241
"""
242
n = len(a)
243
if content[k-1] == n - t + 1:
244
if content[k-1] == r[t-p-1]:
245
if equality:
246
if n == p:
247
yield a
248
else:
249
if n % p == 0:
250
yield a
251
elif content[k-1] > r[t-p-1]:
252
yield a
253
elif content[0] != n-t+1:
254
j = dll.head()
255
sp = s
256
while j != 'end' and j >= a[t-p-1]:
257
#print s, j
258
r[s-1] = t-s
259
a[t-1] = j
260
content[j] -= 1
261
262
if content[j] == 0:
263
dll.hide(j)
264
265
if j != k-1:
266
sp = t+1
267
268
if j == a[t-p-1]:
269
for x in _fast_fixed_content(a[:], content, t+1, p+0, k, r, sp, dll, equality=equality):
270
yield x
271
else:
272
for x in _fast_fixed_content(a[:], content, t+1, t+0, k, r, sp, dll, equality=equality):
273
yield x
274
275
if content[j] == 0:
276
dll.unhide(j)
277
278
content[j] += 1
279
j = dll.next(j)
280
a[t-1] = k-1
281
return
282
283
284
################################
285
# List Fixed Content Algorithm #
286
################################
287
def _lfc(content, equality=False):
288
"""
289
EXAMPLES::
290
291
sage: from sage.combinat.necklace import _lfc
292
sage: list(_lfc([3,3])) #necklaces
293
[[0, 1, 0, 1, 0, 1],
294
[0, 0, 1, 1, 0, 1],
295
[0, 0, 1, 0, 1, 1],
296
[0, 0, 0, 1, 1, 1]]
297
sage: list(_lfc([3,3], equality=True)) #Lyndon words
298
[[0, 0, 1, 1, 0, 1], [0, 0, 1, 0, 1, 1], [0, 0, 0, 1, 1, 1]]
299
"""
300
content = list(content)
301
a = [0]*sum(content)
302
content[0] -= 1
303
k = len(content)
304
305
rng_k = range(k)
306
rng_k.reverse()
307
dll = DoublyLinkedList(rng_k)
308
309
if content[0] == 0:
310
dll.hide(0)
311
312
for z in _list_fixed_content(a, content, 2, 1, k, dll, equality=equality):
313
yield z
314
315
def _list_fixed_content(a, content, t, p, k, dll, equality=False):
316
"""
317
EXAMPLES::
318
319
sage: from sage.combinat.necklace import _list_fixed_content
320
sage: from sage.combinat.misc import DoublyLinkedList
321
sage: e = [3,3]
322
sage: a = [0]*sum(e)
323
sage: e[0] -= 1
324
sage: k = len(e)
325
sage: dll = DoublyLinkedList(list(reversed(range(k))))
326
sage: if e[0] == 0: dll.hide(0)
327
sage: list(_list_fixed_content(a,e,2,1,k,dll))
328
[[0, 1, 0, 1, 0, 1],
329
[0, 0, 1, 1, 0, 1],
330
[0, 0, 1, 0, 1, 1],
331
[0, 0, 0, 1, 1, 1]]
332
sage: list(_list_fixed_content(a,e,2,1,k,dll,True))
333
[[0, 0, 1, 1, 0, 1], [0, 0, 1, 0, 1, 1], [0, 0, 0, 1, 1, 1]]
334
"""
335
n = len(a)
336
if t > n:
337
if equality:
338
if n == p:
339
yield a
340
else:
341
if n % p == 0:
342
yield a
343
else:
344
j = dll.head()
345
while j != 'end' and j >= a[t-p-1]:
346
a[t-1] = j
347
content[j] -= 1
348
349
if content[j] == 0:
350
dll.hide(j)
351
352
if j == a[t-p-1]:
353
for z in _list_fixed_content(a[:], content[:], t+1, p+0, k, dll, equality=equality):
354
yield z
355
else:
356
for z in _list_fixed_content(a[:], content[:], t+1, t+0, k, dll, equality=equality):
357
yield z
358
359
if content[j] == 0:
360
dll.unhide(j)
361
362
content[j] += 1
363
j = dll.next(j)
364
365
366
367
################################
368
#Simple Fixed Content Algorithm#
369
################################
370
def _sfc(content, equality=False):
371
"""
372
This function sets things up and calls _simple_fixed_content.
373
374
EXAMPLES::
375
376
sage: from sage.combinat.necklace import _sfc
377
sage: list(_sfc([3,3])) #necklaces
378
[[0, 0, 0, 1, 1, 1],
379
[0, 0, 1, 0, 1, 1],
380
[0, 0, 1, 1, 0, 1],
381
[0, 1, 0, 1, 0, 1]]
382
sage: list(_sfc([3,3], equality=True)) #Lyndon words
383
[[0, 0, 0, 1, 1, 1], [0, 0, 1, 0, 1, 1], [0, 0, 1, 1, 0, 1]]
384
"""
385
content = list(content)
386
a = [0]*sum(content)
387
content[0] -= 1
388
k = len(content)
389
return _simple_fixed_content(a, content, 2, 1, k, equality=equality)
390
391
def _simple_fixed_content(a, content, t, p, k, equality=False):
392
"""
393
EXAMPLES::
394
395
sage: from sage.combinat.necklace import _simple_fixed_content
396
sage: content = [3,3]
397
sage: a = [0]*sum(content)
398
sage: content[0] -= 1
399
sage: k = len(content); k
400
2
401
sage: list(_simple_fixed_content(a, content, 2, 1, k))
402
[[0, 0, 0, 1, 1, 1],
403
[0, 0, 1, 0, 1, 1],
404
[0, 0, 1, 1, 0, 1],
405
[0, 1, 0, 1, 0, 1]]
406
sage: list(_simple_fixed_content(a, content, 2, 1, k, True))
407
[[0, 0, 0, 1, 1, 1], [0, 0, 1, 0, 1, 1], [0, 0, 1, 1, 0, 1]]
408
"""
409
n = len(a)
410
if t > n:
411
if equality:
412
if n == p:
413
yield a
414
else:
415
if n % p == 0:
416
yield a
417
else:
418
r = range(a[t-p-1],k)
419
for j in r:
420
if content[j] > 0:
421
a[t-1] = j
422
content[j] -= 1
423
if j == a[t-p-1]:
424
for z in _simple_fixed_content(a[:], content, t+1, p+0, k, equality=equality):
425
yield z
426
else:
427
for z in _simple_fixed_content(a[:], content, t+1, t+0, k, equality=equality):
428
yield z
429
content[j] += 1
430
431
432
def _lyn(w):
433
"""
434
Returns the length of the longest prefix of w that is a Lyndon
435
word.
436
437
EXAMPLES::
438
439
sage: import sage.combinat.necklace as necklace
440
sage: necklace._lyn([0,1,1,0,0,1,2])
441
3
442
sage: necklace._lyn([0,0,0,1])
443
4
444
sage: necklace._lyn([2,1,0,0,2,2,1])
445
1
446
"""
447
448
p = 1
449
k = max(w)+1
450
for i in range(1, len(w)):
451
b = w[i]
452
a = w[:i]
453
if b < a[i-p] or b > k-1:
454
return p
455
elif b == a[i-p]:
456
pass
457
else:
458
p = i+1
459
return p
460
461
462
463
464
465