Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/graphs/pq_trees.py
4057 views
1
r"""
2
PQ-Trees
3
4
This module implements PQ-Trees and methods to help recognise Interval
5
Graphs. It is used by :meth:`is_interval
6
<sage.graphs.generic_graph.GenericGraph.is_interval>`.
7
8
Author:
9
10
- Nathann Cohen
11
12
"""
13
14
################################################################################
15
# Copyright (C) 2012 Nathann Cohen <[email protected]> #
16
# #
17
# Distributed under the terms of the GNU General Public License (GPL) #
18
# http://www.gnu.org/licenses/ #
19
################################################################################
20
21
# Constants, to make the code more readable
22
23
FULL = 2
24
PARTIAL = 1
25
EMPTY = 0
26
ALIGNED = True
27
UNALIGNED = False
28
29
##########################################################################
30
# Some Lambda Functions #
31
# #
32
# As the elements of a PQ-Tree can be either P-Trees, Q-Trees, or the #
33
# sets themselves (the leaves), the following lambda function are #
34
# meant to be applied both on PQ-Trees and Sets, and mimic for the #
35
# latter the behaviour we expect from the corresponding methods #
36
# defined in class PQ #
37
##########################################################################
38
39
set_contiguous = lambda tree, x : (
40
tree.set_contiguous(x) if isinstance(tree, PQ) else
41
((FULL, ALIGNED) if x in tree
42
else (EMPTY, ALIGNED)))
43
44
new_P = lambda liste : P(liste) if len(liste) > 1 else liste[0]
45
new_Q = lambda liste : Q(liste) if len(liste) > 1 else liste[0]
46
47
flatten = lambda x : x.flatten() if isinstance(x, PQ) else x
48
49
impossible_msg = "Impossible"
50
51
def reorder_sets(sets):
52
r"""
53
Reorders a collection of sets such that each element appears on an
54
interval.
55
56
Given a collection of sets `C = S_1,...,S_k` on a ground set `X`,
57
this function attempts to reorder them in such a way that `\forall
58
x \in X` and `i<j` with `x\in S_i, S_j`, then `x\in S_l` for every
59
`i<l<j` if it exists.
60
61
INPUT:
62
63
- ``sets`` - a list of instances of ``list, Set`` or ``set``
64
65
ALGORITHM:
66
67
PQ-Trees
68
69
EXAMPLE:
70
71
There is only one way (up to reversal) to represent contiguously
72
the sequence ofsets `\{i-1, i, i+1\}`::
73
74
sage: from sage.graphs.pq_trees import reorder_sets
75
sage: seq = [Set([i-1,i,i+1]) for i in range(1,15)]
76
77
We apply a random permutation::
78
79
sage: p = Permutations(len(seq)).random_element()
80
sage: seq = [ seq[p(i+1)-1] for i in range(len(seq)) ]
81
sage: ordered = reorder_sets(seq)
82
sage: if not 0 in ordered[0]:
83
... ordered = ordered.reverse()
84
sage: print ordered
85
[{0, 1, 2}, {1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}, {5, 6, 7}, {8, 6, 7}, {8, 9, 7}, {8, 9, 10}, {9, 10, 11}, {10, 11, 12}, {11, 12, 13}, {12, 13, 14}, {13, 14, 15}]
86
"""
87
88
if len(sets) == 1:
89
return sets
90
91
s = set([])
92
93
for ss in sets:
94
for i in ss:
95
s.add(i)
96
97
tree = P(sets)
98
99
100
for i in s:
101
tree.set_contiguous(i)
102
tree = flatten(tree)
103
104
return tree.ordering()
105
106
class PQ:
107
r"""
108
This class implements the PQ-Tree, used for the recognition of
109
Interval Graphs, or equivalently for matrices having the so-caled
110
"consecutive ones property".
111
112
Briefly, we are given a collection `C=S_1, ..., S_n` of sets on a
113
common ground set `X`, and we would like to reorder the elements
114
of `C` in such a way that for every element `x\in X` such that
115
`x\in S_i` and `x\in S_j`, `i<j`, we have `x\in S_l` for all
116
`i<l<j`. This property could also be rephrased as : the sets
117
containing `x` are an interval.
118
119
To achieve it, we will actually compute ALL the orderings
120
satisfying such constraints using the structure of PQ-Tree, by
121
adding the constraints one at a time.
122
123
* At first, there is no constraint : all the permutations are
124
allowed. We will then build a tree composed of one node
125
linked to all the sets in our collection (his children). As
126
we want to remember that all the permutations of his
127
children are allowed, we will label it with "P", making it a
128
P-Tree.
129
130
* We are now picking an element `x \in X`, and we want to
131
ensure that all the elements `C_x` containing it are
132
contiguous. We can remove them from their tree `T_1`, create
133
a second tree `T_2` whose only children are the `C_x`, and
134
attach this `T_2` to `T_1`. We also make this new tree a
135
`P-Tree`, as all the elements of `C_x` can be permuted as
136
long as they stay close to each other. Obviously, the whole
137
tree `T_2` can be prmuter with the other children of `T_1`
138
in any way -- it does not impair the fact that the sequence
139
of the children will ensure the sets containing `x` are
140
contiguous.
141
142
* We would like to repeat the same procedure for `x' \in X`,
143
but we are now encountering a problem : there may be sets
144
containing both `x'` and `x`, along with others containing
145
only `x` or only `x'`. We can permute the sets containing
146
only `x'` together, or the sets containing both `x` and `x'`
147
together, but we may NOT permute all the sets containing
148
`x'` together as this may break the relationship between the
149
sets containing `x`. We need `Q`-Trees. A `Q`-Tree is a tree
150
whose children are ordered, even though their order could be
151
reversed (if the children of a `Q`-Tree are `c_1c_2
152
... c_k`, we can change it to `c_k ... c_2c_1`). We can now
153
express all the orderings satisfying our two constraints the
154
following way :
155
156
* We create a tree `T_1` gathering all the elements not
157
containing `x` nor `x'`, and make it a `P-Tree`
158
159
* We create 3 `P`-Trees `T_{x, x'}, T_x, T_{x'}`, which
160
respectively have for children the elements or our
161
collection containing
162
163
* both `x` and `x'`
164
* only `x`
165
* only `x'`
166
167
* To ensure our constraints on both elements, we create a
168
`Q`-tree `T_2` whose children are in order `T_x, T_{x,
169
x'}, T_{x'}`
170
171
* We now make this `Q`-Tree `T_2` a children of the
172
`P`-Tree `T_1`
173
174
Using these two types of tree, and exploring the different cases
175
of intersection, it is possible to represent all the possible
176
permutations of our sets satisfying or constraints, or to prove
177
that no such ordering exists. This is the whole purpose of this
178
class and this algorithm, and is explained with more details in many
179
places, for example in the following document from Hajiaghayi [Haj]_.
180
181
REFERENCES:
182
183
.. [Haj] M. Hajiaghayi
184
http://www-math.mit.edu/~hajiagha/pp11.ps
185
186
AUTHOR : Nathann Cohen
187
"""
188
189
def __init__(self, seq):
190
r"""
191
Construction of a PQ-Tree
192
193
EXAMPLE::
194
195
sage: from sage.graphs.pq_trees import P, Q
196
sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])])
197
"""
198
from sage.sets.set import Set
199
200
self._children = []
201
for e in seq:
202
if isinstance(e, list):
203
e = Set(e)
204
205
if not e in self._children:
206
self._children.append(e)
207
208
def reverse(self):
209
r"""
210
Recursively reverses ``self`` and its children
211
212
EXAMPLE::
213
214
sage: from sage.graphs.pq_trees import P, Q
215
sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])])
216
sage: p.ordering()
217
[{1, 2}, {2, 3}, {2, 4}, {8, 2}, {9, 2}]
218
sage: p.reverse()
219
sage: p.ordering()
220
[{9, 2}, {8, 2}, {2, 4}, {2, 3}, {1, 2}]
221
"""
222
for i in self._children:
223
if isinstance(i, PQ):
224
i.reverse()
225
226
self._children.reverse()
227
228
def __contains__(self, v):
229
r"""
230
Tests whether there exists an element of ``self`` containing
231
an element ``v``
232
233
INPUT:
234
235
- ``v`` -- an element of the ground set
236
237
EXAMPLE::
238
239
sage: from sage.graphs.pq_trees import P, Q
240
sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])])
241
sage: 5 in p
242
False
243
sage: 9 in p
244
True
245
246
"""
247
for i in self:
248
if v in i:
249
return True
250
False
251
252
def split(self, v):
253
r"""
254
Returns the subsequences of children containing and not
255
containing ``v``
256
257
INPUT:
258
259
- ``v`` -- an element of the ground set
260
261
OUTPUT:
262
263
Two lists, the first containing the children of ``self``
264
containing ``v``, and the other containing the other children.
265
266
.. NOTE::
267
268
This command is meant to be used on a partial tree, once it
269
has be "set continuous" on an element ``v`` and aligned it
270
to the right. Hence, none of the list should be empty (an
271
exception is raised if that happens, as it would reveal a
272
bug in the algorithm) and the sum ``contains +
273
does_not_contain`` should be equal to the sequence of
274
children of ``self``.
275
276
EXAMPLE::
277
278
sage: from sage.graphs.pq_trees import P, Q
279
sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])])
280
sage: p.reverse()
281
sage: contains, does_not_contain = p.split(1)
282
sage: contains
283
[{1, 2}]
284
sage: does_not_contain
285
[('P', [{9, 2}, {8, 2}, {2, 4}]), {2, 3}]
286
sage: does_not_contain + contains == p._children
287
True
288
289
"""
290
contains = []
291
does_not_contain = []
292
293
for i in self:
294
if v in i:
295
contains.append(i)
296
else:
297
does_not_contain.append(i)
298
299
if not contains or not does_not_contain:
300
raise ValueError("None of the sets should be empty !")
301
302
return contains, does_not_contain
303
304
def __iter__(self):
305
r"""
306
Iterates over the children of ``self``.
307
308
EXAMPLE::
309
310
sage: from sage.graphs.pq_trees import P, Q
311
sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])])
312
sage: for i in p:
313
... print i
314
{1, 2}
315
{2, 3}
316
('P', [{2, 4}, {8, 2}, {9, 2}])
317
"""
318
319
for i in self._children:
320
yield i
321
322
def cardinality(self):
323
r"""
324
Returns the number of children of ``self``
325
326
EXAMPLE::
327
328
sage: from sage.graphs.pq_trees import P, Q
329
sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])])
330
sage: p.cardinality()
331
3
332
"""
333
return len(self._children)
334
335
def ordering(self):
336
r"""
337
Returns the current ordering given by listing the leaves from
338
left to right.
339
340
EXAMPLE:
341
342
sage: from sage.graphs.pq_trees import P, Q
343
sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])])
344
sage: p.ordering()
345
[{1, 2}, {2, 3}, {2, 4}, {8, 2}, {9, 2}]
346
"""
347
value = []
348
for i in self:
349
if isinstance(i, PQ):
350
value.extend(i.ordering())
351
else:
352
value.append(i)
353
354
return value
355
356
def __repr__(self):
357
r"""
358
Succintly represents ``self``.
359
360
EXAMPLE::
361
362
sage: from sage.graphs.pq_trees import P, Q
363
sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])])
364
sage: print p
365
('Q', [{1, 2}, {2, 3}, ('P', [{2, 4}, {8, 2}, {9, 2}])])
366
"""
367
return str((("P" if self.is_P() else "Q"),self._children))
368
369
def simplify(self, v, left = False, right = False):
370
r"""
371
Returns a simplified copy of self according to the element ``v``
372
373
If ``self`` is a partial P-tree for ``v``, we would like to
374
restrict the permutations of its children to permutations
375
keeping the children containing ``v`` contiguous. This
376
function also "locks" all the elements not containing ``v``
377
inside a `P`-tree, which is useful when one want to keep the
378
elements containing ``v`` on one side (which is the case when
379
this method is called).
380
381
INPUT:
382
383
- ``left, right`` (booleans) -- whether ``v`` is aligned to the
384
right or to the left
385
386
- ``v```-- an element of the ground set
387
388
OUTPUT:
389
390
If ``self`` is a `Q`-Tree, the sequence of its children is
391
returned. If ``self`` is a `P`-tree, 2 `P`-tree are returned,
392
namely the two `P`-tree defined above and restricting the
393
permutations, in the order implied by ``left, right`` (if
394
``right =True``, the second `P`-tree will be the one gathering
395
the elements containing ``v``, if ``left=True``, the
396
opposite).
397
398
.. NOTE::
399
400
This method is assumes that ``self`` is partial for ``v``,
401
and aligned to the side indicated by ``left, right``.
402
403
EXAMPLES:
404
405
A `P`-Tree ::
406
407
sage: from sage.graphs.pq_trees import P, Q
408
sage: p = P([[2,4], [1,2], [0,8], [0,5]])
409
sage: p.simplify(0, right = True)
410
[('P', [{2, 4}, {1, 2}]), ('P', [{0, 8}, {0, 5}])]
411
412
A `Q`-Tree ::
413
414
sage: q = Q([[2,4], [1,2], [0,8], [0,5]])
415
sage: q.simplify(0, right = True)
416
[{2, 4}, {1, 2}, {0, 8}, {0, 5}]
417
"""
418
if sum([left, right]) !=1:
419
raise ValueError("Exactly one of left or right must be specified")
420
421
if self.is_Q():
422
return self._children
423
else:
424
425
contains, does_not_contain = self.split(v)
426
427
A = new_P(does_not_contain)
428
B = new_P(contains)
429
430
if right:
431
return [A, B]
432
else:
433
return [B, A]
434
435
def flatten(self):
436
r"""
437
Returns a flattened copy of ``self``
438
439
If self has only one child, we may as well consider its
440
child's children, as ``self`` encodes no information. This
441
method recursively "flattens" trees having only on PQ-tree
442
child, and returns it.
443
444
EXAMPLE::
445
446
sage: from sage.graphs.pq_trees import P, Q
447
sage: p = Q([P([[2,4], [2,8], [2,9]])])
448
sage: p.flatten()
449
('P', [{2, 4}, {8, 2}, {9, 2}])
450
"""
451
if self.cardinality() == 1:
452
return flatten(self._children[0])
453
else:
454
self._children = [flatten(x) for x in self._children]
455
return self
456
457
458
def is_P(self):
459
r"""
460
Tests whether ``self`` is a `P`-Tree
461
462
EXAMPLE::
463
464
sage: from sage.graphs.pq_trees import P, Q
465
sage: P([[0,1],[2,3]]).is_P()
466
True
467
sage: Q([[0,1],[2,3]]).is_P()
468
False
469
"""
470
return isinstance(self,P)
471
472
def is_Q(self):
473
r"""
474
Tests whether ``self`` is a `Q`-Tree
475
476
EXAMPLE::
477
478
sage: from sage.graphs.pq_trees import P, Q
479
sage: Q([[0,1],[2,3]]).is_Q()
480
True
481
sage: P([[0,1],[2,3]]).is_Q()
482
False
483
"""
484
return isinstance(self,Q)
485
486
class P(PQ):
487
r"""
488
A P-Tree is a PQ-Tree whose children are
489
not ordered (they can be permuted in any way)
490
"""
491
def set_contiguous(self, v):
492
r"""
493
Updates ``self`` so that its sets containing ``v`` are
494
contiguous for any admissible permutation of its subtrees.
495
496
This function also ensures, whenever possible,
497
that all the sets containing ``v`` are located on an interval
498
on the right side of the ordering.
499
500
INPUT:
501
502
- ``v`` -- an element of the ground set
503
504
OUTPUT:
505
506
According to the cases :
507
508
* ``(EMPTY, ALIGNED)`` if no set of the tree contains
509
an occurrence of ``v``
510
511
* ``(FULL, ALIGNED)`` if all the sets of the tree contain
512
``v``
513
514
* ``(PARTIAL, ALIGNED)`` if some (but not all) of the sets
515
contain ``v``, all of which are aligned
516
to the right of the ordering at the end when the function ends
517
518
* ``(PARTIAL, UNALIGNED)`` if some (but not all) of the
519
sets contain ``v``, though it is impossible to align them
520
all to the right
521
522
In any case, the sets containing ``v`` are contiguous when this
523
function ends. If there is no possibility of doing so, the function
524
raises a ``ValueError`` exception.
525
526
EXAMPLE:
527
528
Ensuring the sets containing ``0`` are continuous::
529
530
sage: from sage.graphs.pq_trees import P, Q
531
sage: p = P([[0,3], [1,2], [2,3], [2,4], [4,0],[2,8], [2,9]])
532
sage: p.set_contiguous(0)
533
(1, True)
534
sage: print p
535
('P', [{1, 2}, {2, 3}, {2, 4}, {8, 2}, {9, 2}, ('P', [{0, 3}, {0, 4}])])
536
537
Impossible situation::
538
539
sage: p = P([[0,1], [1,2], [2,3], [3,0]])
540
sage: p.set_contiguous(0)
541
(1, True)
542
sage: p.set_contiguous(1)
543
(1, True)
544
sage: p.set_contiguous(2)
545
(1, True)
546
sage: p.set_contiguous(3)
547
Traceback (most recent call last):
548
...
549
ValueError: Impossible
550
551
"""
552
553
###############################################################
554
# Defining Variables : #
555
# #
556
# Collecting the information of which children are FULL of v, #
557
# which ones are EMPTY, PARTIAL_ALIGNED and PARTIAL_UNALIGNED #
558
# #
559
# Defining variables for their cardinals, just to make the #
560
# code slightly more readable :-) #
561
###############################################################
562
563
seq = [set_contiguous(x, v) for x in self]
564
self.flatten()
565
seq = [set_contiguous(x, v) for x in self]
566
567
f_seq = dict(zip(self, seq))
568
569
set_FULL = []
570
set_EMPTY = []
571
set_PARTIAL_ALIGNED = []
572
set_PARTIAL_UNALIGNED = []
573
574
sorting = {
575
(FULL, ALIGNED) : set_FULL,
576
(EMPTY, ALIGNED) : set_EMPTY,
577
(PARTIAL, ALIGNED) : set_PARTIAL_ALIGNED,
578
(PARTIAL, UNALIGNED) : set_PARTIAL_UNALIGNED
579
}
580
581
for i in self:
582
sorting[f_seq[i]].append(i)
583
584
n_FULL = len(set_FULL)
585
n_EMPTY = len(set_EMPTY)
586
n_PARTIAL_ALIGNED = len(set_PARTIAL_ALIGNED)
587
n_PARTIAL_UNALIGNED = len(set_PARTIAL_UNALIGNED)
588
589
counts = dict(map(lambda (x,y) : (x,len(y)), sorting.iteritems()))
590
591
# Excludes the situation where there is no solution.
592
# read next comment for more explanations
593
594
if (n_PARTIAL_ALIGNED + n_PARTIAL_UNALIGNED > 2 or
595
(n_PARTIAL_UNALIGNED >= 1 and n_EMPTY != self.cardinality() -1)):
596
597
raise ValueError(impossible_msg)
598
599
# From now on, there are at most two pq-trees which are partially filled
600
# If there is one which is not aligned to the right, all the others are empty
601
602
#########################################################
603
# 1/2 #
604
# #
605
# Several easy cases where we can decide without paying #
606
# attention #
607
#########################################################
608
609
# All the children are FULL
610
elif n_FULL == self.cardinality():
611
return FULL, True
612
613
# All the children are empty
614
elif n_EMPTY == self.cardinality():
615
return EMPTY, True
616
617
# There is a PARTIAL UNALIGNED element (and all the others are
618
# empty as we checked before
619
620
elif n_PARTIAL_UNALIGNED == 1:
621
return (PARTIAL, UNALIGNED)
622
623
# If there is just one partial element and all the others are
624
# empty, we just reorder the set to put it at the right end
625
626
elif (n_PARTIAL_ALIGNED == 1 and
627
n_EMPTY == self.cardinality()-1):
628
629
self._children = set_EMPTY + set_PARTIAL_ALIGNED
630
return (PARTIAL, ALIGNED)
631
632
633
################################################################
634
# 2/2 #
635
# #
636
# From now on, there are at most two partial pq-trees and all #
637
# of them have v aligned to their right #
638
# #
639
# We now want to order them in such a way that all the #
640
# elements containing v are located on the right #
641
################################################################
642
643
else:
644
645
self._children = []
646
647
# We first move the empty elements to the left, if any
648
649
if n_EMPTY > 0:
650
self._children.extend(set_EMPTY)
651
652
# If there is one partial element we but have to add it to
653
# the sequence, then add all the full elements
654
655
# We must also make sure these elements will not be
656
# reordered in such a way that the elements containing v
657
# are not contiguous
658
659
# ==> We create a Q-tree
660
661
if n_PARTIAL_ALIGNED < 2:
662
663
new = []
664
665
666
# add the partial element, if any
667
if n_PARTIAL_ALIGNED == 1:
668
669
subtree = set_PARTIAL_ALIGNED[0]
670
new.extend(subtree.simplify(v, right = ALIGNED))
671
672
673
# Then the full elements, if any, in a P-tree (we can
674
# permute any two of them while keeping all the
675
# elements containing v on an interval
676
677
if n_FULL > 0:
678
679
new.append(new_P(set_FULL))
680
681
# We lock all of them in a Q-tree
682
683
self._children.append(new_Q(new))
684
685
return PARTIAL, True
686
687
# If there are 2 partial elements, we take care of both
688
# ends. We also know it will not be possible to align the
689
# interval of sets containing v to the right
690
691
else:
692
693
new = []
694
695
# The second partal element is aligned to the right
696
# while, as we want to put it at the end of the
697
# interval, it should be aligned to the left
698
set_PARTIAL_ALIGNED[1].reverse()
699
700
# 1/3
701
# Left partial subtree
702
subtree = set_PARTIAL_ALIGNED[0]
703
new.extend(subtree.simplify(v, right = ALIGNED))
704
705
# 2/3
706
# Center (Full elements, in a P-tree, as they can be
707
# permuted)
708
709
if n_FULL > 0:
710
new.append(new_P(set_FULL))
711
712
# 3/3
713
# Right partial subtree
714
subtree = set_PARTIAL_ALIGNED[1]
715
new.extend(subtree.simplify(v, left= ALIGNED))
716
717
# We add all of it, locked in a Q-Tree
718
self._children.append(new_Q(new))
719
720
return PARTIAL, False
721
722
class Q(PQ):
723
r"""
724
A Q-Tree is a PQ-Tree whose children are
725
ordered up to reversal
726
"""
727
728
def set_contiguous(self, v):
729
r"""
730
Updates ``self`` so that its sets containing ``v`` are
731
contiguous for any admissible permutation of its subtrees.
732
733
This function also ensures, whenever possible,
734
that all the sets containing ``v`` are located on an interval
735
on the right side of the ordering.
736
737
INPUT:
738
739
- ``v`` -- an element of the ground set
740
741
OUTPUT:
742
743
According to the cases :
744
745
* ``(EMPTY, ALIGNED)`` if no set of the tree contains
746
an occurrence of ``v``
747
748
* ``(FULL, ALIGNED)`` if all the sets of the tree contain
749
``v``
750
751
* ``(PARTIAL, ALIGNED)`` if some (but not all) of the sets
752
contain ``v``, all of which are aligned
753
to the right of the ordering at the end when the function ends
754
755
* ``(PARTIAL, UNALIGNED)`` if some (but not all) of the
756
sets contain ``v``, though it is impossible to align them
757
all to the right
758
759
In any case, the sets containing ``v`` are contiguous when this
760
function ends. If there is no possibility of doing so, the function
761
raises a ``ValueError`` exception.
762
763
EXAMPLE:
764
765
Ensuring the sets containing ``0`` are continuous::
766
767
sage: from sage.graphs.pq_trees import P, Q
768
sage: q = Q([[2,3], Q([[3,0],[3,1]]), Q([[4,0],[4,5]])])
769
sage: q.set_contiguous(0)
770
(1, False)
771
sage: print q
772
('Q', [{2, 3}, {1, 3}, {0, 3}, {0, 4}, {4, 5}])
773
774
Impossible situation::
775
776
sage: p = Q([[0,1], [1,2], [2,0]])
777
sage: p.set_contiguous(0)
778
Traceback (most recent call last):
779
...
780
ValueError: Impossible
781
782
783
"""
784
785
786
#################################################################
787
# Guidelines : #
788
# #
789
# As the tree is a Q-Tree, we can but reverse the order in #
790
# which the elements appear. It means that we can but check #
791
# the elements containing v are already contiguous (even #
792
# though we have to take special care of partial elements -- #
793
# the endpoints of the interval), and answer accordingly #
794
# (partial, full, empty, aligned..). We also want to align the #
795
# elements containing v to the right if possible. #
796
################################################################
797
798
799
###############################################################
800
# Defining Variables : #
801
# #
802
# Collecting the information of which children are FULL of v, #
803
# which ones are EMPTY, PARTIAL_ALIGNED and PARTIAL_UNALIGNED #
804
# #
805
# Defining variables for their cardinals, just to make the #
806
# code slightly more readable :-) #
807
###############################################################
808
809
seq = [set_contiguous(x, v) for x in self]
810
self.flatten()
811
seq = [set_contiguous(x, v) for x in self]
812
813
f_seq = dict(zip(self, seq))
814
815
set_FULL = []
816
set_EMPTY = []
817
set_PARTIAL_ALIGNED = []
818
set_PARTIAL_UNALIGNED = []
819
820
sorting = {
821
(FULL, ALIGNED) : set_FULL,
822
(EMPTY, ALIGNED) : set_EMPTY,
823
(PARTIAL, ALIGNED) : set_PARTIAL_ALIGNED,
824
(PARTIAL, UNALIGNED) : set_PARTIAL_UNALIGNED
825
}
826
827
for i in self:
828
sorting[f_seq[i]].append(i)
829
830
n_FULL = len(set_FULL)
831
n_EMPTY = len(set_EMPTY)
832
n_PARTIAL_ALIGNED = len(set_PARTIAL_ALIGNED)
833
n_PARTIAL_UNALIGNED = len(set_PARTIAL_UNALIGNED)
834
835
counts = dict(map(lambda (x,y) : (x,len(y)), sorting.iteritems()))
836
837
###################################################################
838
# #
839
# Picking the good ordering for the children : #
840
# #
841
# #
842
# There is a possibility of aligning to the right iif #
843
# the vector can assume the form (as a regular expression) : #
844
# #
845
# (EMPTY *) PARTIAL (FULL *) Of course, each of these three #
846
# members could be empty #
847
# #
848
# Hence, in the following case we reverse the vector : #
849
# #
850
# * if the last element is empty (as we checked the whole #
851
# vector is not empty #
852
# #
853
# * if the last element is partial, aligned, and all the #
854
# others are full #
855
###################################################################
856
857
if (f_seq[self._children[-1]] == (EMPTY, ALIGNED) or
858
(f_seq[self._children[-1]] == (PARTIAL, ALIGNED) and n_FULL == self.cardinality() - 1)):
859
860
# We reverse the order of the elements in the SET only. Which means that they are still aligned to the right !
861
self._children.reverse()
862
863
864
#########################################################
865
# 1/2 #
866
# #
867
# Several easy cases where we can decide without paying #
868
# attention #
869
#########################################################
870
871
872
# Excludes the situation where there is no solution.
873
# read next comment for more explanations
874
875
if (n_PARTIAL_ALIGNED + n_PARTIAL_UNALIGNED > 2 or
876
(n_PARTIAL_UNALIGNED >= 1 and n_EMPTY != self.cardinality() -1)):
877
878
raise ValueError(impossible_msg)
879
880
# From now on, there are at most two pq-trees which are partially filled
881
# If there is one which is not aligned to the right, all the others are empty
882
883
# First trivial case, no checking neded
884
elif n_FULL == self.cardinality():
885
return FULL, True
886
887
# Second trivial case, no checking needed
888
elif n_EMPTY == self.cardinality():
889
return EMPTY, True
890
891
# Third trivial case, no checking needed
892
elif n_PARTIAL_UNALIGNED == 1:
893
return (PARTIAL, UNALIGNED)
894
895
# If there is just one partial element
896
# and all the others are empty, we just reorder
897
# the set to put it at the right end
898
899
elif (n_PARTIAL_ALIGNED == 1 and
900
n_EMPTY == self.cardinality()-1):
901
902
if set_PARTIAL_ALIGNED[0] == self._children[-1]:
903
return (PARTIAL, ALIGNED)
904
905
else:
906
return (PARTIAL, UNALIGNED)
907
908
909
##############################################################
910
# 2/2 #
911
# #
912
# We iteratively consider all the children, and check #
913
# that the elements containing v are indeed #
914
# locate on an interval. #
915
# #
916
# We are also interested in knowing whether this interval is #
917
# aligned to the right #
918
# #
919
# Because of the previous tests, we can assume there are at #
920
# most two partial pq-trees and all of them are aligned to #
921
# their right #
922
##############################################################
923
924
else:
925
926
new_children = []
927
928
# Two variables to remember where we are
929
# according to the interval
930
931
seen_nonempty = False
932
seen_right_end = False
933
934
935
for i in self:
936
937
type, aligned = f_seq[i]
938
939
# We met an empty element
940
if type == EMPTY:
941
942
# 2 possibilities :
943
#
944
# * we have NOT met a non-empty element before
945
# and it just means we are looking at the
946
# leading empty elements
947
#
948
# * we have met a non-empty element before and it
949
# means we will never met another non-empty
950
# element again => we have seen the right end
951
# of the interval
952
953
new_children.append(i)
954
955
if seen_nonempty:
956
seen_right_end = True
957
958
# We met a non-empty element
959
else:
960
if seen_right_end:
961
raise ValueError(impossible_msg)
962
963
964
if type == PARTIAL:
965
966
# if we see an ALIGNED partial tree after
967
# having seen a nonempty element then the
968
# partial tree must be aligned to the left and
969
# so we have seen the right end
970
971
if seen_nonempty and aligned:
972
i.reverse()
973
seen_right_end = True
974
975
# right partial subtree
976
subtree = i
977
new_children.extend(subtree.simplify(v, left = True))
978
979
# If we see an UNALIGNED partial element after
980
# having met a nonempty element, there is no
981
# solution to the alignment problem
982
983
elif seen_nonempty and not aligned:
984
raise ValueError(impossible_msg)
985
986
# If we see an unaligned element but no non-empty
987
# element since the beginning, we are witnessing both the
988
# left and right end
989
990
elif not seen_nonempty and not aligned:
991
raise ValueError("Bon, ben ca arrive O_o")
992
seen_right_end = True
993
994
elif not seen_nonempty and aligned:
995
996
# left partial subtree
997
subtree = i
998
999
new_children.extend(subtree.simplify(v, right = True))
1000
1001
1002
else:
1003
new_children.append(i)
1004
1005
seen_nonempty = True
1006
1007
# Setting the updated sequence of children
1008
self._children = new_children
1009
1010
1011
# Whether we achieved an alignment to the right is the
1012
# complement of whether we have seen the right end
1013
1014
return (PARTIAL, not seen_right_end)
1015
1016
1017