Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/combinat/dyck_word.py
8817 views
1
r"""
2
Dyck Words
3
4
A class of an object enumerated by the
5
:func:`Catalan numbers<sage.combinat.combinat.catalan_number>`,
6
see [Sta1999]_, [Sta]_ for details.
7
8
AUTHORS:
9
10
- Mike Hansen
11
12
- Dan Drake (2008--05-30): DyckWordBacktracker support
13
14
- Florent Hivert (2009--02-01): Bijections with NonDecreasingParkingFunctions
15
16
- Christian Stump (2011--12): added combinatorial maps and statistics
17
18
- Mike Zabrocki:
19
20
* (2012--10): added pretty print, characteristic function, more functions
21
* (2013--01): added inverse of area/dinv, bounce/area map
22
23
- Jean--Baptiste Priez, Travis Scrimshaw (2013--05-17): Added ASCII art
24
25
- Travis Scrimshaw (2013--07-09): Removed ``CombinatorialClass`` and added
26
global options.
27
28
REFERENCES:
29
30
.. [Sta1999] R. Stanley, Enumerative Combinatorics, Volume 2.
31
Cambridge University Press, 2001.
32
33
.. [Sta] R. Stanley, electronic document containing the list
34
of Catalan objects at http://www-math.mit.edu/~rstan/ec/catalan.pdf
35
36
.. [Hag2008] The `q,t` -- Catalan Numbers and the Space of Diagonal Harmonics:
37
With an Appendix on the Combinatorics of Macdonald Polynomials, James Haglund,
38
University of Pennsylvania, Philadelphia -- AMS, 2008, 167 pp.
39
"""
40
41
#*****************************************************************************
42
# Copyright (C) 2007 Mike Hansen <[email protected]>,
43
#
44
# Distributed under the terms of the GNU General Public License (GPL)
45
#
46
# This code is distributed in the hope that it will be useful,
47
# but WITHOUT ANY WARRANTY; without even the implied warranty of
48
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
49
# General Public License for more details.
50
#
51
# The full text of the GPL is available at:
52
#
53
# http://www.gnu.org/licenses/
54
#*****************************************************************************
55
56
from combinat import CombinatorialObject, catalan_number
57
from sage.combinat.combinatorial_map import combinatorial_map
58
from backtrack import GenericBacktracker
59
60
from sage.structure.global_options import GlobalOptions
61
from sage.structure.parent import Parent
62
from sage.structure.element import Element
63
from sage.structure.unique_representation import UniqueRepresentation
64
from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets
65
from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets
66
from sage.categories.all import Posets
67
68
from sage.rings.all import QQ
69
from sage.combinat.permutation import Permutation, Permutations
70
from sage.combinat.words.word import Word
71
from sage.combinat.alternating_sign_matrix import AlternatingSignMatrices
72
from sage.misc.latex import latex
73
from sage.misc.classcall_metaclass import ClasscallMetaclass
74
from sage.misc.superseded import deprecated_function_alias
75
76
77
DyckWordOptions=GlobalOptions(name='Dyck words',
78
doc=r"""
79
Set and display the global options for Dyck words. If no parameters
80
are set, then the function returns a copy of the options dictionary.
81
82
The ``options`` to Dyck words can be accessed as the method
83
:obj:`DyckWords.global_options` of :class:`DyckWords` and
84
related parent classes.
85
""",
86
end_doc=r"""
87
EXAMPLES::
88
89
sage: D = DyckWord([1, 1, 0, 1, 0, 0])
90
sage: D
91
[1, 1, 0, 1, 0, 0]
92
sage: DyckWords.global_options(display="lattice")
93
sage: D
94
___
95
_| x
96
| x .
97
| . .
98
sage: DyckWords.global_options(diagram_style="line")
99
sage: D
100
/\/\
101
/ \
102
sage: DyckWords.global_options.reset()
103
""",
104
display=dict(default="list",
105
description='Specifies how Dyck words should be printed',
106
values=dict(list='displayed as a list',
107
lattice='displayed on the lattice defined by ``diagram_style``'),
108
case_sensitive=False),
109
ascii_art=dict(default="path",
110
description='Specifies how the ascii art of Dyck words should be printed',
111
values=dict(path="Using the path string",
112
pretty_output="Using pretty printing"),
113
alias=dict(pretty_print="pretty_output", path_string="path"),
114
case_sensitive=False),
115
diagram_style=dict(default="grid",
116
values=dict(grid='printing as paths on a grid using N and E steps',
117
line='printing as paths on a line using NE and SE steps',),
118
alias={'N-E':'grid', 'NE-SE':'line'},
119
case_sensitive=False),
120
latex_tikz_scale=dict(default=1,
121
description='The default value for the tikz scale when latexed',
122
checker=lambda x: True), # More trouble than it's worth to check
123
latex_diagonal=dict(default=False,
124
description='The default value for displaying the diagonal when latexed',
125
checker=lambda x: isinstance(x, bool)),
126
latex_line_width_scalar=dict(default=2,
127
description='The default value for the line width as a'
128
'multiple of the tikz scale when latexed',
129
checker=lambda x: True), # More trouble than it's worth to check
130
latex_color=dict(default="black",
131
description='The default value for the color when latexed',
132
checker=lambda x: isinstance(x, str)),
133
latex_bounce_path=dict(default=False,
134
description='The default value for displaying the bounce path when latexed',
135
checker=lambda x: isinstance(x, bool)),
136
latex_peaks=dict(default=False,
137
description='The default value for displaying the peaks when latexed',
138
checker=lambda x: isinstance(x, bool)),
139
latex_valleys=dict(default=False,
140
description='The default value for displaying the valleys when latexed',
141
checker=lambda x: isinstance(x, bool)),
142
)
143
144
open_symbol = 1
145
close_symbol = 0
146
147
def replace_parens(x):
148
r"""
149
A map sending ``'('`` to ``open_symbol`` and ``')'`` to
150
``close_symbol``, and raising an error on any input other than
151
``'('`` and ``')'``. The values of the constants ``open_symbol``
152
and ``close_symbol`` are subject to change.
153
154
This is the inverse map of :func:`replace_symbols`.
155
156
INPUT:
157
158
- ``x`` -- either an opening or closing parenthesis
159
160
OUTPUT:
161
162
- If ``x`` is an opening parenthesis, replace ``x`` with the
163
constant ``open_symbol``.
164
165
- If ``x`` is a closing parenthesis, replace ``x`` with the
166
constant ``close_symbol``.
167
168
- Raise a ``ValueError`` if ``x`` is neither an opening nor a
169
closing parenthesis.
170
171
.. SEEALSO:: :func:`replace_symbols`
172
173
EXAMPLES::
174
175
sage: from sage.combinat.dyck_word import replace_parens
176
sage: replace_parens('(')
177
1
178
sage: replace_parens(')')
179
0
180
sage: replace_parens(1)
181
Traceback (most recent call last):
182
...
183
ValueError
184
"""
185
if x == '(':
186
return open_symbol
187
elif x == ')':
188
return close_symbol
189
else:
190
raise ValueError
191
192
def replace_symbols(x):
193
r"""
194
A map sending ``open_symbol`` to ``'('`` and ``close_symbol`` to ``')'``,
195
and raising an error on any input other than ``open_symbol`` and
196
``close_symbol``. The values of the constants ``open_symbol``
197
and ``close_symbol`` are subject to change.
198
199
This is the inverse map of :func:`replace_parens`.
200
201
INPUT:
202
203
- ``x`` -- either ``open_symbol`` or ``close_symbol``.
204
205
OUTPUT:
206
207
- If ``x`` is ``open_symbol``, replace ``x`` with ``'('``.
208
209
- If ``x`` is ``close_symbol``, replace ``x`` with ``')'``.
210
211
- If ``x`` is neither ``open_symbol`` nor ``close_symbol``, a
212
``ValueError`` is raised.
213
214
.. SEEALSO:: :func:`replace_parens`
215
216
EXAMPLES::
217
218
sage: from sage.combinat.dyck_word import replace_symbols
219
sage: replace_symbols(1)
220
'('
221
sage: replace_symbols(0)
222
')'
223
sage: replace_symbols(3)
224
Traceback (most recent call last):
225
...
226
ValueError
227
"""
228
if x == open_symbol:
229
return '('
230
elif x == close_symbol:
231
return ')'
232
else:
233
raise ValueError
234
235
class DyckWord(CombinatorialObject, Element):
236
r"""
237
A Dyck word.
238
239
A Dyck word is a sequence of open and close symbols such that every close
240
symbol has a corresponding open symbol preceding it. That is to say, a
241
Dyck word of length `n` is a list with `k` entries 1 and `n - k`
242
entries 0 such that the first `i` entries always have at least as many 1s
243
among them as 0s. (Here, the 1 serves as the open symbol and the 0 as the
244
close symbol.) Alternatively, the alphabet 1 and 0 can be replaced by
245
other characters such as '(' and ')'.
246
247
A Dyck word is *complete* if every open symbol moreover has a corresponding
248
close symbol.
249
250
A Dyck word may also be specified by either a noncrossing partition or
251
by an area sequence or the sequence of heights.
252
253
A Dyck word may also be thought of as a lattice path in the `\mathbb{Z}^2`
254
grid, starting at the origin `(0,0)`, and with steps in the North
255
`N = (0,1)` and east `E = (1,0)` directions such that it does not pass
256
below the `x = y` diagonal. The diagonal is referred to as the "main
257
diagonal" in the documentation. A North step is represented by a 1 in
258
the list and an East step is represented by a 0.
259
260
Equivalently, the path may be represented with steps in
261
the `NE = (1,1)` and the `SE = (1,-1)` direction such that it does not
262
pass below the horizontal axis.
263
264
A path representing a Dyck word (either using `N` and `E` steps, or
265
using `NE` and `SE` steps) is called a Dyck path.
266
267
EXAMPLES::
268
269
sage: dw = DyckWord([1, 0, 1, 0]); dw
270
[1, 0, 1, 0]
271
sage: print dw
272
()()
273
sage: print dw.height()
274
1
275
sage: dw.to_noncrossing_partition()
276
[[1], [2]]
277
278
::
279
280
sage: DyckWord('()()')
281
[1, 0, 1, 0]
282
sage: DyckWord('(())')
283
[1, 1, 0, 0]
284
sage: DyckWord('((')
285
[1, 1]
286
sage: DyckWord('')
287
[]
288
289
::
290
291
sage: DyckWord(noncrossing_partition=[[1],[2]])
292
[1, 0, 1, 0]
293
sage: DyckWord(noncrossing_partition=[[1,2]])
294
[1, 1, 0, 0]
295
sage: DyckWord(noncrossing_partition=[])
296
[]
297
298
::
299
300
sage: DyckWord(area_sequence=[0,0])
301
[1, 0, 1, 0]
302
sage: DyckWord(area_sequence=[0,1])
303
[1, 1, 0, 0]
304
sage: DyckWord(area_sequence=[0,1,2,2,0,1,1,2])
305
[1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0]
306
sage: DyckWord(area_sequence=[])
307
[]
308
309
::
310
311
sage: DyckWord(heights_sequence=(0,1,0,1,0))
312
[1, 0, 1, 0]
313
sage: DyckWord(heights_sequence=(0,1,2,1,0))
314
[1, 1, 0, 0]
315
sage: DyckWord(heights_sequence=(0,))
316
[]
317
318
::
319
320
sage: print DyckWord([1,0,1,1,0,0]).to_path_string()
321
/\
322
/\/ \
323
sage: DyckWord([1,0,1,1,0,0]).pretty_print()
324
___
325
| x
326
_| .
327
| . .
328
"""
329
__metaclass__ = ClasscallMetaclass
330
331
@staticmethod
332
def __classcall_private__(cls, dw=None, noncrossing_partition=None,
333
area_sequence=None, heights_sequence=None,
334
catalan_code=None):
335
"""
336
Return an element with the appropriate parent.
337
338
EXAMPLES::
339
340
sage: DyckWord([1,0,1,1,0,0])
341
[1, 0, 1, 1, 0, 0]
342
sage: DyckWord(heights_sequence=(0,1,2,1,0))
343
[1, 1, 0, 0]
344
sage: DyckWord(noncrossing_partition=[[1],[2]])
345
[1, 0, 1, 0]
346
"""
347
if dw is None:
348
if catalan_code is not None:
349
return CompleteDyckWords_all().from_Catalan_code(catalan_code)
350
if area_sequence is not None:
351
return CompleteDyckWords_all().from_area_sequence(area_sequence)
352
if noncrossing_partition is not None:
353
return CompleteDyckWords_all().from_noncrossing_partition(noncrossing_partition)
354
if heights_sequence is not None:
355
if heights_sequence[-1] == 0:
356
P = CompleteDyckWords_all()
357
else:
358
P = DyckWords_all()
359
return P.from_heights(heights_sequence)
360
361
raise ValueError("You have not specified a Dyck word.")
362
363
if isinstance(dw, str):
364
l = map(replace_parens, dw)
365
else:
366
l = dw
367
368
if isinstance(l, DyckWord):
369
return l
370
371
# CS: what happens here? there is a loop after a return (which is thus never used)
372
#elif l in DyckWords() or is_a(l):
373
#return DyckWord(l)
374
#for opt in l._latex_options:
375
#if opt not in latex_options:
376
#latex_options[opt] = l._latex_options[opt]
377
#return DyckWord(l,latex_options=latex_options)
378
if l in CompleteDyckWords_all():
379
return CompleteDyckWords_all()(l)
380
if is_a(l):
381
return DyckWords_all()(l)
382
383
raise ValueError("invalid Dyck word")
384
385
def __init__(self, parent, l, latex_options={}):
386
r"""
387
TESTS::
388
389
sage: DW = DyckWords(complete=False).from_heights((0,))
390
sage: TestSuite(DW).run()
391
sage: DW = DyckWords(complete=False).min_from_heights((0,))
392
sage: TestSuite(DW).run()
393
sage: DW = DyckWords().from_Catalan_code([])
394
sage: TestSuite(DW).run()
395
sage: DW = DyckWords().from_area_sequence([])
396
sage: TestSuite(DW).run()
397
"""
398
Element.__init__(self, parent)
399
CombinatorialObject.__init__(self, l)
400
self._latex_options = dict(latex_options)
401
402
_has_2D_print = False
403
404
def set_latex_options(self, D):
405
r"""
406
Set the latex options for use in the ``_latex_`` function. The
407
default values are set in the ``__init__`` function.
408
409
- ``tikz_scale`` -- (default: 1) scale for use with the tikz package.
410
411
- ``diagonal`` -- (default: ``False``) boolean value to draw the
412
diagonal or not.
413
414
- ``line width`` -- (default: 2*``tikz_scale``) value representing the
415
line width.
416
417
- ``color`` -- (default: black) the line color.
418
419
- ``bounce path`` -- (default: ``False``) boolean value to indicate
420
if the bounce path should be drawn.
421
422
- ``peaks`` -- (default: ``False``) boolean value to indicate if the
423
peaks should be displayed.
424
425
- ``valleys`` -- (default: ``False``) boolean value to indicate if the
426
valleys should be displayed.
427
428
INPUT:
429
430
- ``D`` -- a dictionary with a list of latex parameters to change
431
432
EXAMPLES::
433
434
sage: D = DyckWord([1,0,1,0,1,0])
435
sage: D.set_latex_options({"tikz_scale":2})
436
sage: D.set_latex_options({"valleys":True, "color":"blue"})
437
"""
438
for opt in D:
439
self._latex_options[opt] = D[opt]
440
441
def latex_options(self):
442
r"""
443
Return the latex options for use in the ``_latex_`` function as a
444
dictionary. The default values are set using the global options.
445
446
- ``tikz_scale`` -- (default: 1) scale for use with the tikz package.
447
448
- ``diagonal`` -- (default: ``False``) boolean value to draw the
449
diagonal or not.
450
451
- ``line width`` -- (default: 2*``tikz_scale``) value representing the
452
line width.
453
454
- ``color`` -- (default: black) the line color.
455
456
- ``bounce path`` -- (default: ``False``) boolean value to indicate
457
if the bounce path should be drawn.
458
459
- ``peaks`` -- (default: ``False``) boolean value to indicate if the
460
peaks should be displayed.
461
462
- ``valleys`` -- (default: ``False``) boolean value to indicate if the
463
valleys should be displayed.
464
465
EXAMPLES::
466
467
sage: D = DyckWord([1,0,1,0,1,0])
468
sage: D.latex_options()
469
{'valleys': False, 'peaks': False, 'tikz_scale': 1, 'color': 'black', 'diagonal': False, 'bounce path': False, 'line width': 2}
470
"""
471
d = self._latex_options.copy()
472
if "tikz_scale" not in d:
473
d["tikz_scale"] = self.parent().global_options["latex_tikz_scale"]
474
if "diagonal" not in d:
475
d["diagonal"] = self.parent().global_options["latex_diagonal"]
476
if "line width" not in d:
477
d["line width"] = self.parent().global_options["latex_line_width_scalar"]*d["tikz_scale"]
478
if "color" not in d:
479
d["color"] = self.parent().global_options["latex_color"]
480
if "bounce path" not in d:
481
d["bounce path"] = self.parent().global_options["latex_bounce_path"]
482
if "peaks" not in d:
483
d["peaks"] = self.parent().global_options["latex_peaks"]
484
if "valleys" not in d:
485
d["valleys"] = self.parent().global_options["latex_valleys"]
486
return d
487
488
def _repr_(self):
489
r"""
490
TESTS::
491
492
sage: DyckWord([1, 0, 1, 0])
493
[1, 0, 1, 0]
494
sage: DyckWord([1, 1, 0, 0])
495
[1, 1, 0, 0]
496
sage: type(DyckWord([]))._has_2D_print = True
497
sage: DyckWord([1, 0, 1, 0])
498
/\/\
499
sage: DyckWord([1, 1, 0, 0])
500
/\
501
/ \
502
sage: type(DyckWord([]))._has_2D_print = False
503
"""
504
if self._has_2D_print:
505
return self.to_path_string()
506
else:
507
return super(DyckWord, self)._repr_()
508
509
def _repr_lattice(self, type=None, labelling=None, underpath=True):
510
r"""
511
See :meth:`pretty_print()`.
512
513
TESTS::
514
515
sage: print DyckWord(area_sequence=[0,1,0])._repr_lattice(type="NE-SE")
516
/\
517
/ \/\
518
sage: print DyckWord(area_sequence=[0,1,0])._repr_lattice(labelling=[1,3,2],underpath=False)
519
_
520
___| 2
521
| x . 3
522
| . . 1
523
"""
524
if type is None:
525
type = self.parent().global_options['diagram_style']
526
if type == "grid":
527
type = "N-E"
528
elif type == "line":
529
type = "NE-SE"
530
531
if type == "NE-SE":
532
if labelling is not None or underpath is not True:
533
raise ValueError("The labelling cannot be shown with Northeast-Southeast paths.")
534
return self.to_path_string()
535
elif type == "N-E":
536
alst = self.to_area_sequence()
537
n = len(alst)
538
if n == 0:
539
return ".\n"
540
if labelling is None:
541
labels = [" "]*n
542
else:
543
if len(labelling) != n:
544
raise ValueError("The given labelling has the wrong length.")
545
labels = [ str(label) for label in labelling ]
546
if not underpath:
547
max_length = max( len(label) for label in labels )
548
labels = [ label.rjust(max_length+1) for label in labels ]
549
550
length_of_final_fall = list(reversed(self)).index(open_symbol)
551
if length_of_final_fall == 0:
552
final_fall = " "
553
else:
554
final_fall = " _" + "__"*(length_of_final_fall-1)
555
row = " "*(n - alst[-1]-1) + final_fall + "\n"
556
for i in range(n-1):
557
c=0
558
row = row + " "*(n-i-2-alst[-i-2])
559
c+=n-i-2-alst[-i-2]
560
if alst[-i-2]+1!=alst[-i-1]:
561
row+=" _"
562
c+=alst[-i-2]-alst[-i-1]
563
if underpath:
564
row+="__"*(alst[-i-2]-alst[-i-1])+"|" + labels[-1] + "x "*(n-c-2-i)+ " ."*i + "\n"
565
else:
566
row+="__"*(alst[-i-2]-alst[-i-1])+"| " + "x "*(n-c-2-i)+ " ."*i + labels[-1] + "\n"
567
labels.pop()
568
if underpath:
569
row = row + "|" + labels[-1] + " ."*(n-1) + "\n"
570
else:
571
row = row + "| "+" ."*(n-1) + labels[-1] + "\n"
572
return row
573
else:
574
raise ValueError("The given type (=\s) is not valid."%type)
575
576
@staticmethod
577
def set_ascii_art(rep="path"):
578
r"""
579
TESTS::
580
581
sage: DyckWord.set_ascii_art("path")
582
doctest:...: DeprecationWarning: set_ascii_art is deprecated. Use DyckWords.global_options instead.
583
See http://trac.sagemath.org/14875 for details.
584
"""
585
from sage.misc.superseded import deprecation
586
deprecation(14875, 'set_ascii_art is deprecated. Use DyckWords.global_options instead.')
587
DyckWords.global_options(ascii_art=rep)
588
589
def _ascii_art_(self):
590
r"""
591
Return an ASCII art representation of ``self``.
592
593
TESTS::
594
595
sage: ascii_art(list(DyckWords(3)))
596
[ /\ ]
597
[ /\ /\ /\/\ / \ ]
598
[ /\/\/\, /\/ \, / \/\, / \, / \ ]
599
"""
600
from sage.misc.ascii_art import AsciiArt
601
rep = self.parent().global_options['ascii_art']
602
if rep == "path":
603
ret = self.to_path_string()
604
elif rep == "pretty_output":
605
ret = self._repr_lattice()
606
return AsciiArt(ret.splitlines(), baseline=0)
607
608
def __str__(self):
609
r"""
610
Return a string consisting of matched parentheses corresponding to
611
the Dyck word.
612
613
EXAMPLES::
614
615
sage: print DyckWord([1, 0, 1, 0])
616
()()
617
sage: print DyckWord([1, 1, 0, 0])
618
(())
619
"""
620
if self._has_2D_print:
621
return self.to_path_string()
622
else:
623
return "".join(map(replace_symbols, [x for x in self]))
624
625
def to_path_string(self):
626
r"""
627
A path representation of the Dyck word consisting of steps
628
``/`` and ``\`` .
629
630
EXAMPLES::
631
632
sage: print DyckWord([1, 0, 1, 0]).to_path_string()
633
/\/\
634
sage: print DyckWord([1, 1, 0, 0]).to_path_string()
635
/\
636
/ \
637
sage: print DyckWord([1,1,0,1,1,0,0,1,0,1,0,0]).to_path_string()
638
/\
639
/\/ \/\/\
640
/ \
641
"""
642
res = [([" "]*len(self)) for _ in range(self.height())]
643
h = 1
644
for i, p in enumerate(self):
645
if p == open_symbol:
646
res[-h][i] = "/"
647
h +=1
648
else:
649
h -=1
650
res[-h][i] = "\\"
651
return "\n".join("".join(l) for l in res)
652
653
def pretty_print(self, type=None, labelling=None, underpath=True):
654
r"""
655
Display a DyckWord as a lattice path in the `\ZZ^2` grid.
656
657
If the ``type`` is "N-E", then the a cell below the diagonal is
658
indicated by a period, a cell below the path, but above the
659
diagonal are indicated by an x. If a list of labels is included,
660
they are displayed along the vertical edges of the Dyck path.
661
662
If the ``type`` is "NE-SE", then the path is simply printed
663
as up steps and down steps.
664
665
INPUT:
666
667
- ``type`` -- (default: ``None``) can either be:
668
669
- ``None`` to use the global option default
670
- "N-E" to show ``self`` as a path of north and east steps, or
671
- "NE-SE" to show ``self`` as a path of north-east and
672
south-east steps.
673
674
- ``labelling`` -- (if type is "N-E") a list of labels assigned to
675
the up steps in ``self``.
676
677
- ``underpath`` -- (if type is "N-E", default:``True``) If ``True``,
678
the labelling is shown under the path otherwise, it is shown to
679
the right of the path.
680
681
EXAMPLES::
682
683
sage: for D in DyckWords(3): D.pretty_print()
684
_
685
_|
686
_| .
687
| . .
688
___
689
| x
690
_| .
691
| . .
692
_
693
___|
694
| x .
695
| . .
696
___
697
_| x
698
| x .
699
| . .
700
_____
701
| x x
702
| x .
703
| . .
704
705
::
706
707
sage: for D in DyckWords(3): D.pretty_print(type="NE-SE")
708
/\/\/\
709
/\
710
/\/ \
711
/\
712
/ \/\
713
/\/\
714
/ \
715
/\
716
/ \
717
/ \
718
719
::
720
721
sage: D = DyckWord([1,1,1,0,1,0,0,1,1])
722
sage: D.pretty_print()
723
| x x
724
___| x .
725
_| x x . .
726
| x x . . .
727
| x . . . .
728
| . . . . .
729
730
sage: D = DyckWord([1,1,1,0,1,0,0,1,1,0])
731
sage: D.pretty_print()
732
_
733
| x x
734
___| x .
735
_| x x . .
736
| x x . . .
737
| x . . . .
738
| . . . . .
739
740
sage: D = DyckWord([1,1,1,0,1,0,0,1,1,0,0])
741
sage: D.pretty_print()
742
___
743
| x x
744
___| x .
745
_| x x . .
746
| x x . . .
747
| x . . . .
748
| . . . . .
749
750
::
751
752
sage: DyckWord(area_sequence=[0,1,0]).pretty_print(labelling=[1,3,2])
753
_
754
___|2
755
|3x .
756
|1 . .
757
758
sage: DyckWord(area_sequence=[0,1,0]).pretty_print(labelling=[1,3,2],underpath=False)
759
_
760
___| 2
761
| x . 3
762
| . . 1
763
764
::
765
766
sage: DyckWord(area_sequence=[0,1,1,2,3,2,3,3,2,0,1,1,2,3,4,2,3]).pretty_print()
767
_______
768
| x x x
769
_____| x x .
770
| x x x x . .
771
| x x x . . .
772
| x x . . . .
773
_| x . . . . .
774
| x . . . . . .
775
_____| . . . . . . .
776
___| x x . . . . . . . .
777
_| x x x . . . . . . . . .
778
| x x x . . . . . . . . . .
779
___| x x . . . . . . . . . . .
780
| x x x . . . . . . . . . . . .
781
| x x . . . . . . . . . . . . .
782
_| x . . . . . . . . . . . . . .
783
| x . . . . . . . . . . . . . . .
784
| . . . . . . . . . . . . . . . .
785
786
sage: DyckWord(area_sequence=[0,1,1,2,3,2,3,3,2,0,1,1,2,3,4,2,3]).pretty_print(labelling=range(17),underpath=False)
787
_______
788
| x x x 16
789
_____| x x . 15
790
| x x x x . . 14
791
| x x x . . . 13
792
| x x . . . . 12
793
_| x . . . . . 11
794
| x . . . . . . 10
795
_____| . . . . . . . 9
796
___| x x . . . . . . . . 8
797
_| x x x . . . . . . . . . 7
798
| x x x . . . . . . . . . . 6
799
___| x x . . . . . . . . . . . 5
800
| x x x . . . . . . . . . . . . 4
801
| x x . . . . . . . . . . . . . 3
802
_| x . . . . . . . . . . . . . . 2
803
| x . . . . . . . . . . . . . . . 1
804
| . . . . . . . . . . . . . . . . 0
805
806
::
807
808
sage: DyckWord([]).pretty_print()
809
.
810
"""
811
print self._repr_lattice(type, labelling, underpath)
812
813
pp = pretty_print
814
815
def _latex_(self):
816
r"""
817
A latex representation of ``self`` using the tikzpicture package.
818
819
EXAMPLES:
820
821
sage: D = DyckWord([1,0,1,1,1,0,1,1,0,0,0,1,0,0])
822
sage: D.set_latex_options({"valleys":True, "peaks":True, "bounce path":True})
823
sage: latex(D)
824
\vcenter{\hbox{$\begin{tikzpicture}[scale=1]
825
\draw[line width=2,color=red,fill=red] (2, 0) circle (0.21);
826
\draw[line width=2,color=red,fill=red] (6, 2) circle (0.21);
827
\draw[line width=2,color=red,fill=red] (11, 1) circle (0.21);
828
\draw[line width=2,color=red,fill=red] (1, 1) circle (0.21);
829
\draw[line width=2,color=red,fill=red] (5, 3) circle (0.21);
830
\draw[line width=2,color=red,fill=red] (8, 4) circle (0.21);
831
\draw[line width=2,color=red,fill=red] (12, 2) circle (0.21);
832
\draw[rounded corners=1, color=green, line width=4] (0, 0) -- (1, 1) -- (2, 0) -- (3, 1) -- (4, 0) -- (5, 1) -- (6, 2) -- (7, 3) -- (8, 2) -- (9, 1) -- (10, 0) -- (11, 1) -- (12, 2) -- (13, 1) -- (14, 0);
833
\draw[dotted] (0, 0) grid (14, 4);
834
\draw[rounded corners=1, color=black, line width=2] (0, 0) -- (1, 1) -- (2, 0) -- (3, 1) -- (4, 2) -- (5, 3) -- (6, 2) -- (7, 3) -- (8, 4) -- (9, 3) -- (10, 2) -- (11, 1) -- (12, 2) -- (13, 1) -- (14, 0);
835
\end{tikzpicture}$}}
836
sage: DyckWord([1,0])._latex_()
837
'\\vcenter{\\hbox{$\\begin{tikzpicture}[scale=1]\n \\draw[dotted] (0, 0) grid (2, 1);\n \\draw[rounded corners=1, color=black, line width=2] (0, 0) -- (1, 1) -- (2, 0);\n\\end{tikzpicture}$}}'
838
sage: DyckWord([1,0,1,1,0,0])._latex_()
839
'\\vcenter{\\hbox{$\\begin{tikzpicture}[scale=1]\n \\draw[dotted] (0, 0) grid (6, 2);\n \\draw[rounded corners=1, color=black, line width=2] (0, 0) -- (1, 1) -- (2, 0) -- (3, 1) -- (4, 2) -- (5, 1) -- (6, 0);\n\\end{tikzpicture}$}}'
840
"""
841
latex.add_package_to_preamble_if_available("tikz")
842
heights = self.heights()
843
latex_options = self.latex_options()
844
diagonal = latex_options["diagonal"]
845
ht = [(0,0)]
846
valleys = []
847
peaks = []
848
for i in range(1,len(heights)):
849
a,b = ht[-1]
850
if heights[i] > heights[i-1]:
851
if diagonal:
852
ht.append((a,b+1))
853
else:
854
ht.append((a+1,b+1))
855
if i < len(heights)-1 and heights[i+1] < heights[i]:
856
peaks.append(ht[-1])
857
else:
858
if diagonal:
859
ht.append((a+1,b))
860
else:
861
ht.append((a+1,b-1))
862
if i < len(heights)-1 and heights[i+1] > heights[i]:
863
valleys.append(ht[-1])
864
ht = iter(ht)
865
if diagonal:
866
grid = [((0,i),(i,i+1)) for i in range(self.number_of_open_symbols())]
867
else:
868
grid = [((0,0),(len(self),self.height()))]
869
res = "\\vcenter{\\hbox{$\\begin{tikzpicture}[scale="+str(latex_options['tikz_scale'])+"]\n"
870
mark_points = []
871
if latex_options['valleys']:
872
mark_points.extend(valleys)
873
if latex_options['peaks']:
874
mark_points.extend(peaks)
875
for v in mark_points:
876
res += " \\draw[line width=2,color=red,fill=red] %s circle (%s);\n"%(str(v),0.15+.03*latex_options['line width'])
877
if latex_options["bounce path"]:
878
D = self.bounce_path()
879
D.set_latex_options(latex_options)
880
D.set_latex_options({"color":"green","line width":2*latex_options['line width'],"bounce path":False, "peaks":False, "valleys":False})
881
res += D._latex_().split("\n")[-2] + "\n"
882
for v1,v2 in grid:
883
res += " \\draw[dotted] %s grid %s;\n"%(str(v1),str(v2))
884
if diagonal:
885
res += " \\draw (0,0) -- %s;\n"%str((self.number_of_open_symbols(),self.number_of_open_symbols()))
886
res += " \\draw[rounded corners=1, color=%s, line width=%s] (0, 0)"%(latex_options['color'],str(latex_options['line width']))
887
ht.next()
888
for i, j in ht:
889
res += " -- (%s, %s)"%(i, j)
890
res += ";\n"
891
res += "\\end{tikzpicture}$}}"
892
return res
893
894
def length(self):
895
r"""
896
Return the length of ``self``.
897
898
EXAMPLES::
899
900
sage: DyckWord([1, 0, 1, 0]).length()
901
4
902
sage: DyckWord([1, 0, 1, 1, 0]).length()
903
5
904
905
TESTS::
906
907
sage: DyckWord([]).length()
908
0
909
"""
910
return len(self)
911
912
def number_of_open_symbols(self):
913
r"""
914
Return the number of open symbols in ``self``.
915
916
EXAMPLES::
917
918
sage: DyckWord([1, 0, 1, 0]).number_of_open_symbols()
919
2
920
sage: DyckWord([1, 0, 1, 1, 0]).number_of_open_symbols()
921
3
922
923
TESTS::
924
925
sage: DyckWord([]).number_of_open_symbols()
926
0
927
"""
928
return len(filter(lambda x: x == open_symbol, self))
929
930
size = deprecated_function_alias(13550, number_of_open_symbols)
931
932
def number_of_close_symbols(self):
933
r"""
934
Return the number of close symbols in ``self``.
935
936
EXAMPLES::
937
938
sage: DyckWord([1, 0, 1, 0]).number_of_close_symbols()
939
2
940
sage: DyckWord([1, 0, 1, 1, 0]).number_of_close_symbols()
941
2
942
943
TESTS::
944
945
sage: DyckWord([]).number_of_close_symbols()
946
0
947
"""
948
return len(filter(lambda x: x == close_symbol, self))
949
950
def is_complete(self):
951
r"""
952
Return ``True`` if ``self`` is complete.
953
954
A Dyck word `d` is complete if `d` contains as many closers as openers.
955
956
EXAMPLES::
957
958
sage: DyckWord([1, 0, 1, 0]).is_complete()
959
True
960
sage: DyckWord([1, 0, 1, 1, 0]).is_complete()
961
False
962
963
TESTS::
964
965
sage: DyckWord([]).is_complete()
966
True
967
"""
968
return self.number_of_open_symbols() == self.number_of_close_symbols()
969
970
def height(self):
971
r"""
972
Return the height of ``self``.
973
974
We view the Dyck word as a Dyck path from `(0, 0)` to
975
`(2n, 0)` in the first quadrant by letting ``1``'s represent
976
steps in the direction `(1, 1)` and ``0``'s represent steps in
977
the direction `(1, -1)`.
978
979
The height is the maximum `y`-coordinate reached.
980
981
.. SEEALSO:: :meth:`heights`
982
983
EXAMPLES::
984
985
sage: DyckWord([]).height()
986
0
987
sage: DyckWord([1,0]).height()
988
1
989
sage: DyckWord([1, 1, 0, 0]).height()
990
2
991
sage: DyckWord([1, 1, 0, 1, 0]).height()
992
2
993
sage: DyckWord([1, 1, 0, 0, 1, 0]).height()
994
2
995
sage: DyckWord([1, 0, 1, 0]).height()
996
1
997
sage: DyckWord([1, 1, 0, 0, 1, 1, 1, 0, 0, 0]).height()
998
3
999
"""
1000
# calling max(self.heights()) has a significant overhead (20%)
1001
height = 0
1002
height_max = 0
1003
for letter in self:
1004
if letter == open_symbol:
1005
height += 1
1006
height_max = max(height, height_max)
1007
elif letter == close_symbol:
1008
height -= 1
1009
return height_max
1010
1011
def heights(self):
1012
r"""
1013
Return the heights of ``self``.
1014
1015
We view the Dyck word as a Dyck path from `(0,0)` to
1016
`(2n,0)` in the first quadrant by letting ``1``'s represent
1017
steps in the direction `(1,1)` and ``0``'s represent steps in
1018
the direction `(1,-1)`.
1019
1020
The heights is the sequence of the `y`-coordinates of all
1021
`2n+1` lattice points along the path.
1022
1023
.. SEEALSO:: :meth:`from_heights`, :meth:`min_from_heights`
1024
1025
EXAMPLES::
1026
1027
sage: DyckWord([]).heights()
1028
(0,)
1029
sage: DyckWord([1,0]).heights()
1030
(0, 1, 0)
1031
sage: DyckWord([1, 1, 0, 0]).heights()
1032
(0, 1, 2, 1, 0)
1033
sage: DyckWord([1, 1, 0, 1, 0]).heights()
1034
(0, 1, 2, 1, 2, 1)
1035
sage: DyckWord([1, 1, 0, 0, 1, 0]).heights()
1036
(0, 1, 2, 1, 0, 1, 0)
1037
sage: DyckWord([1, 0, 1, 0]).heights()
1038
(0, 1, 0, 1, 0)
1039
sage: DyckWord([1, 1, 0, 0, 1, 1, 1, 0, 0, 0]).heights()
1040
(0, 1, 2, 1, 0, 1, 2, 3, 2, 1, 0)
1041
"""
1042
height = 0
1043
heights = [0]*(len(self)+1)
1044
for i, letter in enumerate(self):
1045
if letter == open_symbol:
1046
height += 1
1047
elif letter == close_symbol:
1048
height -= 1
1049
heights[i+1] = height
1050
return tuple(heights)
1051
1052
@classmethod
1053
def from_heights(cls, heights):
1054
r"""
1055
This is deprecated in :trac:`14875`. Use instead
1056
:class:`DyckWords_all().from_heights()`.
1057
1058
EXAMPLES::
1059
1060
sage: from sage.combinat.dyck_word import DyckWord
1061
sage: DyckWord.from_heights((0,))
1062
doctest:...: DeprecationWarning: this method is deprecated. Use DyckWords(complete=False).from_heights instead.
1063
See http://trac.sagemath.org/14875 for details.
1064
[]
1065
"""
1066
from sage.misc.superseded import deprecation
1067
deprecation(14875,'this method is deprecated. Use DyckWords(complete=False).from_heights instead.')
1068
return DyckWords_all().from_heights(heights)
1069
1070
@classmethod
1071
def min_from_heights(cls, heights):
1072
r"""
1073
This is deprecated in :trac:`14875`. Use instead
1074
:class:`DyckWords_all.min_from_heights()`.
1075
1076
EXAMPLES::
1077
1078
sage: from sage.combinat.dyck_word import DyckWord
1079
sage: DyckWord.min_from_heights((0,))
1080
doctest:...: DeprecationWarning: this method is deprecated. Use DyckWords(complete=False).from_min_heights instead.
1081
See http://trac.sagemath.org/14875 for details.
1082
[]
1083
"""
1084
from sage.misc.superseded import deprecation
1085
deprecation(14875,'this method is deprecated. Use DyckWords(complete=False).from_min_heights instead.')
1086
return DyckWords_all().from_heights(heights)
1087
1088
def associated_parenthesis(self, pos):
1089
r"""
1090
Report the position for the parenthesis in ``self`` that matches the
1091
one at position ``pos`` .
1092
1093
INPUT:
1094
1095
- ``pos`` -- the index of the parenthesis in the list
1096
1097
OUTPUT:
1098
1099
- Integer representing the index of the matching parenthesis.
1100
If no parenthesis matches, return ``None``.
1101
1102
EXAMPLES::
1103
1104
sage: DyckWord([1, 0]).associated_parenthesis(0)
1105
1
1106
sage: DyckWord([1, 0, 1, 0]).associated_parenthesis(0)
1107
1
1108
sage: DyckWord([1, 0, 1, 0]).associated_parenthesis(1)
1109
0
1110
sage: DyckWord([1, 0, 1, 0]).associated_parenthesis(2)
1111
3
1112
sage: DyckWord([1, 0, 1, 0]).associated_parenthesis(3)
1113
2
1114
sage: DyckWord([1, 1, 0, 0]).associated_parenthesis(0)
1115
3
1116
sage: DyckWord([1, 1, 0, 0]).associated_parenthesis(2)
1117
1
1118
sage: DyckWord([1, 1, 0]).associated_parenthesis(1)
1119
2
1120
sage: DyckWord([1, 1]).associated_parenthesis(0)
1121
"""
1122
d = 0
1123
height = 0
1124
if pos >= len(self):
1125
raise ValueError("invalid index")
1126
1127
if self[pos] == open_symbol:
1128
d += 1
1129
height += 1
1130
elif self[pos] == close_symbol:
1131
d -= 1
1132
height -= 1
1133
else:
1134
raise ValueError("unknown symbol %s"%self[pos-1])
1135
1136
while height != 0:
1137
pos += d
1138
if pos < 0 or pos >= len(self):
1139
return None
1140
if self[pos] == open_symbol:
1141
height += 1
1142
elif self[pos] == close_symbol:
1143
height -= 1
1144
return pos
1145
1146
def number_of_initial_rises(self):
1147
r"""
1148
Return the length of the initial run of ``self``
1149
1150
OUPUT:
1151
1152
- a non--negative integer indicating the length of the initial rise
1153
1154
EXAMPLES::
1155
1156
sage: DyckWord([1, 0, 1, 0]).number_of_initial_rises()
1157
1
1158
sage: DyckWord([1, 1, 0, 0]).number_of_initial_rises()
1159
2
1160
sage: DyckWord([1, 1, 0, 0, 1, 0]).number_of_initial_rises()
1161
2
1162
sage: DyckWord([1, 0, 1, 1, 0, 0]).number_of_initial_rises()
1163
1
1164
1165
TESTS::
1166
1167
sage: DyckWord([]).number_of_initial_rises()
1168
0
1169
sage: DyckWord([1, 0]).number_of_initial_rises()
1170
1
1171
"""
1172
if not self:
1173
return 0
1174
i = 1
1175
while self[i] == open_symbol:
1176
i+=1
1177
return i
1178
1179
def peaks(self):
1180
r"""
1181
Return a list of the positions of the peaks of a Dyck word.
1182
1183
A peak is `1` followed by a `0`. Note that this does not agree with
1184
the definition given in [Hag2008]_.
1185
1186
EXAMPLES::
1187
1188
sage: DyckWord([1, 0, 1, 0]).peaks()
1189
[0, 2]
1190
sage: DyckWord([1, 1, 0, 0]).peaks()
1191
[1]
1192
sage: DyckWord([1,1,0,1,0,1,0,0]).peaks() # Haglund's def gives 2
1193
[1, 3, 5]
1194
"""
1195
return [i for i in range(len(self)-1) if self[i] == open_symbol and self[i+1] == close_symbol]
1196
1197
def number_of_peaks(self):
1198
r"""
1199
The number of peaks of the Dyck path associated to ``self`` .
1200
1201
.. SEEALSO:: :meth:`peaks`
1202
1203
EXAMPLES::
1204
1205
sage: DyckWord([1, 0, 1, 0]).number_of_peaks()
1206
2
1207
sage: DyckWord([1, 1, 0, 0]).number_of_peaks()
1208
1
1209
sage: DyckWord([1,1,0,1,0,1,0,0]).number_of_peaks()
1210
3
1211
sage: DyckWord([]).number_of_peaks()
1212
0
1213
"""
1214
return len(self.peaks())
1215
1216
def valleys(self):
1217
r"""
1218
Return a list of the positions of the valleys of a Dyck word.
1219
1220
A valley is `0` followed by a `1`.
1221
1222
EXAMPLES::
1223
1224
sage: DyckWord([1, 0, 1, 0]).valleys()
1225
[1]
1226
sage: DyckWord([1, 1, 0, 0]).valleys()
1227
[]
1228
sage: DyckWord([1,1,0,1,0,1,0,0]).valleys()
1229
[2, 4]
1230
"""
1231
return [i for i in xrange(len(self)-1) if self[i] == close_symbol and self[i+1] == open_symbol]
1232
1233
def number_of_valleys(self):
1234
r"""
1235
Return the number of valleys of ``self``.
1236
1237
EXAMPLES::
1238
1239
sage: DyckWord([1, 0, 1, 0]).number_of_valleys()
1240
1
1241
sage: DyckWord([1, 1, 0, 0]).number_of_valleys()
1242
0
1243
sage: DyckWord([1, 1, 0, 0, 1, 0]).number_of_valleys()
1244
1
1245
sage: DyckWord([1, 0, 1, 1, 0, 0]).number_of_valleys()
1246
1
1247
1248
TESTS::
1249
1250
sage: DyckWord([]).number_of_valleys()
1251
0
1252
sage: DyckWord([1, 0]).number_of_valleys()
1253
0
1254
"""
1255
return len(self.valleys())
1256
1257
def position_of_first_return(self):
1258
r"""
1259
Return the number of vertical steps before the Dyck path returns to
1260
the main diagonal.
1261
1262
EXAMPLES::
1263
1264
sage: DyckWord([1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0]).position_of_first_return()
1265
1
1266
sage: DyckWord([1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0]).position_of_first_return()
1267
7
1268
sage: DyckWord([1, 1, 0, 0]).position_of_first_return()
1269
2
1270
sage: DyckWord([1, 0, 1, 0]).position_of_first_return()
1271
1
1272
sage: DyckWord([]).position_of_first_return()
1273
0
1274
"""
1275
touches = self.touch_points()
1276
if touches == []:
1277
return 0
1278
else:
1279
return touches[0]
1280
1281
def positions_of_double_rises(self):
1282
r"""
1283
Return a list of positions in ``self`` where there are two
1284
consecutive `1`'s.
1285
1286
EXAMPLES::
1287
1288
sage: DyckWord([1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0]).positions_of_double_rises()
1289
[2, 5]
1290
sage: DyckWord([1, 1, 0, 0]).positions_of_double_rises()
1291
[0]
1292
sage: DyckWord([1, 0, 1, 0]).positions_of_double_rises()
1293
[]
1294
"""
1295
return [ i for i in xrange(len(self)-1) if self[i] == self[i+1] == open_symbol ]
1296
1297
def number_of_double_rises(self):
1298
r"""
1299
Return a the number of positions in ``self`` where there are two
1300
consecutive `1`'s.
1301
1302
EXAMPLES::
1303
1304
sage: DyckWord([1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0]).number_of_double_rises()
1305
2
1306
sage: DyckWord([1, 1, 0, 0]).number_of_double_rises()
1307
1
1308
sage: DyckWord([1, 0, 1, 0]).number_of_double_rises()
1309
0
1310
"""
1311
return len(self.positions_of_double_rises())
1312
1313
def returns_to_zero(self):
1314
r"""
1315
Return a list of positions where ``self`` has height `0`,
1316
excluding the position `0`.
1317
1318
EXAMPLES::
1319
1320
sage: DyckWord([]).returns_to_zero()
1321
[]
1322
sage: DyckWord([1, 0]).returns_to_zero()
1323
[2]
1324
sage: DyckWord([1, 0, 1, 0]).returns_to_zero()
1325
[2, 4]
1326
sage: DyckWord([1, 1, 0, 0]).returns_to_zero()
1327
[4]
1328
"""
1329
h = self.heights()
1330
return [i for i in xrange(2, len(h), 2) if h[i] == 0]
1331
1332
return_to_zero = deprecated_function_alias(13550, returns_to_zero)
1333
1334
def touch_points(self):
1335
r"""
1336
Return the abscissae (or, equivalently, ordinates) of the
1337
points where the Dyck path corresponding to ``self`` (comprising
1338
`NE` and `SE` steps) touches the main diagonal. This includes
1339
the last point (if it is on the main diagonal) but excludes the
1340
beginning point.
1341
1342
Note that these abscissae are precisely the entries of
1343
:meth:`returns_to_zero` divided by `2`.
1344
1345
OUTPUT:
1346
1347
- a list of integers indicating where the path touches the diagonal
1348
1349
EXAMPLES::
1350
1351
sage: DyckWord([1, 0, 1, 0]).touch_points()
1352
[1, 2]
1353
sage: DyckWord([1, 1, 0, 0]).touch_points()
1354
[2]
1355
sage: DyckWord([1, 1, 0, 0, 1, 0]).touch_points()
1356
[2, 3]
1357
sage: DyckWord([1, 0, 1, 1, 0, 0]).touch_points()
1358
[1, 3]
1359
"""
1360
return [ i // 2 for i in self.returns_to_zero() ]
1361
1362
def touch_composition(self):
1363
r"""
1364
Return a composition which indicates the positions where ``self``
1365
returns to the diagonal.
1366
1367
This assumes ``self`` to be a complete Dyck word.
1368
1369
OUTPUT:
1370
1371
- a composition of length equal to the length of the Dyck word.
1372
1373
EXAMPLES::
1374
1375
sage: DyckWord([1, 0, 1, 0]).touch_composition()
1376
[1, 1]
1377
sage: DyckWord([1, 1, 0, 0]).touch_composition()
1378
[2]
1379
sage: DyckWord([1, 1, 0, 0, 1, 0]).touch_composition()
1380
[2, 1]
1381
sage: DyckWord([1, 0, 1, 1, 0, 0]).touch_composition()
1382
[1, 2]
1383
sage: DyckWord([]).touch_composition()
1384
[]
1385
"""
1386
from sage.combinat.composition import Composition
1387
if self.length() == 0:
1388
return Composition([])
1389
return Composition( descents=[i-1 for i in self.touch_points()] )
1390
1391
def number_of_touch_points(self):
1392
r"""
1393
Return the number of touches of ``self`` at the main diagonal.
1394
1395
OUTPUT:
1396
1397
- a non--negative integer
1398
1399
EXAMPLES::
1400
1401
sage: DyckWord([1, 0, 1, 0]).number_of_touch_points()
1402
2
1403
sage: DyckWord([1, 1, 0, 0]).number_of_touch_points()
1404
1
1405
sage: DyckWord([1, 1, 0, 0, 1, 0]).number_of_touch_points()
1406
2
1407
sage: DyckWord([1, 0, 1, 1, 0, 0]).number_of_touch_points()
1408
2
1409
1410
TESTS::
1411
1412
sage: DyckWord([]).number_of_touch_points()
1413
0
1414
"""
1415
return len(self.touch_points())
1416
1417
def rise_composition(self):
1418
r"""
1419
The sequences of lengths of runs of `1`'s in ``self``. Also equal to
1420
the sequence of lengths of vertical segments in the Dyck path.
1421
1422
EXAMPLES::
1423
1424
sage: DyckWord([1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0]).pretty_print()
1425
___
1426
| x
1427
_______| .
1428
| x x x . .
1429
| x x . . .
1430
_| x . . . .
1431
| x . . . . .
1432
| . . . . . .
1433
1434
sage: DyckWord([1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0]).rise_composition()
1435
[2, 3, 2]
1436
sage: DyckWord([1,1,0,0]).rise_composition()
1437
[2]
1438
sage: DyckWord([1,0,1,0]).rise_composition()
1439
[1, 1]
1440
"""
1441
from sage.combinat.composition import Composition
1442
L = list(self)
1443
rise_comp = []
1444
while L:
1445
i = L.index(0)
1446
L = L[i+1:]
1447
if i > 0:
1448
rise_comp.append(i)
1449
return Composition(rise_comp)
1450
1451
@combinatorial_map(name='to two-row standard tableau')
1452
def to_standard_tableau(self):
1453
r"""
1454
Return a standard tableau of shape `(a,b)` where
1455
`a` is the number of open symbols and `b` is the number of
1456
close symbols in ``self``.
1457
1458
EXAMPLES::
1459
1460
sage: DyckWord([]).to_standard_tableau()
1461
[]
1462
sage: DyckWord([1, 0]).to_standard_tableau()
1463
[[1], [2]]
1464
sage: DyckWord([1, 1, 0, 0]).to_standard_tableau()
1465
[[1, 2], [3, 4]]
1466
sage: DyckWord([1, 0, 1, 0]).to_standard_tableau()
1467
[[1, 3], [2, 4]]
1468
sage: DyckWord([1]).to_standard_tableau()
1469
[[1]]
1470
sage: DyckWord([1, 0, 1]).to_standard_tableau()
1471
[[1, 3], [2]]
1472
"""
1473
open_positions = []
1474
close_positions = []
1475
for i in range(len(self)):
1476
if self[i] == open_symbol:
1477
open_positions.append(i+1)
1478
else:
1479
close_positions.append(i+1)
1480
from sage.combinat.tableau import StandardTableau
1481
return StandardTableau(filter(lambda x: x != [], [ open_positions, close_positions ]))
1482
1483
@combinatorial_map(name="to binary trees: up step, left tree, down step, right tree")
1484
def to_binary_tree(self, usemap="1L0R"):
1485
r"""
1486
Return a binary tree recursively constructed from the Dyck path
1487
by the sent ``usemap``. The default usemap is ``1L0R`` which means:
1488
1489
- an empty Dyck word is a leaf,
1490
1491
- a non empty Dyck word reads `1 L 0 R` where `L` and `R` correspond
1492
to respectively its left and right subtrees.
1493
1494
INPUT:
1495
1496
- ``usemap`` -- a string, either ``1L0R``, ``1R0L``, ``L1R0``,
1497
``R1L0``.
1498
1499
Other valid ``usemap`` are ``1R0L``, ``L1R0``, and ``R1L0`` and all
1500
corresponding to different recursive definitions of Dyck paths.
1501
1502
EXAMPLES::
1503
1504
sage: dw = DyckWord([1,0])
1505
sage: dw.to_binary_tree()
1506
[., .]
1507
sage: dw = DyckWord([])
1508
sage: dw.to_binary_tree()
1509
.
1510
sage: dw = DyckWord([1,0,1,1,0,0])
1511
sage: dw.to_binary_tree()
1512
[., [[., .], .]]
1513
sage: dw.to_binary_tree("L1R0")
1514
[[., .], [., .]]
1515
sage: dw = DyckWord([1,0,1,1,0,0,1,1,1,0,1,0,0,0])
1516
sage: dw.to_binary_tree() == dw.to_binary_tree("1R0L").left_right_symmetry()
1517
True
1518
sage: dw.to_binary_tree() == dw.to_binary_tree("L1R0").left_border_symmetry()
1519
False
1520
sage: dw.to_binary_tree("1R0L") == dw.to_binary_tree("L1R0").left_border_symmetry()
1521
True
1522
sage: dw.to_binary_tree("R1L0") == dw.to_binary_tree("L1R0").left_right_symmetry()
1523
True
1524
sage: dw.to_binary_tree("R10L")
1525
Traceback (most recent call last):
1526
...
1527
ValueError: R10L is not a correct map
1528
"""
1529
if usemap not in ["1L0R", "1R0L", "L1R0", "R1L0"]:
1530
raise ValueError("%s is not a correct map"%(usemap))
1531
from sage.combinat.binary_tree import BinaryTree
1532
if len(self) == 0:
1533
return BinaryTree()
1534
tp = [0]
1535
tp.extend(self.touch_points())
1536
l = len(self)
1537
if usemap[0] == '1': # we check what kind of reduction we want
1538
s0 = 1 #start point for first substree
1539
e0 = tp[1] *2 -1 #end point for first subtree
1540
s1 = e0 + 1 #start point for second subtree
1541
e1 = l #end point for second subtree
1542
else:
1543
s0 = 0
1544
e0 = tp[len(tp)-2] *2
1545
s1 = e0 + 1
1546
e1 = l - 1
1547
trees = [DyckWord(self[s0:e0]).to_binary_tree(usemap), DyckWord(self[s1:e1]).to_binary_tree(usemap)]
1548
if usemap[0] == "R" or usemap[1] == "R":
1549
trees.reverse()
1550
return BinaryTree(trees)
1551
1552
@combinatorial_map(name="to the Tamari corresponding Binary tree")
1553
def to_binary_tree_tamari(self):
1554
r"""
1555
Return the binary tree with consistency with the Tamari order
1556
of ``self``.
1557
1558
EXAMPLES::
1559
1560
sage: DyckWord([1,0]).to_binary_tree_tamari()
1561
[., .]
1562
sage: DyckWord([1,0,1,1,0,0]).to_binary_tree_tamari()
1563
[[., .], [., .]]
1564
sage: DyckWord([1,0,1,0,1,0]).to_binary_tree_tamari()
1565
[[[., .], .], .]
1566
"""
1567
return self.to_binary_tree("L1R0")
1568
1569
def to_area_sequence(self):
1570
r"""
1571
Return the area sequence of the Dyck word ``self``.
1572
1573
The area sequence of a Dyck word `w` is defined as follows:
1574
Representing the Dyck word `w` as a Dyck path from `(0, 0)` to
1575
`(n, n)` using `N` and `E` steps (this involves padding `w` by
1576
`E` steps until `w` reaches the main diagonal if `w` is not
1577
already a complete Dyck path), the area sequence of `w` is the
1578
sequence `(a_1, a_2, \ldots, a_n)`, where `a_i` is the number
1579
of full cells in the `i`-th row of the rectangle
1580
`[0, n] \times [0, n]` which lie completely above the diagonal.
1581
(The cells are the regions into which the rectangle is
1582
subdivided by the lines `x = i` with `i` integer and the lines
1583
`y = j` with `j` integer. The `i`-th row consists of all the
1584
cells between the lines `y = i-1` and `y = i`.)
1585
1586
An alternative definition:
1587
Representing the Dyck word `w` as a Dyck path consisting of
1588
`NE` and `SE` steps, the area sequence is the sequence of
1589
ordinates of all lattice points on the path which are
1590
starting points of `NE` steps.
1591
1592
A list of integers `l` is the area sequence of some Dyck path
1593
if and only if it satisfies `l_0 = 0` and
1594
`0 \leq l_{i+1} \leq l_i + 1` for `i > 0`.
1595
1596
EXAMPLES::
1597
1598
sage: DyckWord([]).to_area_sequence()
1599
[]
1600
sage: DyckWord([1, 0]).to_area_sequence()
1601
[0]
1602
sage: DyckWord([1, 1, 0, 0]).to_area_sequence()
1603
[0, 1]
1604
sage: DyckWord([1, 0, 1, 0]).to_area_sequence()
1605
[0, 0]
1606
sage: all(dw ==
1607
....: DyckWords().from_area_sequence(dw.to_area_sequence())
1608
....: for i in range(6) for dw in DyckWords(i))
1609
True
1610
sage: DyckWord([1,0,1,0,1,0,1,0,1,0]).to_area_sequence()
1611
[0, 0, 0, 0, 0]
1612
sage: DyckWord([1,1,1,1,1,0,0,0,0,0]).to_area_sequence()
1613
[0, 1, 2, 3, 4]
1614
sage: DyckWord([1,1,1,1,0,1,0,0,0,0]).to_area_sequence()
1615
[0, 1, 2, 3, 3]
1616
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).to_area_sequence()
1617
[0, 1, 1, 0, 1, 1, 1]
1618
"""
1619
seq = []
1620
a = 0
1621
for move in self:
1622
if move == open_symbol:
1623
seq.append(a)
1624
a += 1
1625
else:
1626
a -= 1
1627
return seq
1628
1629
class DyckWord_complete(DyckWord):
1630
r"""
1631
The class of complete
1632
:class:`Dyck words<sage.combinat.dyck_word.DyckWord>`.
1633
A Dyck word is complete, if it contains as many closers as openers.
1634
1635
For further information on Dyck words, see
1636
:class:`DyckWords_class<sage.combinat.dyck_word.DyckWord>`.
1637
"""
1638
def semilength(self):
1639
r"""
1640
Return the semilength of ``self``.
1641
1642
The semilength of a complete Dyck word `d` is the number of openers
1643
and the number of closers.
1644
1645
EXAMPLES::
1646
1647
sage: DyckWord([1, 0, 1, 0]).semilength()
1648
2
1649
1650
TESTS::
1651
1652
sage: DyckWord([]).semilength()
1653
0
1654
"""
1655
return len(self) // 2
1656
1657
@combinatorial_map(name='to partition')
1658
def to_partition(self):
1659
r"""
1660
Return the partition associated to ``self`` .
1661
1662
This partition is determined by thinking of ``self`` as a lattice path
1663
and considering the cells which are above the path but within the
1664
`n \times n` grid and the partition is formed by reading the sequence
1665
of the number of cells in this collection in each row.
1666
1667
OUTPUT:
1668
1669
- a partition representing the rows of cells in the square lattice
1670
and above the path
1671
1672
EXAMPLES::
1673
1674
sage: DyckWord([]).to_partition()
1675
[]
1676
sage: DyckWord([1,0]).to_partition()
1677
[]
1678
sage: DyckWord([1,1,0,0]).to_partition()
1679
[]
1680
sage: DyckWord([1,0,1,0]).to_partition()
1681
[1]
1682
sage: DyckWord([1,0,1,0,1,0]).to_partition()
1683
[2, 1]
1684
sage: DyckWord([1,1,0,0,1,0]).to_partition()
1685
[2]
1686
sage: DyckWord([1,0,1,1,0,0]).to_partition()
1687
[1, 1]
1688
"""
1689
from sage.combinat.partition import Partition
1690
n = len(self) // 2
1691
res = []
1692
for c in reversed(self):
1693
if c == close_symbol:
1694
n -= 1
1695
else:
1696
res.append(n)
1697
return Partition(res)
1698
1699
def number_of_parking_functions(self):
1700
r"""
1701
Return the number of parking functions with ``self`` as the supporting
1702
Dyck path.
1703
1704
One representation of a parking function is as a pair consisting of a
1705
Dyck path and a permutation `\pi` such that if
1706
`[a_0, a_1, \ldots, a_{n-1}]` is the area_sequence of the Dyck path
1707
(see :meth:`to_area_sequence<DyckWord.to_area_sequence>`) then the
1708
permutation `\pi` satisfies `\pi_i < \pi_{i+1}` whenever
1709
`a_{i} < a_{i+1}`. This function counts the number of permutations `\pi`
1710
which satisfy this condition.
1711
1712
EXAMPLES::
1713
1714
sage: DyckWord(area_sequence=[0,1,2]).number_of_parking_functions()
1715
1
1716
sage: DyckWord(area_sequence=[0,1,1]).number_of_parking_functions()
1717
3
1718
sage: DyckWord(area_sequence=[0,1,0]).number_of_parking_functions()
1719
3
1720
sage: DyckWord(area_sequence=[0,0,0]).number_of_parking_functions()
1721
6
1722
"""
1723
from sage.rings.arith import multinomial
1724
return multinomial( list(self.rise_composition()) )
1725
1726
def list_parking_functions( self ):
1727
r"""
1728
Return all parking functions whose supporting Dyck path is ``self``.
1729
1730
EXAMPLES::
1731
1732
sage: DyckWord([1,1,0,0,1,0]).list_parking_functions()
1733
Permutations of the multi-set [1, 1, 3]
1734
sage: DyckWord([1,1,1,0,0,0]).list_parking_functions()
1735
Permutations of the multi-set [1, 1, 1]
1736
sage: DyckWord([1,0,1,0,1,0]).list_parking_functions()
1737
Permutations of the set [1, 2, 3]
1738
"""
1739
alist = self.to_area_sequence()
1740
return Permutations([i - alist[i]+1 for i in range(len(alist))])
1741
# TODO: upon implementation of ParkingFunction class
1742
# map(ParkingFunction, Permutations([i - alist[i]+1 for i in range(len(alist))]))
1743
1744
def reading_permutation(self):
1745
r"""
1746
The permutation formed by taking the reading word of the Dyck path
1747
representing ``self`` (with `N` and `E` steps) if the vertical
1748
edges of the Dyck path are labeled from bottom to top with `1`
1749
through `n` and the diagonals are read from top to bottom starting
1750
with the diagonal furthest from the main diagonal.
1751
1752
EXAMPLES::
1753
1754
sage: DyckWord([1,0,1,0]).reading_permutation()
1755
[2, 1]
1756
sage: DyckWord([1,1,0,0]).reading_permutation()
1757
[2, 1]
1758
sage: DyckWord([1,1,0,1,0,0]).reading_permutation()
1759
[3, 2, 1]
1760
sage: DyckWord([1,1,0,0,1,0]).reading_permutation()
1761
[2, 3, 1]
1762
sage: DyckWord([1,0,1,1,0,0,1,0]).reading_permutation()
1763
[3, 4, 2, 1]
1764
"""
1765
alist = self.to_area_sequence()
1766
if len(alist) == 0:
1767
return Permutation([])
1768
m = max(alist)
1769
p1 = Word([m-alist[-i-1] for i in range(len(alist))]).standard_permutation()
1770
return p1.inverse().complement()
1771
1772
def characteristic_symmetric_function(self, q=None, R=QQ['q','t'].fraction_field()):
1773
r"""
1774
The characteristic function of ``self`` is the sum of
1775
`q^{dinv(D,F)} Q_{ides(read(D,F))}` over all permutation
1776
fillings of the Dyck path representing ``self``, where
1777
`ides(read(D,F))` is the descent composition of the inverse of the
1778
reading word of the filling.
1779
1780
INPUT:
1781
1782
- ``q`` -- (default: ``q = R('q')``) a parameter for the generating
1783
function power
1784
1785
- ``R`` -- (default : ``R = QQ['q','t'].fraction_field()``) the base
1786
ring to do the calculations over
1787
1788
OUTPUT:
1789
1790
- an element of the symmetric functions over the ring ``R``
1791
(in the Schur basis).
1792
1793
EXAMPLES::
1794
1795
sage: R = QQ['q','t'].fraction_field()
1796
sage: (q,t) = R.gens()
1797
sage: f = sum(t**D.area()*D.characteristic_symmetric_function() for D in DyckWords(3)); f
1798
(q^3+q^2*t+q*t^2+t^3+q*t)*s[1, 1, 1] + (q^2+q*t+t^2+q+t)*s[2, 1] + s[3]
1799
sage: f.nabla(power=-1)
1800
s[1, 1, 1]
1801
"""
1802
from sage.combinat.ncsf_qsym.qsym import QuasiSymmetricFunctions
1803
from sage.combinat.sf.sf import SymmetricFunctions
1804
if q is None:
1805
q=R('q')
1806
else:
1807
if not q in R:
1808
raise ValueError("q=%s must be an element of the base ring %s"%(q,R))
1809
F = QuasiSymmetricFunctions(R).Fundamental()
1810
p = self.reading_permutation().inverse()
1811
perms = [Word(perm).standard_permutation() for perm in self.list_parking_functions()]
1812
QSexpr = sum( q**self.dinv(pv.inverse())*F(Permutation([p(i) for i in pv]).descents_composition()) for pv in perms)
1813
s = SymmetricFunctions(R).s()
1814
return s(QSexpr.to_symmetric_function())
1815
1816
def to_pair_of_standard_tableaux(self):
1817
r"""
1818
Convert ``self`` to a pair of standard tableaux of the same shape and
1819
of length less than or equal to two.
1820
1821
EXAMPLES::
1822
1823
sage: DyckWord([1,0,1,0]).to_pair_of_standard_tableaux()
1824
([[1], [2]], [[1], [2]])
1825
sage: DyckWord([1,1,0,0]).to_pair_of_standard_tableaux()
1826
([[1, 2]], [[1, 2]])
1827
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).to_pair_of_standard_tableaux()
1828
([[1, 2, 4, 7], [3, 5, 6]], [[1, 2, 4, 6], [3, 5, 7]])
1829
"""
1830
from sage.combinat.tableau import Tableau
1831
n = self.semilength()
1832
if n==0:
1833
return (Tableau([]), Tableau([]))
1834
elif self.height()==n:
1835
return (Tableau([range(1,n+1)]),Tableau([range(1,n+1)]))
1836
else:
1837
left = [[],[]]
1838
right = [[], []]
1839
for pos in range(n):
1840
if self[pos] == open_symbol:
1841
left[0].append(pos+1)
1842
else:
1843
left[1].append(pos+1)
1844
if self[-pos-1] == close_symbol:
1845
right[0].append(pos+1)
1846
else:
1847
right[1].append(pos+1)
1848
return (Tableau(left), Tableau(right))
1849
1850
@combinatorial_map(name='to 312 avoiding permutation')
1851
def to_312_avoiding_permutation(self):
1852
r"""
1853
Convert ``self`` to a `312`-avoiding permutation using the bijection by
1854
Bandlow and Killpatrick in [BK2001]_. Sends the area to the
1855
inversion number.
1856
1857
REFERENCES:
1858
1859
.. [BK2001] J. Bandlow, K. Killpatrick -- An area-to_inv bijection
1860
between Dyck paths and 312-avoiding permutations, Electronic Journal
1861
of Combinatorics, Volume 8, Issue 1 (2001).
1862
1863
EXAMPLES::
1864
1865
sage: DyckWord([1,1,0,0]).to_312_avoiding_permutation()
1866
[2, 1]
1867
sage: DyckWord([1,0,1,0]).to_312_avoiding_permutation()
1868
[1, 2]
1869
sage: p = DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).to_312_avoiding_permutation(); p
1870
[2, 3, 1, 5, 6, 7, 4]
1871
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).area()
1872
5
1873
sage: p.length()
1874
5
1875
1876
TESTS::
1877
1878
sage: PD = [D.to_312_avoiding_permutation() for D in DyckWords(5)]
1879
sage: all(pi.avoids([3,1,2]) for pi in PD)
1880
True
1881
sage: all(D.area()==D.to_312_avoiding_permutation().length() for D in DyckWords(5))
1882
True
1883
"""
1884
n = self.semilength()
1885
area = self.to_area_sequence()
1886
from sage.groups.perm_gps.permgroup_named import SymmetricGroup
1887
pi = SymmetricGroup(n).one()
1888
for j in range(n):
1889
for i in range(area[j]):
1890
pi = pi.apply_simple_reflection(j-i)
1891
return Permutation(~pi)
1892
1893
@combinatorial_map(name='to non-crossing permutation')
1894
def to_noncrossing_permutation(self):
1895
r"""
1896
Use the bijection by C. Stump in [Stu2008]_ to send ``self`` to a
1897
non-crossing permutation.
1898
1899
A non-crossing permutation when written in cyclic notation has cycles
1900
which are strictly increasing. Sends the area to the inversion number
1901
and ``self.major_index()`` to `n(n-1) - maj(\sigma) - maj(\sigma^{-1})`.
1902
Uses the function :func:`~sage.combinat.dyck_word.pealing`
1903
1904
REFERENCES:
1905
1906
.. [Stu2008] C. Stump -- More bijective Catalan combinatorics on
1907
permutations and on colored permutations, Preprint.
1908
:arXiv:`0808.2822`.
1909
1910
EXAMPLES::
1911
1912
sage: DyckWord([1,1,0,0]).to_noncrossing_permutation()
1913
[2, 1]
1914
sage: DyckWord([1,0,1,0]).to_noncrossing_permutation()
1915
[1, 2]
1916
sage: p = DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).to_noncrossing_permutation(); p
1917
[2, 3, 1, 5, 6, 7, 4]
1918
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).area()
1919
5
1920
sage: p.length()
1921
5
1922
1923
TESTS::
1924
1925
sage: all(D.area()==D.to_noncrossing_permutation().length() for D in DyckWords(5))
1926
True
1927
sage: all(20-D.major_index()==D.to_noncrossing_permutation().major_index()
1928
....: +D.to_noncrossing_permutation().imajor_index() for D in DyckWords(5))
1929
True
1930
"""
1931
n = self.semilength()
1932
if n==0:
1933
return Permutation([])
1934
D,touch_sequence = pealing(self, return_touches=True)
1935
from sage.groups.perm_gps.permgroup_named import SymmetricGroup
1936
S = SymmetricGroup(n)
1937
pi = S.one()
1938
while touch_sequence:
1939
for touches in touch_sequence:
1940
pi = pi * S( tuple(touches) )
1941
D,touch_sequence = pealing(D, return_touches=True)
1942
return Permutation(pi)
1943
1944
@combinatorial_map(name='to 321 avoiding permutation')
1945
def to_321_avoiding_permutation(self):
1946
r"""
1947
Use the bijection (pp. 60-61 of [Knu1973]_ or section 3.1 of [CK2008]_)
1948
to send ``self`` to a `321`-avoiding permutation.
1949
1950
It is shown in [EP2004]_ that it sends the number of centered tunnels
1951
to the number of fixed points, the number of right tunnels to the
1952
number of exceedences, and the semilength plus the height of the middle
1953
point to 2 times the length of the longest increasing subsequence.
1954
1955
REFERENCES:
1956
1957
.. [EP2004] S. Elizalde, I. Pak. *Bijections for refined restricted
1958
permutations**. JCTA 105(2) 2004.
1959
.. [CK2008] A. Claesson, S. Kitaev. *Classification of bijections
1960
between `321`- and `132`- avoiding permutations*. Seminaire
1961
Lotharingien de Combinatoire **60** 2008. :arxiv:`0805.1325`.
1962
.. [Knu1973] D. Knuth. *The Art of Computer Programming, Vol. III*.
1963
Addison-Wesley. Reading, MA. 1973.
1964
1965
EXAMPLES::
1966
1967
sage: DyckWord([1,0,1,0]).to_321_avoiding_permutation()
1968
[2, 1]
1969
sage: DyckWord([1,1,0,0]).to_321_avoiding_permutation()
1970
[1, 2]
1971
sage: D = DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0])
1972
sage: p = D.to_321_avoiding_permutation()
1973
sage: p
1974
[3, 5, 1, 6, 2, 7, 4]
1975
sage: D.number_of_tunnels()
1976
0
1977
sage: p.number_of_fixed_points()
1978
0
1979
sage: D.number_of_tunnels('right')
1980
4
1981
sage: len(p.weak_excedences())-p.number_of_fixed_points()
1982
4
1983
sage: n = D.semilength()
1984
sage: D.heights()[n] + n
1985
8
1986
sage: 2*p.longest_increasing_subsequence_length()
1987
8
1988
1989
TESTS::
1990
1991
sage: PD = [D.to_321_avoiding_permutation() for D in DyckWords(5)]
1992
sage: all(pi.avoids([3,2,1]) for pi in PD)
1993
True
1994
sage: to_perm = lambda x: x.to_321_avoiding_permutation()
1995
sage: all(D.number_of_tunnels() == to_perm(D).number_of_fixed_points()
1996
....: for D in DyckWords(5))
1997
True
1998
sage: all(D.number_of_tunnels('right') == len(to_perm(D).weak_excedences())
1999
....: -to_perm(D).number_of_fixed_points() for D in DyckWords(5))
2000
True
2001
sage: all(D.heights()[5]+5 == 2*to_perm(D).longest_increasing_subsequence_length()
2002
....: for D in DyckWords(5))
2003
True
2004
"""
2005
from sage.combinat.rsk import RSK_inverse
2006
A,B = self.to_pair_of_standard_tableaux()
2007
return RSK_inverse(A,B, output='permutation')
2008
2009
@combinatorial_map(name='to 132 avoiding permutation')
2010
def to_132_avoiding_permutation(self):
2011
r"""
2012
Use the bijection by C. Krattenthaler in [Kra2001]_ to send ``self``
2013
to a `132`-avoiding permutation.
2014
2015
REFERENCES:
2016
2017
.. [Kra2001] C. Krattenthaler -- Permutations with restricted patterns and Dyck
2018
paths, Adv. Appl. Math. 27 (2001), 510--530.
2019
2020
EXAMPLES::
2021
2022
sage: DyckWord([1,1,0,0]).to_132_avoiding_permutation()
2023
[1, 2]
2024
sage: DyckWord([1,0,1,0]).to_132_avoiding_permutation()
2025
[2, 1]
2026
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).to_132_avoiding_permutation()
2027
[6, 5, 4, 7, 2, 1, 3]
2028
2029
TESTS::
2030
2031
sage: PD = [D.to_132_avoiding_permutation() for D in DyckWords(5)]
2032
sage: all(pi.avoids([1,3,2]) for pi in PD)
2033
True
2034
"""
2035
n = self.semilength()
2036
area = self.to_area_sequence()
2037
area.append(0)
2038
pi = []
2039
values = range(1,n+1)
2040
for i in range(n):
2041
if area[n-i-1]+1 > area[n-i]:
2042
pi.append(n-i-area[n-i-1])
2043
values.remove(n-i-area[n-i-1])
2044
else:
2045
v = min( v for v in values if v > n-i-area[n-i-1] )
2046
pi.append(v)
2047
values.remove(v)
2048
return Permutation(pi)
2049
2050
def to_permutation(self,map):
2051
r"""
2052
This is simply a method collecting all implemented maps from Dyck
2053
words to permutations.
2054
2055
INPUT:
2056
2057
- ``map`` -- defines the map from Dyck words to permutations.
2058
These are currently:
2059
2060
- ``Bandlow-Killpatrick``: :func:`to_312_avoiding_permutation`
2061
- ``Knuth``: :func:`to_321_avoiding_permutation`
2062
- ``Krattenthaler``: :func:`to_132_avoiding_permutation`
2063
- ``Stump``: :func:`to_noncrossing_permutation`
2064
2065
EXAMPLES::
2066
2067
sage: D = DyckWord([1,1,1,0,1,0,0,0])
2068
sage: D.pretty_print()
2069
_____
2070
_| x x
2071
| x x .
2072
| x . .
2073
| . . .
2074
2075
sage: D.to_permutation(map="Bandlow-Killpatrick")
2076
[3, 4, 2, 1]
2077
sage: D.to_permutation(map="Stump")
2078
[4, 2, 3, 1]
2079
sage: D.to_permutation(map="Knuth")
2080
[1, 2, 4, 3]
2081
sage: D.to_permutation(map="Krattenthaler")
2082
[2, 1, 3, 4]
2083
"""
2084
if map=="Bandlow-Killpatrick":
2085
return self.to_312_avoiding_permutation()
2086
elif map=="Knuth":
2087
return self.to_321_avoiding_permutation()
2088
elif map=="Krattenthaler":
2089
return self.to_132_avoiding_permutation()
2090
elif map=="Stump":
2091
return self.to_noncrossing_permutation()
2092
else:
2093
raise ValueError("The given map is not valid.")
2094
2095
def to_noncrossing_partition(self, bijection=None):
2096
r"""
2097
Bijection of Biane from ``self`` to a noncrossing partition.
2098
2099
There is an optional parameter ``bijection`` that indicates if a
2100
different bijection from Dyck words to non-crossing partitions
2101
should be used (since there are potentially many).
2102
2103
If the parameter ``bijection`` is "Stump" then the bijection used is
2104
from [Stu2008]_, see also the method :meth:`to_noncrossing_permutation`.
2105
2106
Thanks to Mathieu Dutour for describing the bijection. See also
2107
:func:`from_noncrossing_partition`.
2108
2109
EXAMPLES::
2110
2111
sage: DyckWord([]).to_noncrossing_partition()
2112
[]
2113
sage: DyckWord([1, 0]).to_noncrossing_partition()
2114
[[1]]
2115
sage: DyckWord([1, 1, 0, 0]).to_noncrossing_partition()
2116
[[1, 2]]
2117
sage: DyckWord([1, 1, 1, 0, 0, 0]).to_noncrossing_partition()
2118
[[1, 2, 3]]
2119
sage: DyckWord([1, 0, 1, 0, 1, 0]).to_noncrossing_partition()
2120
[[1], [2], [3]]
2121
sage: DyckWord([1, 1, 0, 1, 0, 0]).to_noncrossing_partition()
2122
[[2], [1, 3]]
2123
sage: DyckWord([]).to_noncrossing_partition("Stump")
2124
[]
2125
sage: DyckWord([1, 0]).to_noncrossing_partition("Stump")
2126
[[1]]
2127
sage: DyckWord([1, 1, 0, 0]).to_noncrossing_partition("Stump")
2128
[[1, 2]]
2129
sage: DyckWord([1, 1, 1, 0, 0, 0]).to_noncrossing_partition("Stump")
2130
[[1, 3], [2]]
2131
sage: DyckWord([1, 0, 1, 0, 1, 0]).to_noncrossing_partition("Stump")
2132
[[1], [2], [3]]
2133
sage: DyckWord([1, 1, 0, 1, 0, 0]).to_noncrossing_partition("Stump")
2134
[[1, 2, 3]]
2135
"""
2136
if bijection=="Stump":
2137
return [[v for v in c] for c in self.to_noncrossing_permutation().cycle_tuples()]
2138
partition = []
2139
stack = []
2140
i = 0
2141
p = 1
2142
2143
#Invariants:
2144
# - self[i] = 0
2145
# - p is the number of opening parens at position i
2146
2147
while i < len(self):
2148
stack.append(p)
2149
j = i + 1
2150
while j < len(self) and self[j] == close_symbol:
2151
j += 1
2152
2153
#Now j points to the next 1 or past the end of self
2154
nz = j - (i+1) # the number of )'s between i and j
2155
if nz > 0:
2156
# Remove the nz last elements of stack and
2157
# make a new part in partition
2158
if nz > len(stack):
2159
raise ValueError("incorrect Dyck word")
2160
2161
partition.append( stack[-nz:] )
2162
2163
stack = stack[: -nz]
2164
i = j
2165
p += 1
2166
2167
if len(stack) > 0:
2168
raise ValueError("incorrect Dyck word")
2169
2170
return partition
2171
2172
def to_Catalan_code(self):
2173
r"""
2174
Return the Catalan code associated to ``self`` . The Catalan code is a
2175
sequence of non--negative integers `a_i` such that if `i < j` and
2176
`a_i > 0` and `a_j > 0` and `a_{i+1} = a_{i+2} = \cdots = a_{j-1} = 0`
2177
then `a_i - a_j < j-i`.
2178
2179
The Catalan code of a Dyck word is example (x) in Richard Stanley's
2180
exercises on combinatorial interpretations for Catalan objects.
2181
The code in this example is the reverse of the description provided
2182
there. See [Sta1999]_ and [Sta]_.
2183
2184
EXAMPLES::
2185
2186
sage: DyckWord([]).to_Catalan_code()
2187
[]
2188
sage: DyckWord([1, 0]).to_Catalan_code()
2189
[0]
2190
sage: DyckWord([1, 1, 0, 0]).to_Catalan_code()
2191
[0, 1]
2192
sage: DyckWord([1, 0, 1, 0]).to_Catalan_code()
2193
[0, 0]
2194
sage: all(dw ==
2195
....: DyckWords().from_Catalan_code(dw.to_Catalan_code())
2196
....: for i in range(6) for dw in DyckWords(i))
2197
True
2198
"""
2199
if not self:
2200
return []
2201
cut = self.associated_parenthesis(0)
2202
recdw = DyckWord(self[1:cut]+self[cut+1:])
2203
returns = [0]+recdw.returns_to_zero()
2204
res = recdw.to_Catalan_code()
2205
res.append(returns.index(cut-1))
2206
return res
2207
2208
@classmethod
2209
def from_Catalan_code(cls, code):
2210
r"""
2211
This is deprecated in :trac:`14875`. Use instead
2212
:meth:`CompleteDyckWords.from_Catalan_code()`.
2213
2214
EXAMPLES::
2215
2216
sage: from sage.combinat.dyck_word import DyckWord_complete
2217
sage: DyckWord_complete.from_Catalan_code([])
2218
doctest:...: DeprecationWarning: this method is deprecated. Use DyckWords().from_Catalan_code instead.
2219
See http://trac.sagemath.org/14875 for details.
2220
[]
2221
"""
2222
from sage.misc.superseded import deprecation
2223
deprecation(14875,'this method is deprecated. Use DyckWords().from_Catalan_code instead.')
2224
return CompleteDyckWords_all().from_Catalan_code(code)
2225
2226
@classmethod
2227
def from_area_sequence(cls, code):
2228
r"""
2229
This is deprecated in :trac:`14875`. Use instead
2230
:meth:`CompleteDyckWords.from_area_sequence()`.
2231
2232
EXAMPLES::
2233
2234
sage: from sage.combinat.dyck_word import DyckWord_complete
2235
sage: DyckWord_complete.from_area_sequence([])
2236
doctest:...: DeprecationWarning: this method is deprecated. Use DyckWords().from_area_sequence instead.
2237
See http://trac.sagemath.org/14875 for details.
2238
[]
2239
"""
2240
from sage.misc.superseded import deprecation
2241
deprecation(14875,'this method is deprecated. Use DyckWords().from_area_sequence instead.')
2242
return CompleteDyckWords_all().from_area_sequence(code)
2243
2244
@combinatorial_map(name="To Ordered tree")
2245
def to_ordered_tree(self):
2246
r"""
2247
Return the ordered tree corresponding to ``self`` where the depth
2248
of the tree is the maximal height of ``self``.
2249
2250
EXAMPLES::
2251
2252
sage: D = DyckWord([1,1,0,0])
2253
sage: D.to_ordered_tree()
2254
[[[]]]
2255
sage: D = DyckWord([1,0,1,0])
2256
sage: D.to_ordered_tree()
2257
[[], []]
2258
sage: D = DyckWord([1, 0, 1, 1, 0, 0])
2259
sage: D.to_ordered_tree()
2260
[[], [[]]]
2261
sage: D = DyckWord([1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0])
2262
sage: D.to_ordered_tree()
2263
[[], [[], []], [[], [[]]]]
2264
2265
TESTS::
2266
2267
sage: D = DyckWord([1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0])
2268
sage: D == D.to_ordered_tree().to_dyck_word()
2269
True
2270
"""
2271
from sage.combinat.ordered_tree import OrderedTree
2272
levels = [OrderedTree().clone()]
2273
for u in self:
2274
if u == 1:
2275
levels.append(OrderedTree().clone())
2276
else:
2277
tree =levels.pop()
2278
tree.set_immutable()
2279
root = levels.pop()
2280
root.append(tree)
2281
levels.append(root)
2282
root = levels[0]
2283
root.set_immutable()
2284
return root
2285
2286
def to_triangulation(self):
2287
r"""
2288
Map ``self`` to a triangulation.
2289
2290
.. TODO::
2291
2292
Implement :meth:`DyckWord_complete.to_triangulation`.
2293
2294
TESTS::
2295
2296
sage: DyckWord([1, 1, 0, 0]).to_triangulation()
2297
Traceback (most recent call last):
2298
...
2299
NotImplementedError: TODO
2300
"""
2301
raise NotImplementedError("TODO")
2302
2303
def to_non_decreasing_parking_function(self):
2304
r"""
2305
Bijection to :class:`non-decreasing parking
2306
functions<sage.combinat.non_decreasing_parking_function.NonDecreasingParkingFunctions>`.
2307
See there the method
2308
:meth:`~sage.combinat.non_decreasing_parking_function.NonDecreasingParkingFunction.to_dyck_word`
2309
for more information.
2310
2311
EXAMPLES::
2312
2313
sage: DyckWord([]).to_non_decreasing_parking_function()
2314
[]
2315
sage: DyckWord([1,0]).to_non_decreasing_parking_function()
2316
[1]
2317
sage: DyckWord([1,1,0,0]).to_non_decreasing_parking_function()
2318
[1, 1]
2319
sage: DyckWord([1,0,1,0]).to_non_decreasing_parking_function()
2320
[1, 2]
2321
sage: DyckWord([1,0,1,1,0,1,0,0,1,0]).to_non_decreasing_parking_function()
2322
[1, 2, 2, 3, 5]
2323
2324
TESTS::
2325
2326
sage: ld=DyckWords(5);
2327
sage: list(ld) == [dw.to_non_decreasing_parking_function().to_dyck_word() for dw in ld]
2328
True
2329
"""
2330
from sage.combinat.non_decreasing_parking_function import NonDecreasingParkingFunction
2331
return NonDecreasingParkingFunction.from_dyck_word(self)
2332
2333
@classmethod
2334
def from_non_decreasing_parking_function(cls, pf):
2335
r"""
2336
This is deprecated in :trac:`14875`. Use instead
2337
:meth:`CompleteDyckWords.from_non_decreasing_parking_function()`.
2338
2339
EXAMPLES::
2340
2341
sage: from sage.combinat.dyck_word import DyckWord_complete
2342
sage: DyckWord_complete.from_non_decreasing_parking_function([])
2343
doctest:...: DeprecationWarning: this method is deprecated. Use DyckWords().from_non_decreasing_parking_function instead.
2344
See http://trac.sagemath.org/14875 for details.
2345
[]
2346
"""
2347
from sage.misc.superseded import deprecation
2348
deprecation(14875,'this method is deprecated. Use DyckWords().from_non_decreasing_parking_function instead.')
2349
return CompleteDyckWords_all().from_area_sequence([ i - pf[i] + 1 for i in range(len(pf)) ])
2350
2351
def major_index(self):
2352
r"""
2353
Return the major index of ``self`` .
2354
2355
The major index of a Dyck word `D` is the sum of the positions of
2356
the valleys of `D` (when started counting at position ``1``).
2357
2358
EXAMPLES::
2359
2360
sage: DyckWord([1, 0, 1, 0]).major_index()
2361
2
2362
sage: DyckWord([1, 1, 0, 0]).major_index()
2363
0
2364
sage: DyckWord([1, 1, 0, 0, 1, 0]).major_index()
2365
4
2366
sage: DyckWord([1, 0, 1, 1, 0, 0]).major_index()
2367
2
2368
2369
TESTS::
2370
2371
sage: DyckWord([]).major_index()
2372
0
2373
sage: DyckWord([1, 0]).major_index()
2374
0
2375
"""
2376
valleys = self.valleys()
2377
return sum(valleys) + len(valleys)
2378
2379
def pyramid_weight( self ):
2380
r"""
2381
A pyramid of ``self`` is a subsequence of the form
2382
`1^h 0^h`. A pyramid is maximal if it is neither preceded by a `1`
2383
nor followed by a `0`.
2384
2385
The pyramid weight of a Dyck path is the sum of the lengths of the
2386
maximal pyramids and was defined in [DS1992]_.
2387
2388
EXAMPLES::
2389
2390
sage: DyckWord([1,1,0,1,1,1,0,0,1,0,0,0,1,1,0,0]).pyramid_weight()
2391
6
2392
sage: DyckWord([1,1,1,0,0,0]).pyramid_weight()
2393
3
2394
sage: DyckWord([1,0,1,0,1,0]).pyramid_weight()
2395
3
2396
sage: DyckWord([1,1,0,1,0,0]).pyramid_weight()
2397
2
2398
2399
REFERENCES:
2400
2401
.. [DS1992] A. Denise, R. Simion, Two combinatorial statistics on
2402
Dyck paths, Discrete Math 137 (1992), 155--176.
2403
"""
2404
aseq = self.to_area_sequence() + [0]
2405
bseq = self.reverse().to_area_sequence() + [0]
2406
apeak = []
2407
bpeak = []
2408
for i in range(len(aseq)-1):
2409
if aseq[i+1]<=aseq[i]:
2410
apeak.append(i)
2411
if bseq[i+1]<=bseq[i]:
2412
bpeak.append(i)
2413
out = 0
2414
for i in range(len(apeak)):
2415
out += min(aseq[apeak[i]]-aseq[apeak[i]+1]+1,bseq[bpeak[-i-1]]-bseq[bpeak[-i-1]+1]+1)
2416
return out
2417
2418
def tunnels(self):
2419
r"""
2420
Return the list of ranges of the matching parentheses in the Dyck
2421
word ``self``.
2422
That is, if ``(a,b)`` is in ``self.tunnels()``, then the matching
2423
parenthesis to ``self[a]`` is ``self[b-1]``.
2424
2425
EXAMPLES::
2426
2427
sage: DyckWord([1, 1, 0, 1, 1, 0, 0, 1, 0, 0]).tunnels()
2428
[(0, 10), (1, 3), (3, 7), (4, 6), (7, 9)]
2429
"""
2430
heights = self.heights()
2431
tunnels = []
2432
for i in range(len(heights)-1):
2433
height = heights[i]
2434
if height < heights[i+1]:
2435
tunnels.append( (i,i+1+heights[i+1:].index(height)) )
2436
return tunnels
2437
2438
def number_of_tunnels(self,tunnel_type='centered'):
2439
r"""
2440
Return the number of tunnels of ``self`` of type ``tunnel_type``.
2441
2442
A tunnel is a pair `(a,b)` where ``a`` is the position of an open
2443
parenthesis and ``b`` is the position of the matching close
2444
parenthesis. If `a + b = n` then the tunnel is called *centered* . If
2445
`a + b < n` then the tunnel is called *left* and if `a + b > n`, then
2446
the tunnel is called *right*.
2447
2448
INPUT:
2449
2450
- ``tunnel_type`` -- (default: ``'centered'``) can be one of the
2451
following: ``'left'``, ``'right'``, ``'centered'``, or ``'all'``.
2452
2453
EXAMPLES::
2454
2455
sage: DyckWord([1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0]).number_of_tunnels()
2456
0
2457
sage: DyckWord([1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0]).number_of_tunnels('left')
2458
5
2459
sage: DyckWord([1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0]).number_of_tunnels('right')
2460
2
2461
sage: DyckWord([1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0]).number_of_tunnels('all')
2462
7
2463
sage: DyckWord([1, 1, 0, 0]).number_of_tunnels('centered')
2464
2
2465
"""
2466
n = len(self)
2467
tunnels = self.tunnels()
2468
if tunnel_type == 'left':
2469
return len( [ i for (i,j) in tunnels if i+j < n ] )
2470
elif tunnel_type == 'centered':
2471
return len( [ i for (i,j) in tunnels if i+j == n ] )
2472
elif tunnel_type == 'right':
2473
return len( [ i for (i,j) in tunnels if i+j > n ] )
2474
elif tunnel_type == 'all':
2475
return len(tunnels)
2476
else:
2477
raise ValueError("The given tunnel_type is not valid.")
2478
2479
@combinatorial_map(order = 2, name="Reverse path")
2480
def reverse(self):
2481
r"""
2482
Return the reverse and complement of ``self``.
2483
2484
This operation corresponds to flipping the Dyck path across the
2485
`y=-x` line.
2486
2487
EXAMPLES::
2488
2489
sage: DyckWord([1,1,0,0,1,0]).reverse()
2490
[1, 0, 1, 1, 0, 0]
2491
sage: DyckWord([1,1,1,0,0,0]).reverse()
2492
[1, 1, 1, 0, 0, 0]
2493
sage: len(filter(lambda D: D.reverse() == D, DyckWords(5)))
2494
10
2495
2496
TESTS::
2497
2498
sage: DyckWord([]).reverse()
2499
[]
2500
"""
2501
list = []
2502
for i in range(len(self)):
2503
if self[i] == open_symbol:
2504
list.append(close_symbol)
2505
else:
2506
list.append(open_symbol)
2507
list.reverse()
2508
return DyckWord(list)
2509
2510
def first_return_decomposition(self):
2511
r"""
2512
Decompose a Dyck word into a pair of Dyck words (potentially empty)
2513
where the first word consists of the word after the first up step and
2514
the corresponding matching closing parenthesis.
2515
2516
EXAMPLES::
2517
2518
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).first_return_decomposition()
2519
([1, 0, 1, 0], [1, 1, 0, 1, 0, 1, 0, 0])
2520
sage: DyckWord([1,1,0,0]).first_return_decomposition()
2521
([1, 0], [])
2522
sage: DyckWord([1,0,1,0]).first_return_decomposition()
2523
([], [1, 0])
2524
"""
2525
k = self.position_of_first_return()*2
2526
return DyckWord(self[1:k-1]),DyckWord(self[k:])
2527
2528
def decomposition_reverse(self):
2529
r"""
2530
Return the involution of ``self`` with a recursive definition.
2531
2532
If a Dyck word `D` decomposes as `1 D_1 0 D_2` where `D_1` and
2533
`D_2` are complete Dyck words then the decomposition reverse is
2534
`1 \phi(D_2) 0 \phi(D_1)`.
2535
2536
EXAMPLES::
2537
2538
sage: DyckWord([1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0]).decomposition_reverse()
2539
[1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0]
2540
sage: DyckWord([1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0]).decomposition_reverse()
2541
[1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0]
2542
sage: DyckWord([1,1,0,0]).decomposition_reverse()
2543
[1, 0, 1, 0]
2544
sage: DyckWord([1,0,1,0]).decomposition_reverse()
2545
[1, 1, 0, 0]
2546
"""
2547
if self.semilength() == 0:
2548
return self
2549
else:
2550
D1,D2 = self.first_return_decomposition()
2551
return DyckWord([1]+list(D2.decomposition_reverse())+[0]+list(D1.decomposition_reverse()))
2552
2553
@combinatorial_map(name="Area-dinv to bounce-area")
2554
def area_dinv_to_bounce_area_map(self):
2555
r"""
2556
Return the image of ``self`` under the map which sends a
2557
Dyck word with ``area`` equal to `r` and ``dinv`` equal to `s` to a
2558
Dyck word with ``bounce`` equal to `r` and ``area`` equal to `s` .
2559
2560
The inverse of this map is :meth:`bounce_area_to_area_dinv_map`.
2561
2562
For a definition of this map, see [Hag2008]_ p. 50 where it is called
2563
`\zeta`. However, this map differs from Haglund's map by an application
2564
of :meth:`reverse` (as does the definition of the :meth:`bounce`
2565
statistic).
2566
2567
EXAMPLES::
2568
2569
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).area_dinv_to_bounce_area_map()
2570
[1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0]
2571
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).area()
2572
5
2573
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).dinv()
2574
13
2575
sage: DyckWord([1,1,1,1,1,0,0,0,1,0,0,1,0,0]).area()
2576
13
2577
sage: DyckWord([1,1,1,1,1,0,0,0,1,0,0,1,0,0]).bounce()
2578
5
2579
sage: DyckWord([1,1,1,1,1,0,0,0,1,0,0,1,0,0]).area_dinv_to_bounce_area_map()
2580
[1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0]
2581
sage: DyckWord([1,1,0,0]).area_dinv_to_bounce_area_map()
2582
[1, 0, 1, 0]
2583
sage: DyckWord([1,0,1,0]).area_dinv_to_bounce_area_map()
2584
[1, 1, 0, 0]
2585
"""
2586
a = self.to_area_sequence()
2587
if a == []:
2588
return self
2589
a.reverse()
2590
image = []
2591
for i in range(max(a),-2,-1):
2592
for j in a:
2593
if j == i:
2594
image.append(1)
2595
elif j == i + 1:
2596
image.append(0)
2597
return DyckWord(image)
2598
2599
@combinatorial_map(name="Bounce-area to area-dinv")
2600
def bounce_area_to_area_dinv_map( D ):
2601
r"""
2602
Return the image of the Dyck word under the map which sends a
2603
Dyck word with ``bounce`` equal to `r` and ``area`` equal to `s` to a
2604
Dyck word with ``area`` equal to `r` and ``dinv`` equal to `s` .
2605
2606
This implementation uses a recursive method by saying that
2607
the last entry in the area sequence of `D` is equal to the number of
2608
touch points of the Dyck path minus 1 of the image of this map.
2609
2610
The inverse of this map is :meth:`area_dinv_to_bounce_area_map`.
2611
2612
For a definition of this map, see [Hag2008]_ p. 50 where it is called
2613
`\zeta^{-1}`. However, this map differs from Haglund's map by an
2614
application of :meth:`reverse` (as does the definition of the
2615
:meth:`bounce` statistic).
2616
2617
EXAMPLES::
2618
2619
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).bounce_area_to_area_dinv_map()
2620
[1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0]
2621
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).area()
2622
5
2623
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).bounce()
2624
9
2625
sage: DyckWord([1,1,0,0,1,1,1,1,0,0,1,0,0,0]).area()
2626
9
2627
sage: DyckWord([1,1,0,0,1,1,1,1,0,0,1,0,0,0]).dinv()
2628
5
2629
sage: all(D==D.bounce_area_to_area_dinv_map().area_dinv_to_bounce_area_map() for D in DyckWords(6))
2630
True
2631
sage: DyckWord([1,1,0,0]).bounce_area_to_area_dinv_map()
2632
[1, 0, 1, 0]
2633
sage: DyckWord([1,0,1,0]).bounce_area_to_area_dinv_map()
2634
[1, 1, 0, 0]
2635
"""
2636
aseq = D.to_area_sequence()
2637
out = []
2638
zeros = []
2639
for i in range(len(aseq)):
2640
p = (zeros+[len(out)])[aseq[i]]
2641
out = [1] + out[p:]+[0] + out[:p]
2642
zeros = [0] + [j+len(out)-p for j in zeros[:aseq[i]]]
2643
return DyckWord( out )
2644
2645
def area(self):
2646
r"""
2647
Return the area for ``self`` corresponding to the area
2648
of the Dyck path.
2649
2650
One can view a balanced Dyck word as a lattice path from
2651
`(0,0)` to `(n,n)` in the first quadrant by letting
2652
'1's represent steps in the direction `(1,0)` and '0's
2653
represent steps in the direction `(0,1)`. The resulting
2654
path will remain weakly above the diagonal `y = x`.
2655
2656
The area statistic is the number of complete
2657
squares in the integer lattice which are below the path and above
2658
the line `y = x`. The 'half-squares' directly above the
2659
line `y = x` do not contribute to this statistic.
2660
2661
EXAMPLES::
2662
2663
sage: dw = DyckWord([1,0,1,0])
2664
sage: dw.area() # 2 half-squares, 0 complete squares
2665
0
2666
2667
::
2668
2669
sage: dw = DyckWord([1,1,1,0,1,1,1,0,0,0,1,1,0,0,1,0,0,0])
2670
sage: dw.area()
2671
19
2672
2673
::
2674
2675
sage: DyckWord([1,1,1,1,0,0,0,0]).area()
2676
6
2677
sage: DyckWord([1,1,1,0,1,0,0,0]).area()
2678
5
2679
sage: DyckWord([1,1,1,0,0,1,0,0]).area()
2680
4
2681
sage: DyckWord([1,1,1,0,0,0,1,0]).area()
2682
3
2683
sage: DyckWord([1,0,1,1,0,1,0,0]).area()
2684
2
2685
sage: DyckWord([1,1,0,1,1,0,0,0]).area()
2686
4
2687
sage: DyckWord([1,1,0,0,1,1,0,0]).area()
2688
2
2689
sage: DyckWord([1,0,1,1,1,0,0,0]).area()
2690
3
2691
sage: DyckWord([1,0,1,1,0,0,1,0]).area()
2692
1
2693
sage: DyckWord([1,0,1,0,1,1,0,0]).area()
2694
1
2695
sage: DyckWord([1,1,0,0,1,0,1,0]).area()
2696
1
2697
sage: DyckWord([1,1,0,1,0,0,1,0]).area()
2698
2
2699
sage: DyckWord([1,1,0,1,0,1,0,0]).area()
2700
3
2701
sage: DyckWord([1,0,1,0,1,0,1,0]).area()
2702
0
2703
"""
2704
above = 0
2705
diagonal = 0
2706
a = 0
2707
for move in self:
2708
if move == open_symbol:
2709
above += 1
2710
elif move == close_symbol:
2711
diagonal += 1
2712
a += above - diagonal
2713
return a
2714
2715
a_statistic = deprecated_function_alias(13550, area)
2716
2717
def bounce_path(self):
2718
r"""
2719
Return the bounce path of ``self`` formed by starting at `(n,n)` and
2720
traveling West until encountering the first vertical step of ``self``,
2721
then South until encountering the diagonal, then West again to hit
2722
the path, etc. until the `(0,0)` point is reached. The path followed
2723
by this walk is the bounce path.
2724
2725
.. SEEALSO:: :meth:`bounce`
2726
2727
EXAMPLES::
2728
2729
sage: DyckWord([1,1,0,1,0,0]).bounce_path()
2730
[1, 0, 1, 1, 0, 0]
2731
sage: DyckWord([1,1,1,0,0,0]).bounce_path()
2732
[1, 1, 1, 0, 0, 0]
2733
sage: DyckWord([1,0,1,0,1,0]).bounce_path()
2734
[1, 0, 1, 0, 1, 0]
2735
sage: DyckWord([1,1,1,1,0,0,1,0,0,0]).bounce_path()
2736
[1, 1, 0, 0, 1, 1, 1, 0, 0, 0]
2737
2738
TESTS::
2739
2740
sage: DyckWord([]).bounce_path()
2741
[]
2742
sage: DyckWord([1,0]).bounce_path()
2743
[1, 0]
2744
2745
"""
2746
area_seq = self.to_area_sequence()
2747
i = len(area_seq)-1
2748
n = 5
2749
while i > 0:
2750
n -= 1
2751
a = area_seq[i]
2752
i_new = i - a
2753
while i > i_new:
2754
i -= 1
2755
area_seq[i] = area_seq[i+1] - 1
2756
i -= 1
2757
return DyckWord(area_sequence = area_seq)
2758
2759
def bounce(self):
2760
r"""
2761
Return the bounce statistic of ``self`` due to J. Haglund,
2762
see [Hag2008]_.
2763
2764
One can view a balanced Dyck word as a lattice path from `(0,0)` to
2765
`(n,n)` in the first quadrant by letting '1's represent steps in
2766
the direction `(0,1)` and '0's represent steps in the direction
2767
`(1,0)`. The resulting path will remain weakly above the diagonal
2768
`y = x`.
2769
2770
We describe the bounce statistic of such a path in terms of what is
2771
known as the "bounce path".
2772
2773
We can think of our bounce path as describing the trail of a billiard
2774
ball shot West from `(n, n)`, which "bounces" down whenever it
2775
encounters a vertical step and "bounces" left when it encounters the
2776
line `y = x`.
2777
2778
The bouncing ball will strike the diagonal at the places
2779
2780
.. MATH::
2781
2782
(0, 0), (j_1, j_1), (j_2, j_2), \ldots, (j_r-1, j_r-1), (j_r, j_r)
2783
= (n, n).
2784
2785
We define the bounce to be the sum `\sum_{i=1}^{r-1} j_i`.
2786
2787
EXAMPLES::
2788
2789
sage: DyckWord([1,1,1,0,1,1,1,0,0,0,1,1,0,0,1,0,0,0]).bounce()
2790
7
2791
sage: DyckWord([1,1,1,1,0,0,0,0]).bounce()
2792
0
2793
sage: DyckWord([1,1,1,0,1,0,0,0]).bounce()
2794
1
2795
sage: DyckWord([1,1,1,0,0,1,0,0]).bounce()
2796
2
2797
sage: DyckWord([1,1,1,0,0,0,1,0]).bounce()
2798
3
2799
sage: DyckWord([1,0,1,1,0,1,0,0]).bounce()
2800
3
2801
sage: DyckWord([1,1,0,1,1,0,0,0]).bounce()
2802
1
2803
sage: DyckWord([1,1,0,0,1,1,0,0]).bounce()
2804
2
2805
sage: DyckWord([1,0,1,1,1,0,0,0]).bounce()
2806
1
2807
sage: DyckWord([1,0,1,1,0,0,1,0]).bounce()
2808
4
2809
sage: DyckWord([1,0,1,0,1,1,0,0]).bounce()
2810
3
2811
sage: DyckWord([1,1,0,0,1,0,1,0]).bounce()
2812
5
2813
sage: DyckWord([1,1,0,1,0,0,1,0]).bounce()
2814
4
2815
sage: DyckWord([1,1,0,1,0,1,0,0]).bounce()
2816
2
2817
sage: DyckWord([1,0,1,0,1,0,1,0]).bounce()
2818
6
2819
"""
2820
x_pos = len(self) // 2
2821
y_pos = len(self) // 2
2822
2823
b = 0
2824
2825
mode = "left"
2826
makeup_steps = 0
2827
l = self._list[:]
2828
l.reverse()
2829
2830
for move in l:
2831
#print x_pos, y_pos, mode, move
2832
if mode == "left":
2833
if move == close_symbol:
2834
x_pos -= 1
2835
elif move == open_symbol:
2836
y_pos -= 1
2837
if x_pos == y_pos:
2838
b += x_pos
2839
else:
2840
mode = "drop"
2841
elif mode == "drop":
2842
if move == close_symbol:
2843
makeup_steps += 1
2844
elif move == open_symbol:
2845
y_pos -= 1
2846
if x_pos == y_pos:
2847
b += x_pos
2848
mode = "left"
2849
x_pos -= makeup_steps
2850
makeup_steps = 0
2851
2852
return b
2853
2854
b_statistic = deprecated_function_alias(13550, bounce)
2855
2856
def dinv(self, labeling = None):
2857
r"""
2858
Return the dinv statistic of ``self`` due to M. Haiman, see [Hag2008]_.
2859
2860
If a labeling is provided then this function returns the dinv of the
2861
labeled Dyck word.
2862
2863
INPUT:
2864
2865
- ``labeling`` -- an optional argument to be viewed as the labelings
2866
of the vertical edges of the Dyck path
2867
2868
OUTPUT:
2869
2870
- an integer representing the ``dinv`` statistic of the Dyck path
2871
or the labelled Dyck path.
2872
2873
EXAMPLES::
2874
2875
sage: DyckWord([1,0,1,0,1,0,1,0,1,0]).dinv()
2876
10
2877
sage: DyckWord([1,1,1,1,1,0,0,0,0,0]).dinv()
2878
0
2879
sage: DyckWord([1,1,1,1,0,1,0,0,0,0]).dinv()
2880
1
2881
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).dinv()
2882
13
2883
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).dinv([1,2,3,4,5,6,7])
2884
11
2885
sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).dinv([6,7,5,3,4,2,1])
2886
2
2887
"""
2888
alist = self.to_area_sequence()
2889
cnt = 0
2890
for j in range(len(alist)):
2891
for i in range(j):
2892
if (alist[i]-alist[j] == 0 and (labeling is None or labeling[i]<labeling[j])) or (alist[i]-alist[j]==1 and (labeling is None or labeling[i]>labeling[j])):
2893
cnt+=1
2894
return cnt
2895
2896
@combinatorial_map(name='to alternating sign matrix')
2897
def to_alternating_sign_matrix(self):
2898
r"""
2899
Return ``self`` as an alternating sign matrix.
2900
2901
This is an inclusion map from Dyck words of length `2n` to certain
2902
`n \times n` alternating sign matrices.
2903
2904
EXAMPLES::
2905
2906
sage: DyckWord([1,1,1,0,1,0,0,0]).to_alternating_sign_matrix()
2907
[ 0 0 1 0]
2908
[ 1 0 -1 1]
2909
[ 0 1 0 0]
2910
[ 0 0 1 0]
2911
sage: DyckWord([1,0,1,0,1,1,0,0]).to_alternating_sign_matrix()
2912
[1 0 0 0]
2913
[0 1 0 0]
2914
[0 0 0 1]
2915
[0 0 1 0]
2916
"""
2917
parkfn = self.reverse().to_non_decreasing_parking_function()
2918
parkfn2 = [len(parkfn) + 1 - parkfn[i] for i in range(len(parkfn))]
2919
monotone_triangle = [[0]*(len(parkfn2) - j) for j in range(len(parkfn2))]
2920
for i in range(len(monotone_triangle)):
2921
for j in range(len(monotone_triangle[i])):
2922
monotone_triangle[i][j] = len(monotone_triangle[i]) - j
2923
monotone_triangle[i][0]=parkfn2[i]
2924
A = AlternatingSignMatrices(len(parkfn))
2925
return A.from_monotone_triangle(monotone_triangle)
2926
2927
class DyckWords(Parent, UniqueRepresentation):
2928
r"""
2929
Dyck words.
2930
2931
A Dyck word is a sequence `(w_1, \ldots, w_n)` consisting of 1 s and 0 s,
2932
with the property that for any `i` with `1 \leq i \leq n`, the sequence
2933
`(w_1, \ldots, w_i)` contains at least as many 1 s as 0 s.
2934
2935
A Dyck word is balanced if the total number of 1 s is equal to the total
2936
number of 0 s. The number of balanced Dyck words of length `2k` is given
2937
by the :func:`Catalan number<sage.combinat.combinat.catalan_number>` `C_k`.
2938
2939
EXAMPLES:
2940
2941
This class can be called with three keyword parameters ``k1``, ``k2``
2942
and ``complete``.
2943
2944
If neither ``k1`` nor ``k2`` are specified, then :class:`DyckWords`
2945
returns the combinatorial class of all balanced (=complete) Dyck words,
2946
unless the keyword ``complete`` is set to False (in which case it
2947
returns the class of all Dyck words).
2948
2949
::
2950
2951
sage: DW = DyckWords(); DW
2952
Complete Dyck words
2953
sage: [] in DW
2954
True
2955
sage: [1, 0, 1, 0] in DW
2956
True
2957
sage: [1, 1, 0] in DW
2958
False
2959
sage: ADW = DyckWords(complete=False); ADW
2960
Dyck words
2961
sage: [] in ADW
2962
True
2963
sage: [1, 0, 1, 0] in ADW
2964
True
2965
sage: [1, 1, 0] in ADW
2966
True
2967
sage: [1, 0, 0] in ADW
2968
False
2969
2970
If just ``k1`` is specified, then it returns the balanced Dyck words with
2971
``k1`` opening parentheses and ``k1`` closing parentheses.
2972
2973
::
2974
2975
sage: DW2 = DyckWords(2); DW2
2976
Dyck words with 2 opening parentheses and 2 closing parentheses
2977
sage: DW2.first()
2978
[1, 0, 1, 0]
2979
sage: DW2.last()
2980
[1, 1, 0, 0]
2981
sage: DW2.cardinality()
2982
2
2983
sage: DyckWords(100).cardinality() == catalan_number(100)
2984
True
2985
2986
If ``k2`` is specified in addition to ``k1``, then it returns the
2987
Dyck words with ``k1`` opening parentheses and ``k2`` closing parentheses.
2988
2989
::
2990
2991
sage: DW32 = DyckWords(3,2); DW32
2992
Dyck words with 3 opening parentheses and 2 closing parentheses
2993
sage: DW32.list()
2994
[[1, 0, 1, 0, 1],
2995
[1, 0, 1, 1, 0],
2996
[1, 1, 0, 0, 1],
2997
[1, 1, 0, 1, 0],
2998
[1, 1, 1, 0, 0]]
2999
"""
3000
@staticmethod
3001
def __classcall_private__(cls, k1=None, k2=None, complete=True):
3002
"""
3003
Choose the correct parent based upon input.
3004
3005
EXAMPLES::
3006
3007
sage: DW1 = DyckWords(3,3)
3008
sage: DW2 = DyckWords(3)
3009
sage: DW1 is DW2
3010
True
3011
"""
3012
if k2 is None:
3013
if k1 is None:
3014
if complete:
3015
return CompleteDyckWords_all()
3016
return DyckWords_all()
3017
return CompleteDyckWords_size(k1)
3018
3019
if k1 < 0 or (k2 is not None and k2 < 0):
3020
raise ValueError("k1 (= %s) and k2 (= %s) must be nonnegative, with k1 >= k2."%(k1, k2))
3021
if k1 < k2:
3022
raise ValueError("k1 (= %s) must be >= k2 (= %s)"%(k1, k2))
3023
3024
if k1 == k2:
3025
return CompleteDyckWords_size(k1)
3026
return DyckWords_size(k1, k2)
3027
3028
Element = DyckWord
3029
global_options = DyckWordOptions
3030
3031
def _element_constructor_(self, word):
3032
"""
3033
Construct an element of ``self``.
3034
3035
EXAMPLES::
3036
3037
sage: D = DyckWords()
3038
sage: elt = D([1, 1, 0, 1, 0, 0]); elt
3039
[1, 1, 0, 1, 0, 0]
3040
sage: elt.parent() is D
3041
True
3042
"""
3043
if isinstance(word, DyckWord) and word.parent() is self:
3044
return word
3045
return self.element_class(self, list(word))
3046
3047
def __contains__(self, x):
3048
r"""
3049
TESTS::
3050
3051
sage: D = DyckWords(complete=False)
3052
sage: [] in D
3053
True
3054
sage: [1] in D
3055
True
3056
sage: [0] in D
3057
False
3058
sage: [1, 0] in D
3059
True
3060
"""
3061
if isinstance(x, DyckWord):
3062
return True
3063
3064
if not isinstance(x, list):
3065
return False
3066
3067
return is_a(x)
3068
3069
def from_heights(self, heights):
3070
r"""
3071
Compute a Dyck word knowing its heights.
3072
3073
We view the Dyck word as a Dyck path from `(0, 0)` to
3074
`(2n, 0)` in the first quadrant by letting ``1``'s represent
3075
steps in the direction `(1, 1)` and ``0``'s represent steps in
3076
the direction `(1, -1)`.
3077
3078
The :meth:`heights` is the sequence of the `y`-coordinates of
3079
the `2n+1` lattice points along this path.
3080
3081
EXAMPLES::
3082
3083
sage: from sage.combinat.dyck_word import DyckWord
3084
sage: D = DyckWords(complete=False)
3085
sage: D.from_heights((0,))
3086
[]
3087
sage: D.from_heights((0, 1, 0))
3088
[1, 0]
3089
sage: D.from_heights((0, 1, 2, 1, 0))
3090
[1, 1, 0, 0]
3091
3092
This also works for incomplete Dyck words::
3093
3094
sage: D.from_heights((0, 1, 2, 1, 2, 1))
3095
[1, 1, 0, 1, 0]
3096
sage: D.from_heights((0, 1, 2, 1))
3097
[1, 1, 0]
3098
3099
.. SEEALSO:: :meth:`heights`, :meth:`min_from_heights`
3100
3101
TESTS::
3102
3103
sage: all(dw == D.from_heights(dw.heights())
3104
....: for i in range(7) for dw in DyckWords(i))
3105
True
3106
3107
sage: D.from_heights((1, 2, 1))
3108
Traceback (most recent call last):
3109
...
3110
ValueError: heights must start with 0: (1, 2, 1)
3111
sage: D.from_heights((0, 1, 4, 1))
3112
Traceback (most recent call last):
3113
...
3114
ValueError: consecutive heights must differ by exactly 1: (0, 1, 4, 1)
3115
sage: D.from_heights(())
3116
Traceback (most recent call last):
3117
...
3118
ValueError: heights must start with 0: ()
3119
"""
3120
l1 = len(heights)-1
3121
res = [0]*(l1)
3122
if heights == () or heights[0] != 0:
3123
raise ValueError("heights must start with 0: %s"%(heights,))
3124
for i in range(l1):
3125
if heights[i] == heights[i+1]-1:
3126
res[i] = 1
3127
elif heights[i] != heights[i+1]+1:
3128
raise ValueError("consecutive heights must differ by exactly 1: %s"%(heights,))
3129
return self.element_class(self, res)
3130
3131
def min_from_heights(self, heights):
3132
r"""
3133
Compute the smallest Dyck word which achieves or surpasses
3134
a given sequence of heights.
3135
3136
INPUT:
3137
3138
- ``heights`` -- a nonempty list or iterable consisting of
3139
nonnegative integers, the first of which is `0`
3140
3141
OUTPUT:
3142
3143
- The smallest Dyck word whose sequence of heights is
3144
componentwise higher-or-equal to ``heights``. Here,
3145
"smaller" can be understood both in the sense of
3146
lexicographic order on the Dyck words, and in the sense
3147
of every vertex of the path having the smallest possible
3148
height.
3149
3150
.. SEEALSO::
3151
3152
- :meth:`heights`
3153
- :meth:`from_heights`
3154
3155
EXAMPLES::
3156
3157
sage: D = DyckWords(complete=False)
3158
sage: D.min_from_heights((0,))
3159
[]
3160
sage: D.min_from_heights((0, 1, 0))
3161
[1, 0]
3162
sage: D.min_from_heights((0, 0, 2, 0, 0))
3163
[1, 1, 0, 0]
3164
sage: D.min_from_heights((0, 0, 2, 0, 2, 0))
3165
[1, 1, 0, 1, 0]
3166
sage: D.min_from_heights((0, 0, 1, 0, 1, 0))
3167
[1, 1, 0, 1, 0]
3168
3169
TESTS::
3170
3171
sage: D.min_from_heights(())
3172
Traceback (most recent call last):
3173
...
3174
ValueError: heights must start with 0: ()
3175
"""
3176
if heights == () or heights[0] != 0:
3177
raise ValueError("heights must start with 0: %s"%(heights,))
3178
# round heights to the smallest even-odd integer
3179
heights = list(heights)
3180
for i in range(0, len(heights), 2):
3181
if heights[i] % 2 == 1:
3182
heights[i]+=1
3183
for i in range(1, len(heights), 2):
3184
if heights[i] % 2 == 0:
3185
heights[i]+=1
3186
3187
# smooth heights
3188
for i in range(len(heights)-1):
3189
if heights[i+1] < heights[i]:
3190
heights[i+1] = heights[i]-1
3191
for i in range(len(heights)-1, 0, -1):
3192
if heights[i] > heights[i-1]:
3193
heights[i-1] = heights[i]-1
3194
return self.from_heights(heights)
3195
3196
class DyckWords_all(DyckWords):
3197
"""
3198
All Dyck words.
3199
"""
3200
def __init__(self):
3201
"""
3202
Intialize ``self``.
3203
3204
EXAMPLES::
3205
3206
sage: TestSuite(DyckWords(complete=False)).run()
3207
"""
3208
DyckWords.__init__(self, category=InfiniteEnumeratedSets())
3209
3210
def _repr_(self):
3211
r"""
3212
TESTS::
3213
3214
sage: DyckWords(complete=False)
3215
Dyck words
3216
"""
3217
return "Dyck words"
3218
3219
def _an_element_(self):
3220
r"""
3221
TESTS::
3222
3223
sage: DyckWords(complete=False).an_element()
3224
[1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
3225
"""
3226
return DyckWords(5).an_element()
3227
3228
def __iter__(self):
3229
"""
3230
Iterate over ``self``.
3231
3232
EXAMPLES::
3233
3234
sage: it = DyckWords(complete=False).__iter__()
3235
sage: [it.next() for x in range(10)]
3236
[[],
3237
[1],
3238
[1, 0],
3239
[1, 1],
3240
[1, 0, 0],
3241
[1, 0, 1],
3242
[1, 1, 0],
3243
[1, 1, 1],
3244
[1, 0, 1, 0],
3245
[1, 1, 0, 0]]
3246
"""
3247
n = 0
3248
yield self.element_class(self, [])
3249
while True:
3250
for k in range(1, n+1):
3251
for x in DyckWords_size(k, n-k):
3252
yield self.element_class(self, list(x))
3253
n += 1
3254
3255
class DyckWordBacktracker(GenericBacktracker):
3256
r"""
3257
This class is an iterator for all Dyck words
3258
with `n` opening parentheses and `n - k` closing parentheses using
3259
the backtracker class. It is used by the :class:`DyckWords_size` class.
3260
3261
This is not really meant to be called directly, partially because
3262
it fails in a couple corner cases: ``DWB(0)`` yields ``[0]``, not the
3263
empty word, and ``DWB(k, k+1)`` yields something (it shouldn't yield
3264
anything). This could be fixed with a sanity check in ``_rec()``, but
3265
then we'd be doing the sanity check *every time* we generate new
3266
objects; instead, we do *one* sanity check in :class:`DyckWords` and
3267
assume here that the sanity check has already been made.
3268
3269
AUTHOR:
3270
3271
- Dan Drake (2008-05-30)
3272
"""
3273
def __init__(self, k1, k2):
3274
r"""
3275
TESTS::
3276
3277
sage: from sage.combinat.dyck_word import DyckWordBacktracker
3278
sage: len(list(DyckWordBacktracker(5, 5)))
3279
42
3280
sage: len(list(DyckWordBacktracker(6,4)))
3281
90
3282
sage: len(list(DyckWordBacktracker(7,0)))
3283
1
3284
"""
3285
GenericBacktracker.__init__(self, [], (0, 0))
3286
# note that the comments in this class think of our objects as
3287
# Dyck paths, not words; having k1 opening parens and k2 closing
3288
# parens corresponds to paths of length k1 + k2 ending at height
3289
# k1 - k2.
3290
self.n = k1 + k2
3291
self.endht = k1 - k2
3292
3293
def _rec(self, path, state):
3294
r"""
3295
TESTS::
3296
3297
sage: from sage.combinat.dyck_word import DyckWordBacktracker
3298
sage: dwb = DyckWordBacktracker(3, 3)
3299
sage: list(dwb._rec([1,1,0],(3, 2)))
3300
[([1, 1, 0, 0], (4, 1), False), ([1, 1, 0, 1], (4, 3), False)]
3301
sage: list(dwb._rec([1,1,0,0],(4, 0)))
3302
[([1, 1, 0, 0, 1], (5, 1), False)]
3303
sage: list(DyckWordBacktracker(4, 4)._rec([1,1,1,1],(4, 4)))
3304
[([1, 1, 1, 1, 0], (5, 3), False)]
3305
"""
3306
len, ht = state
3307
3308
if len < self.n - 1:
3309
# if length is less than n-1, new path won't have length n, so
3310
# don't yield it, and keep building paths
3311
3312
# if the path isn't too low and is not touching the x-axis, we can
3313
# yield a path with a downstep at the end
3314
if ht > (self.endht - (self.n - len)) and ht > 0:
3315
yield path + [0], (len + 1, ht - 1), False
3316
3317
# if the path isn't too high, it can also take an upstep
3318
if ht < (self.endht + (self.n - len)):
3319
yield path + [1], (len + 1, ht + 1), False
3320
else:
3321
# length is n - 1, so add a single step (up or down,
3322
# according to current height and endht), don't try to
3323
# construct more paths, and yield the path
3324
if ht < self.endht:
3325
yield path + [1], None, True
3326
else:
3327
yield path + [0], None, True
3328
3329
class DyckWords_size(DyckWords):
3330
"""
3331
Dyck words with `k_1` openers and `k_2` closers.
3332
"""
3333
def __init__(self, k1, k2):
3334
r"""
3335
TESTS::
3336
3337
sage: TestSuite(DyckWords(4,2)).run()
3338
"""
3339
self.k1 = k1
3340
self.k2 = k2
3341
DyckWords.__init__(self, category=FiniteEnumeratedSets())
3342
3343
def _repr_(self):
3344
r"""
3345
TESTS::
3346
3347
sage: DyckWords(4)
3348
Dyck words with 4 opening parentheses and 4 closing parentheses
3349
"""
3350
return "Dyck words with %s opening parentheses and %s closing parentheses"%(self.k1, self.k2)
3351
3352
def __contains__(self, x):
3353
r"""
3354
EXAMPLES::
3355
3356
sage: [1, 0, 0, 1] in DyckWords(2,2)
3357
False
3358
sage: [1, 0, 1, 0] in DyckWords(2,2)
3359
True
3360
sage: [1, 0, 1, 0, 1] in DyckWords(3,2)
3361
True
3362
sage: [1, 0, 1, 1, 0] in DyckWords(3,2)
3363
True
3364
sage: [1, 0, 1, 1] in DyckWords(3,1)
3365
True
3366
"""
3367
return is_a(x, self.k1, self.k2)
3368
3369
def __iter__(self):
3370
r"""
3371
Return an iterator for Dyck words with ``k1`` opening and ``k2``
3372
closing parentheses.
3373
3374
EXAMPLES::
3375
3376
sage: list(DyckWords(0))
3377
[[]]
3378
sage: list(DyckWords(1))
3379
[[1, 0]]
3380
sage: list(DyckWords(2))
3381
[[1, 0, 1, 0], [1, 1, 0, 0]]
3382
sage: len(DyckWords(5))
3383
42
3384
sage: list(DyckWords(3,2))
3385
[[1, 0, 1, 0, 1],
3386
[1, 0, 1, 1, 0],
3387
[1, 1, 0, 0, 1],
3388
[1, 1, 0, 1, 0],
3389
[1, 1, 1, 0, 0]]
3390
"""
3391
if self.k1 == 0:
3392
yield self.element_class(self, [])
3393
elif self.k2 == 0:
3394
yield self.element_class(self, [open_symbol]*self.k1)
3395
else:
3396
for w in DyckWordBacktracker(self.k1, self.k2):
3397
yield self.element_class(self, w)
3398
3399
def cardinality(self):
3400
r"""
3401
Return the number of Dyck words with `k_1` openers and `k_2` closers.
3402
3403
This number is
3404
3405
.. MATH::
3406
3407
\frac{k_1 - k_2 + 1}{k_1 + 1} \binom{k_1 + k_2}{k_2}
3408
= \binom{k_1 + k_2}{k_2} - \binom{k_1 + k_2}{k_2 - 1}
3409
3410
if `k_2 \leq k_1 + 1`, and `0` if `k_2 > k_1` (these numbers are the
3411
same if `k_2 = k_1 + 1`).
3412
3413
EXAMPLES::
3414
3415
sage: DyckWords(3, 2).cardinality()
3416
5
3417
sage: all( all( DyckWords(p, q).cardinality()
3418
....: == len(DyckWords(p, q).list()) for q in range(p + 1) )
3419
....: for p in range(7) )
3420
True
3421
"""
3422
from sage.rings.arith import binomial
3423
return (self.k1 - self.k2 + 1) * binomial(self.k1 + self.k2, self.k2) // (self.k1 + 1)
3424
3425
################################################################
3426
## Complete Dyck words
3427
3428
class CompleteDyckWords(DyckWords):
3429
"""
3430
Abstract base class for all complete Dyck words.
3431
"""
3432
Element = DyckWord_complete
3433
3434
def __contains__(self, x):
3435
r"""
3436
TESTS::
3437
3438
sage: [] in DyckWords()
3439
True
3440
sage: [1] in DyckWords()
3441
False
3442
sage: [0] in DyckWords()
3443
False
3444
"""
3445
if isinstance(x, DyckWord_complete):
3446
return True
3447
3448
if not isinstance(x, list):
3449
return False
3450
3451
if len(x) % 2 != 0:
3452
return False
3453
3454
return is_a(x, len(x) // 2)
3455
3456
def from_Catalan_code(self, code):
3457
r"""
3458
Return the Dyck word associated to the given Catalan code
3459
``code``.
3460
3461
A Catalan code is a sequence of non--negative integers `a_i` such
3462
that if `i < j` and `a_i > 0` and `a_j > 0` and `a_{i+1} = a_{i+2}
3463
= \cdots = a_{j-1} = 0` then `a_i - a_j < j-i`.
3464
3465
The Catalan code of a Dyck word is example (x) in Richard Stanley's
3466
exercises on combinatorial interpretations for Catalan objects.
3467
The code in this example is the reverse of the description provided
3468
there. See [Sta1999]_ and [Sta]_.
3469
3470
EXAMPLES::
3471
3472
sage: DyckWords().from_Catalan_code([])
3473
[]
3474
sage: DyckWords().from_Catalan_code([0])
3475
[1, 0]
3476
sage: DyckWords().from_Catalan_code([0, 1])
3477
[1, 1, 0, 0]
3478
sage: DyckWords().from_Catalan_code([0, 0])
3479
[1, 0, 1, 0]
3480
"""
3481
code = list(code)
3482
if not code:
3483
return self.element_class(self, [])
3484
res = self.from_Catalan_code(code[:-1])
3485
cuts = [0] + res.returns_to_zero()
3486
lst = [1] + res[:cuts[code[-1]]] + [0] + res[cuts[code[-1]]:]
3487
return self.element_class(self, lst)
3488
3489
def from_area_sequence(self, code):
3490
r"""
3491
Return the Dyck word associated to the given area sequence
3492
``code``.
3493
3494
See :meth:`to_area_sequence` for a definition of the area
3495
sequence of a Dyck word.
3496
3497
.. SEEALSO:: :meth:`area`, :meth:`to_area_sequence`.
3498
3499
INPUT:
3500
3501
- ``code`` -- a list of integers satisfying ``code[0] == 0``
3502
and ``0 <= code[i+1] <= code[i]+1``.
3503
3504
EXAMPLES::
3505
3506
sage: DyckWords().from_area_sequence([])
3507
[]
3508
sage: DyckWords().from_area_sequence([0])
3509
[1, 0]
3510
sage: DyckWords().from_area_sequence([0, 1])
3511
[1, 1, 0, 0]
3512
sage: DyckWords().from_area_sequence([0, 0])
3513
[1, 0, 1, 0]
3514
"""
3515
if not is_area_sequence(code):
3516
raise ValueError("The given sequence is not a sequence giving the number of cells between the Dyck path and the diagonal.")
3517
dyck_word = []
3518
for i in xrange(len(code)):
3519
if i > 0:
3520
dyck_word.extend([close_symbol]*(code[i-1]-code[i]+1))
3521
dyck_word.append(open_symbol)
3522
dyck_word.extend([close_symbol]*(2*len(code)-len(dyck_word)))
3523
return self.element_class(self, dyck_word)
3524
3525
def from_noncrossing_partition(self, ncp):
3526
r"""
3527
Convert a noncrossing partition ``ncp`` to a Dyck word.
3528
3529
TESTS::
3530
3531
sage: DyckWord(noncrossing_partition=[[1,2]]) # indirect doctest
3532
[1, 1, 0, 0]
3533
sage: DyckWord(noncrossing_partition=[[1],[2]])
3534
[1, 0, 1, 0]
3535
3536
::
3537
3538
sage: dws = DyckWords(5).list()
3539
sage: ncps = map( lambda x: x.to_noncrossing_partition(), dws)
3540
sage: dws2 = map( lambda x: DyckWord(noncrossing_partition=x), ncps)
3541
sage: dws == dws2
3542
True
3543
"""
3544
l = [ 0 ] * int( sum( [ len(v) for v in ncp ] ) )
3545
for v in ncp:
3546
l[v[-1]-1] = len(v)
3547
3548
res = []
3549
for i in l:
3550
res += [ open_symbol ] + [close_symbol]*int(i)
3551
return self.element_class(self, res)
3552
3553
def from_non_decreasing_parking_function(self, pf):
3554
r"""
3555
Bijection from :class:`non-decreasing parking
3556
functions<sage.combinat.non_decreasing_parking_function.NonDecreasingParkingFunctions>`.
3557
See there the method
3558
:meth:`~sage.combinat.non_decreasing_parking_function.NonDecreasingParkingFunction.to_dyck_word`
3559
for more information.
3560
3561
EXAMPLES::
3562
3563
sage: D = DyckWords()
3564
sage: D.from_non_decreasing_parking_function([])
3565
[]
3566
sage: D.from_non_decreasing_parking_function([1])
3567
[1, 0]
3568
sage: D.from_non_decreasing_parking_function([1,1])
3569
[1, 1, 0, 0]
3570
sage: D.from_non_decreasing_parking_function([1,2])
3571
[1, 0, 1, 0]
3572
sage: D.from_non_decreasing_parking_function([1,1,1])
3573
[1, 1, 1, 0, 0, 0]
3574
sage: D.from_non_decreasing_parking_function([1,2,3])
3575
[1, 0, 1, 0, 1, 0]
3576
sage: D.from_non_decreasing_parking_function([1,1,3,3,4,6,6])
3577
[1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0]
3578
3579
TESTS::
3580
3581
sage: D.from_non_decreasing_parking_function(NonDecreasingParkingFunction([]))
3582
[]
3583
sage: D.from_non_decreasing_parking_function(NonDecreasingParkingFunction([1,1,3,3,4,6,6]))
3584
[1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0]
3585
"""
3586
return self.from_area_sequence([ i - pf[i] + 1 for i in range(len(pf)) ])
3587
3588
class CompleteDyckWords_all(CompleteDyckWords, DyckWords_all):
3589
"""
3590
All complete Dyck words.
3591
"""
3592
def _repr_(self):
3593
r"""
3594
TESTS::
3595
3596
sage: DyckWords()
3597
Complete Dyck words
3598
"""
3599
return "Complete Dyck words"
3600
3601
def __iter__(self):
3602
"""
3603
Iterate over ``self``.
3604
3605
EXAMPLES::
3606
3607
sage: it = DyckWords().__iter__()
3608
sage: [it.next() for x in range(10)]
3609
[[],
3610
[1, 0],
3611
[1, 0, 1, 0],
3612
[1, 1, 0, 0],
3613
[1, 0, 1, 0, 1, 0],
3614
[1, 0, 1, 1, 0, 0],
3615
[1, 1, 0, 0, 1, 0],
3616
[1, 1, 0, 1, 0, 0],
3617
[1, 1, 1, 0, 0, 0],
3618
[1, 0, 1, 0, 1, 0, 1, 0]]
3619
"""
3620
n = 0
3621
while True:
3622
for x in CompleteDyckWords_size(n):
3623
yield self.element_class(self, list(x))
3624
n += 1
3625
3626
class height_poset(UniqueRepresentation, Parent):
3627
r"""
3628
The poset of complete Dyck words compared componentwise by
3629
``heights``.
3630
This is, ``D`` is smaller than or equal to ``D'`` if it is
3631
weakly below ``D'``.
3632
"""
3633
def __init__(self):
3634
r"""
3635
TESTS::
3636
3637
sage: poset = DyckWords().height_poset()
3638
sage: TestSuite(poset).run()
3639
"""
3640
Parent.__init__(self, facade=DyckWords_all(), category=Posets())
3641
3642
def _an_element_(self):
3643
r"""
3644
TESTS::
3645
3646
sage: DyckWords().height_poset().an_element() # indirect doctest
3647
[1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
3648
"""
3649
return DyckWords_all().an_element()
3650
3651
def __call__(self, obj):
3652
r"""
3653
TESTS::
3654
3655
sage: poset = DyckWords().height_poset()
3656
sage: poset([1,0,1,0])
3657
[1, 0, 1, 0]
3658
"""
3659
return DyckWords_all()(obj)
3660
3661
def le(self, dw1, dw2):
3662
r"""
3663
Compare two Dyck words of equal size, and return ``True`` if
3664
all of the heights of ``dw1`` are less than or equal to the
3665
respective heights of ``dw2`` .
3666
3667
.. SEEALSO::
3668
3669
:meth:`heights<sage.combinat.dyck_word.DyckWord.heights>`
3670
3671
EXAMPLES::
3672
3673
sage: poset = DyckWords().height_poset()
3674
sage: poset.le(DyckWord([]), DyckWord([]))
3675
True
3676
sage: poset.le(DyckWord([1,0]), DyckWord([1,0]))
3677
True
3678
sage: poset.le(DyckWord([1,0,1,0]), DyckWord([1,1,0,0]))
3679
True
3680
sage: poset.le(DyckWord([1,1,0,0]), DyckWord([1,0,1,0]))
3681
False
3682
sage: [poset.le(dw1, dw2)
3683
....: for dw1 in DyckWords(3) for dw2 in DyckWords(3)]
3684
[True, True, True, True, True, False, True, False, True, True, False, False, True, True, True, False, False, False, True, True, False, False, False, False, True]
3685
"""
3686
if len(dw1) != len(dw2):
3687
raise ValueError("Length mismatch: %s and %s"%(dw1, dw2))
3688
sh = dw1.heights()
3689
oh = dw2.heights()
3690
return all(sh[i] <= oh[i] for i in range(len(dw1)))
3691
3692
class CompleteDyckWords_size(CompleteDyckWords, DyckWords_size):
3693
"""
3694
All complete Dyck words of a given size.
3695
"""
3696
def __init__(self, k):
3697
"""
3698
Initialize ``self``.
3699
3700
TESTS::
3701
3702
sage: TestSuite(DyckWords(4)).run()
3703
"""
3704
CompleteDyckWords.__init__(self, category=FiniteEnumeratedSets())
3705
DyckWords_size.__init__(self, k, k)
3706
3707
def __contains__(self, x):
3708
r"""
3709
TESTS::
3710
3711
sage: [1, 0] in DyckWords(1)
3712
True
3713
sage: [1, 0] in DyckWords(2)
3714
False
3715
sage: [1, 1, 0, 0] in DyckWords(2)
3716
True
3717
sage: [1, 0, 0, 1] in DyckWords(2)
3718
False
3719
"""
3720
return CompleteDyckWords.__contains__(self, x) and len(x) // 2 == self.k1
3721
3722
def cardinality(self):
3723
r"""
3724
Return the number of complete Dyck words of semilength `n`, i.e. the
3725
`n`-th :func:`Catalan number<sage.combinat.combinat.catalan_number>`.
3726
3727
EXAMPLES::
3728
3729
sage: DyckWords(4).cardinality()
3730
14
3731
sage: ns = range(9)
3732
sage: dws = [DyckWords(n) for n in ns]
3733
sage: all([ dw.cardinality() == len(dw.list()) for dw in dws])
3734
True
3735
"""
3736
return catalan_number(self.k1)
3737
3738
def _iter_by_recursion(self):
3739
"""
3740
Iterate over ``self`` by recursively using the position of
3741
the first return to 0.
3742
3743
EXAMPLES::
3744
3745
sage: DW = DyckWords(4)
3746
sage: L = [d for d in DW._iter_by_recursion()]; L
3747
[[1, 0, 1, 0, 1, 0, 1, 0],
3748
[1, 0, 1, 0, 1, 1, 0, 0],
3749
[1, 0, 1, 1, 0, 0, 1, 0],
3750
[1, 0, 1, 1, 0, 1, 0, 0],
3751
[1, 0, 1, 1, 1, 0, 0, 0],
3752
[1, 1, 0, 0, 1, 0, 1, 0],
3753
[1, 1, 0, 0, 1, 1, 0, 0],
3754
[1, 1, 0, 1, 0, 0, 1, 0],
3755
[1, 1, 1, 0, 0, 0, 1, 0],
3756
[1, 1, 0, 1, 0, 1, 0, 0],
3757
[1, 1, 0, 1, 1, 0, 0, 0],
3758
[1, 1, 1, 0, 0, 1, 0, 0],
3759
[1, 1, 1, 0, 1, 0, 0, 0],
3760
[1, 1, 1, 1, 0, 0, 0, 0]]
3761
sage: len(L) == DW.cardinality()
3762
True
3763
"""
3764
# Do a couple of small cases first
3765
if self.k1 <= 2:
3766
if self.k1 == 0:
3767
yield self.element_class(self, [])
3768
elif self.k1 == 1:
3769
yield self.element_class(self, [1, 0])
3770
elif self.k1 == 2:
3771
yield self.element_class(self, [1, 0, 1, 0])
3772
yield self.element_class(self, [1, 1, 0, 0])
3773
return
3774
3775
# Create all necessary parents
3776
P = [CompleteDyckWords_size(i) for i in range(self.k1)]
3777
3778
for i in range(self.k1):
3779
for p in P[i]._iter_by_recursion():
3780
list_1p0 = [1] + list(p) + [0]
3781
for s in P[-i-1]._iter_by_recursion():
3782
yield self.element_class(self, list_1p0 + list(s))
3783
3784
def is_area_sequence(seq):
3785
r"""
3786
Test if a sequence `l` of integers satisfies `l_0 = 0` and
3787
`0 \leq l_{i+1} \leq l_i + 1` for `i > 0`.
3788
3789
EXAMPLES::
3790
3791
sage: from sage.combinat.dyck_word import is_area_sequence
3792
sage: is_area_sequence([0,2,0])
3793
False
3794
sage: is_area_sequence([1,2,3])
3795
False
3796
sage: is_area_sequence([0,1,0])
3797
True
3798
sage: is_area_sequence([0,1,2])
3799
True
3800
sage: is_area_sequence([])
3801
True
3802
"""
3803
if seq == []:
3804
return True
3805
return seq[0] == 0 and all( 0 <= seq[i+1] and seq[i+1] <= seq[i]+1 for i in xrange(len(seq)-1) )
3806
3807
def is_a(obj, k1 = None, k2 = None):
3808
r"""
3809
Test if ``obj`` is a Dyck word with exactly ``k1`` open symbols and
3810
exactly ``k2`` close symbols.
3811
3812
If ``k1`` is not specified, then the number of open symbols can be
3813
arbitrary. If ``k1`` is specified but ``k2`` is not, then ``k2`` is
3814
set to be ``k1``.
3815
3816
EXAMPLES::
3817
3818
sage: from sage.combinat.dyck_word import is_a
3819
sage: is_a([1,1,0,0])
3820
True
3821
sage: is_a([1,0,1,0])
3822
True
3823
sage: is_a([1,1,0,0], 2)
3824
True
3825
sage: is_a([1,1,0,0], 3)
3826
False
3827
sage: is_a([1,1,0,0], 3, 2)
3828
False
3829
sage: is_a([1,1,0])
3830
True
3831
sage: is_a([0,1,0])
3832
False
3833
sage: is_a([1,0,0])
3834
False
3835
sage: is_a([1,1,0],2,1)
3836
True
3837
sage: is_a([1,1,0],2)
3838
False
3839
sage: is_a([1,1,0],1,1)
3840
False
3841
"""
3842
if k1 is not None:
3843
if k2 is None:
3844
k2 = k1
3845
elif k1 < k2:
3846
raise ValueError("k1 (= %s) must be >= k2 (= %s)"%(k1, k2))
3847
3848
n_opens = 0
3849
n_closes = 0
3850
3851
for p in obj:
3852
if p == open_symbol:
3853
n_opens += 1
3854
elif p == close_symbol:
3855
if n_opens == n_closes:
3856
return False
3857
n_closes += 1
3858
else:
3859
return False
3860
3861
return (k1 is None and k2 is None) or (n_opens == k1 and n_closes == k2)
3862
3863
is_a_prefix = deprecated_function_alias(14875, is_a)
3864
3865
def from_noncrossing_partition(ncp):
3866
r"""
3867
This is deprecated in :trac:`14875`. Instead use
3868
:meth:`CompleteDyckWords.from_noncrossing_partition()`.
3869
3870
TESTS::
3871
3872
sage: sage.combinat.dyck_word.from_noncrossing_partition([[1,2]])
3873
doctest:...: DeprecationWarning: this method is deprecated. Use DyckWords().from_noncrossing_partition instead.
3874
See http://trac.sagemath.org/14875 for details.
3875
[1, 1, 0, 0]
3876
"""
3877
from sage.misc.superseded import deprecation
3878
deprecation(14875,'this method is deprecated. Use DyckWords().from_noncrossing_partition instead.')
3879
return CompleteDyckWords_all().from_noncrossing_partition(ncp)
3880
3881
def from_ordered_tree(tree):
3882
r"""
3883
TESTS::
3884
3885
sage: sage.combinat.dyck_word.from_ordered_tree(1)
3886
Traceback (most recent call last):
3887
...
3888
NotImplementedError: TODO
3889
"""
3890
raise NotImplementedError("TODO")
3891
3892
def pealing(D,return_touches=False):
3893
r"""
3894
A helper function for computing the bijection from a Dyck word to a
3895
`231`-avoiding permutation using the bijection "Stump". For details
3896
see [Stu2008]_.
3897
3898
.. SEEALSO::
3899
3900
:meth:`~sage.combinat.dyck_word.DyckWord_complete.to_noncrossing_partition`
3901
3902
EXAMPLES::
3903
3904
sage: from sage.combinat.dyck_word import pealing
3905
sage: pealing(DyckWord([1,1,0,0]))
3906
[1, 0, 1, 0]
3907
sage: pealing(DyckWord([1,0,1,0]))
3908
[1, 0, 1, 0]
3909
sage: pealing(DyckWord([1, 1, 0, 0, 1, 1, 1, 0, 0, 0]))
3910
[1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
3911
sage: pealing(DyckWord([1,1,0,0]),return_touches=True)
3912
([1, 0, 1, 0], [[1, 2]])
3913
sage: pealing(DyckWord([1,0,1,0]),return_touches=True)
3914
([1, 0, 1, 0], [])
3915
sage: pealing(DyckWord([1, 1, 0, 0, 1, 1, 1, 0, 0, 0]),return_touches=True)
3916
([1, 0, 1, 0, 1, 0, 1, 0, 1, 0], [[1, 2], [3, 5]])
3917
"""
3918
n = D.semilength()
3919
area = D.to_area_sequence()
3920
new_area = []
3921
touch_sequences = []
3922
touches = []
3923
for i in range(n-1):
3924
if area[i+1] == 0:
3925
touches.append(i+1)
3926
if len(touches) > 1:
3927
touch_sequences.append(touches)
3928
touches = []
3929
elif area[i] == 0:
3930
touches = []
3931
new_area.append(0)
3932
elif area[i+1] == 1:
3933
new_area.append(0)
3934
touches.append(i+1)
3935
else:
3936
new_area.append(area[i+1]-2)
3937
new_area.append(0)
3938
if area[n-1] != 0:
3939
touches.append(n)
3940
if len(touches) > 1:
3941
touch_sequences.append(touches)
3942
D = DyckWords().from_area_sequence(new_area)
3943
if return_touches:
3944
return (D, touch_sequences)
3945
else:
3946
return D
3947
3948
from sage.structure.sage_object import register_unpickle_override
3949
register_unpickle_override('sage.combinat.dyck_word', 'DyckWord', DyckWord)
3950
3951
3952