passagemath-combinat 10.6.42__cp314-cp314-musllinux_1_2_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_combinat/__init__.py +3 -0
- passagemath_combinat-10.6.42.dist-info/METADATA +160 -0
- passagemath_combinat-10.6.42.dist-info/RECORD +400 -0
- passagemath_combinat-10.6.42.dist-info/WHEEL +5 -0
- passagemath_combinat-10.6.42.dist-info/top_level.txt +3 -0
- passagemath_combinat.libs/libgmp-0e7fc84e.so.10.5.0 +0 -0
- passagemath_combinat.libs/libsymmetrica-81fe8739.so.3.0.0 +0 -0
- sage/algebras/affine_nil_temperley_lieb.py +263 -0
- sage/algebras/all.py +24 -0
- sage/algebras/all__sagemath_combinat.py +35 -0
- sage/algebras/askey_wilson.py +935 -0
- sage/algebras/associated_graded.py +345 -0
- sage/algebras/cellular_basis.py +350 -0
- sage/algebras/cluster_algebra.py +2766 -0
- sage/algebras/down_up_algebra.py +860 -0
- sage/algebras/free_algebra.py +1698 -0
- sage/algebras/free_algebra_element.py +345 -0
- sage/algebras/free_algebra_quotient.py +405 -0
- sage/algebras/free_algebra_quotient_element.py +295 -0
- sage/algebras/free_zinbiel_algebra.py +885 -0
- sage/algebras/hall_algebra.py +783 -0
- sage/algebras/hecke_algebras/all.py +4 -0
- sage/algebras/hecke_algebras/ariki_koike_algebra.py +1796 -0
- sage/algebras/hecke_algebras/ariki_koike_specht_modules.py +475 -0
- sage/algebras/hecke_algebras/cubic_hecke_algebra.py +3520 -0
- sage/algebras/hecke_algebras/cubic_hecke_base_ring.py +1473 -0
- sage/algebras/hecke_algebras/cubic_hecke_matrix_rep.py +1079 -0
- sage/algebras/iwahori_hecke_algebra.py +3095 -0
- sage/algebras/jordan_algebra.py +1773 -0
- sage/algebras/lie_conformal_algebras/abelian_lie_conformal_algebra.py +113 -0
- sage/algebras/lie_conformal_algebras/affine_lie_conformal_algebra.py +156 -0
- sage/algebras/lie_conformal_algebras/all.py +18 -0
- sage/algebras/lie_conformal_algebras/bosonic_ghosts_lie_conformal_algebra.py +134 -0
- sage/algebras/lie_conformal_algebras/examples.py +43 -0
- sage/algebras/lie_conformal_algebras/fermionic_ghosts_lie_conformal_algebra.py +131 -0
- sage/algebras/lie_conformal_algebras/finitely_freely_generated_lca.py +139 -0
- sage/algebras/lie_conformal_algebras/free_bosons_lie_conformal_algebra.py +174 -0
- sage/algebras/lie_conformal_algebras/free_fermions_lie_conformal_algebra.py +167 -0
- sage/algebras/lie_conformal_algebras/freely_generated_lie_conformal_algebra.py +107 -0
- sage/algebras/lie_conformal_algebras/graded_lie_conformal_algebra.py +135 -0
- sage/algebras/lie_conformal_algebras/lie_conformal_algebra.py +353 -0
- sage/algebras/lie_conformal_algebras/lie_conformal_algebra_element.py +236 -0
- sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_basis.py +78 -0
- sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_structure_coefs.py +328 -0
- sage/algebras/lie_conformal_algebras/n2_lie_conformal_algebra.py +117 -0
- sage/algebras/lie_conformal_algebras/neveu_schwarz_lie_conformal_algebra.py +86 -0
- sage/algebras/lie_conformal_algebras/virasoro_lie_conformal_algebra.py +82 -0
- sage/algebras/lie_conformal_algebras/weyl_lie_conformal_algebra.py +205 -0
- sage/algebras/nil_coxeter_algebra.py +191 -0
- sage/algebras/q_commuting_polynomials.py +673 -0
- sage/algebras/q_system.py +608 -0
- sage/algebras/quantum_clifford.py +959 -0
- sage/algebras/quantum_groups/ace_quantum_onsager.py +693 -0
- sage/algebras/quantum_groups/all.py +9 -0
- sage/algebras/quantum_groups/fock_space.py +2219 -0
- sage/algebras/quantum_groups/q_numbers.py +207 -0
- sage/algebras/quantum_groups/quantum_group_gap.py +2695 -0
- sage/algebras/quantum_groups/representations.py +591 -0
- sage/algebras/quantum_matrix_coordinate_algebra.py +1006 -0
- sage/algebras/quantum_oscillator.py +623 -0
- sage/algebras/quaternion_algebra.py +20 -0
- sage/algebras/quaternion_algebra_element.py +55 -0
- sage/algebras/rational_cherednik_algebra.py +525 -0
- sage/algebras/schur_algebra.py +670 -0
- sage/algebras/shuffle_algebra.py +1011 -0
- sage/algebras/splitting_algebra.py +779 -0
- sage/algebras/tensor_algebra.py +709 -0
- sage/algebras/yangian.py +1082 -0
- sage/algebras/yokonuma_hecke_algebra.py +1018 -0
- sage/all__sagemath_combinat.py +35 -0
- sage/combinat/SJT.py +255 -0
- sage/combinat/affine_permutation.py +2405 -0
- sage/combinat/algebraic_combinatorics.py +55 -0
- sage/combinat/all.py +53 -0
- sage/combinat/all__sagemath_combinat.py +195 -0
- sage/combinat/alternating_sign_matrix.py +2063 -0
- sage/combinat/baxter_permutations.py +346 -0
- sage/combinat/bijectionist.py +3220 -0
- sage/combinat/binary_recurrence_sequences.py +1180 -0
- sage/combinat/blob_algebra.py +685 -0
- sage/combinat/catalog_partitions.py +27 -0
- sage/combinat/chas/all.py +23 -0
- sage/combinat/chas/fsym.py +1180 -0
- sage/combinat/chas/wqsym.py +2601 -0
- sage/combinat/cluster_complex.py +326 -0
- sage/combinat/colored_permutations.py +2039 -0
- sage/combinat/colored_permutations_representations.py +964 -0
- sage/combinat/composition_signed.py +142 -0
- sage/combinat/composition_tableau.py +855 -0
- sage/combinat/constellation.py +1729 -0
- sage/combinat/core.py +751 -0
- sage/combinat/counting.py +12 -0
- sage/combinat/crystals/affine.py +742 -0
- sage/combinat/crystals/affine_factorization.py +518 -0
- sage/combinat/crystals/affinization.py +331 -0
- sage/combinat/crystals/alcove_path.py +2013 -0
- sage/combinat/crystals/all.py +22 -0
- sage/combinat/crystals/bkk_crystals.py +141 -0
- sage/combinat/crystals/catalog.py +115 -0
- sage/combinat/crystals/catalog_elementary_crystals.py +18 -0
- sage/combinat/crystals/catalog_infinity_crystals.py +33 -0
- sage/combinat/crystals/catalog_kirillov_reshetikhin.py +18 -0
- sage/combinat/crystals/crystals.py +257 -0
- sage/combinat/crystals/direct_sum.py +260 -0
- sage/combinat/crystals/elementary_crystals.py +1251 -0
- sage/combinat/crystals/fast_crystals.py +441 -0
- sage/combinat/crystals/fully_commutative_stable_grothendieck.py +1205 -0
- sage/combinat/crystals/generalized_young_walls.py +1076 -0
- sage/combinat/crystals/highest_weight_crystals.py +436 -0
- sage/combinat/crystals/induced_structure.py +695 -0
- sage/combinat/crystals/infinity_crystals.py +730 -0
- sage/combinat/crystals/kac_modules.py +863 -0
- sage/combinat/crystals/kirillov_reshetikhin.py +4196 -0
- sage/combinat/crystals/kyoto_path_model.py +497 -0
- sage/combinat/crystals/letters.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/crystals/letters.pxd +79 -0
- sage/combinat/crystals/letters.pyx +3056 -0
- sage/combinat/crystals/littelmann_path.py +1518 -0
- sage/combinat/crystals/monomial_crystals.py +1262 -0
- sage/combinat/crystals/multisegments.py +462 -0
- sage/combinat/crystals/mv_polytopes.py +467 -0
- sage/combinat/crystals/pbw_crystal.py +511 -0
- sage/combinat/crystals/pbw_datum.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/crystals/pbw_datum.pxd +4 -0
- sage/combinat/crystals/pbw_datum.pyx +487 -0
- sage/combinat/crystals/polyhedral_realization.py +372 -0
- sage/combinat/crystals/spins.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/crystals/spins.pxd +21 -0
- sage/combinat/crystals/spins.pyx +756 -0
- sage/combinat/crystals/star_crystal.py +290 -0
- sage/combinat/crystals/subcrystal.py +464 -0
- sage/combinat/crystals/tensor_product.py +1177 -0
- sage/combinat/crystals/tensor_product_element.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/crystals/tensor_product_element.pxd +35 -0
- sage/combinat/crystals/tensor_product_element.pyx +1870 -0
- sage/combinat/crystals/virtual_crystal.py +420 -0
- sage/combinat/cyclic_sieving_phenomenon.py +204 -0
- sage/combinat/debruijn_sequence.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/debruijn_sequence.pyx +355 -0
- sage/combinat/decorated_permutation.py +270 -0
- sage/combinat/degree_sequences.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/degree_sequences.pyx +588 -0
- sage/combinat/derangements.py +527 -0
- sage/combinat/descent_algebra.py +1008 -0
- sage/combinat/diagram.py +1551 -0
- sage/combinat/diagram_algebras.py +5886 -0
- sage/combinat/dyck_word.py +4349 -0
- sage/combinat/e_one_star.py +1623 -0
- sage/combinat/enumerated_sets.py +123 -0
- sage/combinat/expnums.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/expnums.pyx +148 -0
- sage/combinat/fast_vector_partitions.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/fast_vector_partitions.pyx +346 -0
- sage/combinat/fqsym.py +1977 -0
- sage/combinat/free_dendriform_algebra.py +954 -0
- sage/combinat/free_prelie_algebra.py +1141 -0
- sage/combinat/fully_commutative_elements.py +1077 -0
- sage/combinat/fully_packed_loop.py +1523 -0
- sage/combinat/gelfand_tsetlin_patterns.py +1409 -0
- sage/combinat/gray_codes.py +311 -0
- sage/combinat/grossman_larson_algebras.py +667 -0
- sage/combinat/growth.py +4352 -0
- sage/combinat/hall_polynomial.py +188 -0
- sage/combinat/hillman_grassl.py +866 -0
- sage/combinat/integer_matrices.py +329 -0
- sage/combinat/integer_vectors_mod_permgroup.py +1238 -0
- sage/combinat/k_tableau.py +4564 -0
- sage/combinat/kazhdan_lusztig.py +215 -0
- sage/combinat/key_polynomial.py +885 -0
- sage/combinat/knutson_tao_puzzles.py +2286 -0
- sage/combinat/lr_tableau.py +311 -0
- sage/combinat/matrices/all.py +24 -0
- sage/combinat/matrices/hadamard_matrix.py +3790 -0
- sage/combinat/matrices/latin.py +2912 -0
- sage/combinat/misc.py +401 -0
- sage/combinat/multiset_partition_into_sets_ordered.py +3541 -0
- sage/combinat/ncsf_qsym/all.py +21 -0
- sage/combinat/ncsf_qsym/combinatorics.py +317 -0
- sage/combinat/ncsf_qsym/generic_basis_code.py +1427 -0
- sage/combinat/ncsf_qsym/ncsf.py +5637 -0
- sage/combinat/ncsf_qsym/qsym.py +4053 -0
- sage/combinat/ncsf_qsym/tutorial.py +447 -0
- sage/combinat/ncsym/all.py +21 -0
- sage/combinat/ncsym/bases.py +855 -0
- sage/combinat/ncsym/dual.py +593 -0
- sage/combinat/ncsym/ncsym.py +2076 -0
- sage/combinat/necklace.py +551 -0
- sage/combinat/non_decreasing_parking_function.py +634 -0
- sage/combinat/nu_dyck_word.py +1474 -0
- sage/combinat/output.py +861 -0
- sage/combinat/parallelogram_polyomino.py +4326 -0
- sage/combinat/parking_functions.py +1602 -0
- sage/combinat/partition_algebra.py +1998 -0
- sage/combinat/partition_kleshchev.py +1982 -0
- sage/combinat/partition_shifting_algebras.py +584 -0
- sage/combinat/partition_tuple.py +3114 -0
- sage/combinat/path_tableaux/all.py +13 -0
- sage/combinat/path_tableaux/catalog.py +29 -0
- sage/combinat/path_tableaux/dyck_path.py +380 -0
- sage/combinat/path_tableaux/frieze.py +476 -0
- sage/combinat/path_tableaux/path_tableau.py +728 -0
- sage/combinat/path_tableaux/semistandard.py +510 -0
- sage/combinat/perfect_matching.py +779 -0
- sage/combinat/plane_partition.py +3300 -0
- sage/combinat/q_bernoulli.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/q_bernoulli.pyx +128 -0
- sage/combinat/quickref.py +81 -0
- sage/combinat/recognizable_series.py +2051 -0
- sage/combinat/regular_sequence.py +4316 -0
- sage/combinat/regular_sequence_bounded.py +543 -0
- sage/combinat/restricted_growth.py +81 -0
- sage/combinat/ribbon.py +20 -0
- sage/combinat/ribbon_shaped_tableau.py +489 -0
- sage/combinat/ribbon_tableau.py +1180 -0
- sage/combinat/rigged_configurations/all.py +46 -0
- sage/combinat/rigged_configurations/bij_abstract_class.py +548 -0
- sage/combinat/rigged_configurations/bij_infinity.py +370 -0
- sage/combinat/rigged_configurations/bij_type_A.py +163 -0
- sage/combinat/rigged_configurations/bij_type_A2_dual.py +338 -0
- sage/combinat/rigged_configurations/bij_type_A2_even.py +218 -0
- sage/combinat/rigged_configurations/bij_type_A2_odd.py +199 -0
- sage/combinat/rigged_configurations/bij_type_B.py +900 -0
- sage/combinat/rigged_configurations/bij_type_C.py +267 -0
- sage/combinat/rigged_configurations/bij_type_D.py +771 -0
- sage/combinat/rigged_configurations/bij_type_D_tri.py +392 -0
- sage/combinat/rigged_configurations/bij_type_D_twisted.py +576 -0
- sage/combinat/rigged_configurations/bij_type_E67.py +402 -0
- sage/combinat/rigged_configurations/bijection.py +143 -0
- sage/combinat/rigged_configurations/kleber_tree.py +1475 -0
- sage/combinat/rigged_configurations/kr_tableaux.py +1898 -0
- sage/combinat/rigged_configurations/rc_crystal.py +461 -0
- sage/combinat/rigged_configurations/rc_infinity.py +540 -0
- sage/combinat/rigged_configurations/rigged_configuration_element.py +2403 -0
- sage/combinat/rigged_configurations/rigged_configurations.py +1918 -0
- sage/combinat/rigged_configurations/rigged_partition.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/rigged_configurations/rigged_partition.pxd +15 -0
- sage/combinat/rigged_configurations/rigged_partition.pyx +680 -0
- sage/combinat/rigged_configurations/tensor_product_kr_tableaux.py +499 -0
- sage/combinat/rigged_configurations/tensor_product_kr_tableaux_element.py +428 -0
- sage/combinat/rsk.py +3438 -0
- sage/combinat/schubert_polynomial.py +508 -0
- sage/combinat/set_partition.py +3318 -0
- sage/combinat/set_partition_iterator.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/set_partition_iterator.pyx +136 -0
- sage/combinat/set_partition_ordered.py +1590 -0
- sage/combinat/sf/abreu_nigro.py +346 -0
- sage/combinat/sf/all.py +52 -0
- sage/combinat/sf/character.py +576 -0
- sage/combinat/sf/classical.py +319 -0
- sage/combinat/sf/dual.py +996 -0
- sage/combinat/sf/elementary.py +549 -0
- sage/combinat/sf/hall_littlewood.py +1028 -0
- sage/combinat/sf/hecke.py +336 -0
- sage/combinat/sf/homogeneous.py +464 -0
- sage/combinat/sf/jack.py +1428 -0
- sage/combinat/sf/k_dual.py +1458 -0
- sage/combinat/sf/kfpoly.py +447 -0
- sage/combinat/sf/llt.py +789 -0
- sage/combinat/sf/macdonald.py +2019 -0
- sage/combinat/sf/monomial.py +525 -0
- sage/combinat/sf/multiplicative.py +113 -0
- sage/combinat/sf/new_kschur.py +1786 -0
- sage/combinat/sf/ns_macdonald.py +964 -0
- sage/combinat/sf/orthogonal.py +246 -0
- sage/combinat/sf/orthotriang.py +355 -0
- sage/combinat/sf/powersum.py +963 -0
- sage/combinat/sf/schur.py +880 -0
- sage/combinat/sf/sf.py +1653 -0
- sage/combinat/sf/sfa.py +7053 -0
- sage/combinat/sf/symplectic.py +253 -0
- sage/combinat/sf/witt.py +721 -0
- sage/combinat/shifted_primed_tableau.py +2735 -0
- sage/combinat/shuffle.py +830 -0
- sage/combinat/sidon_sets.py +146 -0
- sage/combinat/similarity_class_type.py +1721 -0
- sage/combinat/sine_gordon.py +618 -0
- sage/combinat/six_vertex_model.py +784 -0
- sage/combinat/skew_partition.py +2053 -0
- sage/combinat/skew_tableau.py +2989 -0
- sage/combinat/sloane_functions.py +8935 -0
- sage/combinat/specht_module.py +1403 -0
- sage/combinat/species/all.py +48 -0
- sage/combinat/species/characteristic_species.py +321 -0
- sage/combinat/species/composition_species.py +273 -0
- sage/combinat/species/cycle_species.py +284 -0
- sage/combinat/species/empty_species.py +155 -0
- sage/combinat/species/functorial_composition_species.py +148 -0
- sage/combinat/species/generating_series.py +673 -0
- sage/combinat/species/library.py +148 -0
- sage/combinat/species/linear_order_species.py +169 -0
- sage/combinat/species/misc.py +83 -0
- sage/combinat/species/partition_species.py +290 -0
- sage/combinat/species/permutation_species.py +268 -0
- sage/combinat/species/product_species.py +423 -0
- sage/combinat/species/recursive_species.py +476 -0
- sage/combinat/species/set_species.py +192 -0
- sage/combinat/species/species.py +820 -0
- sage/combinat/species/structure.py +539 -0
- sage/combinat/species/subset_species.py +243 -0
- sage/combinat/species/sum_species.py +225 -0
- sage/combinat/subword.py +564 -0
- sage/combinat/subword_complex.py +2122 -0
- sage/combinat/subword_complex_c.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/subword_complex_c.pyx +119 -0
- sage/combinat/super_tableau.py +821 -0
- sage/combinat/superpartition.py +1154 -0
- sage/combinat/symmetric_group_algebra.py +3774 -0
- sage/combinat/symmetric_group_representations.py +1830 -0
- sage/combinat/t_sequences.py +877 -0
- sage/combinat/tableau.py +9506 -0
- sage/combinat/tableau_residues.py +860 -0
- sage/combinat/tableau_tuple.py +5353 -0
- sage/combinat/tiling.py +2432 -0
- sage/combinat/triangles_FHM.py +777 -0
- sage/combinat/tutorial.py +1857 -0
- sage/combinat/vector_partition.py +337 -0
- sage/combinat/words/abstract_word.py +1722 -0
- sage/combinat/words/all.py +59 -0
- sage/combinat/words/alphabet.py +268 -0
- sage/combinat/words/finite_word.py +7201 -0
- sage/combinat/words/infinite_word.py +113 -0
- sage/combinat/words/lyndon_word.py +652 -0
- sage/combinat/words/morphic.py +351 -0
- sage/combinat/words/morphism.py +3878 -0
- sage/combinat/words/paths.py +2932 -0
- sage/combinat/words/shuffle_product.py +278 -0
- sage/combinat/words/suffix_trees.py +1873 -0
- sage/combinat/words/word.py +769 -0
- sage/combinat/words/word_char.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/words/word_char.pyx +847 -0
- sage/combinat/words/word_datatypes.cpython-314-x86_64-linux-musl.so +0 -0
- sage/combinat/words/word_datatypes.pxd +4 -0
- sage/combinat/words/word_datatypes.pyx +1067 -0
- sage/combinat/words/word_generators.py +2026 -0
- sage/combinat/words/word_infinite_datatypes.py +1218 -0
- sage/combinat/words/word_options.py +99 -0
- sage/combinat/words/words.py +2396 -0
- sage/data_structures/all__sagemath_combinat.py +1 -0
- sage/databases/all__sagemath_combinat.py +13 -0
- sage/databases/findstat.py +4897 -0
- sage/databases/oeis.py +2058 -0
- sage/databases/sloane.py +393 -0
- sage/dynamics/all__sagemath_combinat.py +14 -0
- sage/dynamics/cellular_automata/all.py +7 -0
- sage/dynamics/cellular_automata/catalog.py +34 -0
- sage/dynamics/cellular_automata/elementary.py +612 -0
- sage/dynamics/cellular_automata/glca.py +477 -0
- sage/dynamics/cellular_automata/solitons.py +1463 -0
- sage/dynamics/finite_dynamical_system.py +1249 -0
- sage/dynamics/finite_dynamical_system_catalog.py +382 -0
- sage/games/all.py +7 -0
- sage/games/hexad.py +704 -0
- sage/games/quantumino.py +591 -0
- sage/games/sudoku.py +889 -0
- sage/games/sudoku_backtrack.cpython-314-x86_64-linux-musl.so +0 -0
- sage/games/sudoku_backtrack.pyx +189 -0
- sage/groups/all__sagemath_combinat.py +1 -0
- sage/groups/indexed_free_group.py +489 -0
- sage/libs/all__sagemath_combinat.py +6 -0
- sage/libs/lrcalc/__init__.py +1 -0
- sage/libs/lrcalc/lrcalc.py +525 -0
- sage/libs/symmetrica/__init__.py +7 -0
- sage/libs/symmetrica/all.py +101 -0
- sage/libs/symmetrica/kostka.pxi +168 -0
- sage/libs/symmetrica/part.pxi +193 -0
- sage/libs/symmetrica/plet.pxi +42 -0
- sage/libs/symmetrica/sab.pxi +196 -0
- sage/libs/symmetrica/sb.pxi +332 -0
- sage/libs/symmetrica/sc.pxi +192 -0
- sage/libs/symmetrica/schur.pxi +956 -0
- sage/libs/symmetrica/symmetrica.cpython-314-x86_64-linux-musl.so +0 -0
- sage/libs/symmetrica/symmetrica.pxi +1172 -0
- sage/libs/symmetrica/symmetrica.pyx +39 -0
- sage/monoids/all.py +13 -0
- sage/monoids/automatic_semigroup.py +1054 -0
- sage/monoids/free_abelian_monoid.py +315 -0
- sage/monoids/free_abelian_monoid_element.cpython-314-x86_64-linux-musl.so +0 -0
- sage/monoids/free_abelian_monoid_element.pxd +16 -0
- sage/monoids/free_abelian_monoid_element.pyx +397 -0
- sage/monoids/free_monoid.py +335 -0
- sage/monoids/free_monoid_element.py +431 -0
- sage/monoids/hecke_monoid.py +65 -0
- sage/monoids/string_monoid.py +817 -0
- sage/monoids/string_monoid_element.py +547 -0
- sage/monoids/string_ops.py +143 -0
- sage/monoids/trace_monoid.py +972 -0
- sage/rings/all__sagemath_combinat.py +2 -0
- sage/sat/all.py +4 -0
- sage/sat/boolean_polynomials.py +405 -0
- sage/sat/converters/__init__.py +6 -0
- sage/sat/converters/anf2cnf.py +14 -0
- sage/sat/converters/polybori.py +611 -0
- sage/sat/solvers/__init__.py +5 -0
- sage/sat/solvers/cryptominisat.py +287 -0
- sage/sat/solvers/dimacs.py +783 -0
- sage/sat/solvers/picosat.py +228 -0
- sage/sat/solvers/sat_lp.py +156 -0
- sage/sat/solvers/satsolver.cpython-314-x86_64-linux-musl.so +0 -0
- sage/sat/solvers/satsolver.pxd +3 -0
- sage/sat/solvers/satsolver.pyx +405 -0
|
@@ -0,0 +1,1873 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-combinat
|
|
2
|
+
r"""
|
|
3
|
+
Suffix tries and suffix trees
|
|
4
|
+
"""
|
|
5
|
+
# ****************************************************************************
|
|
6
|
+
# Copyright (C) 2008 Franco Saliola <saliola@gmail.com>
|
|
7
|
+
#
|
|
8
|
+
# This program is free software: you can redistribute it and/or modify
|
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
|
10
|
+
# the Free Software Foundation, either version 2 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
# https://www.gnu.org/licenses/
|
|
13
|
+
# ****************************************************************************
|
|
14
|
+
from itertools import chain
|
|
15
|
+
|
|
16
|
+
from sage.structure.sage_object import SageObject
|
|
17
|
+
from sage.sets.set import Set
|
|
18
|
+
from sage.combinat.words.words import Words
|
|
19
|
+
from sage.combinat.words.word import Word
|
|
20
|
+
from sage.misc.lazy_import import lazy_import
|
|
21
|
+
from sage.rings.integer import Integer
|
|
22
|
+
|
|
23
|
+
lazy_import('sage.graphs.digraph', 'DiGraph')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ------------
|
|
27
|
+
# Suffix Tries
|
|
28
|
+
# ------------
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SuffixTrie(SageObject):
|
|
32
|
+
def __init__(self, word):
|
|
33
|
+
r"""
|
|
34
|
+
Construct the suffix trie of the word w.
|
|
35
|
+
|
|
36
|
+
The suffix trie of a finite word w is a data structure representing
|
|
37
|
+
the factors of w. It is a tree whose edges are labelled with
|
|
38
|
+
letters of w, and whose leafs correspond to suffixes of w.
|
|
39
|
+
|
|
40
|
+
This is a straightforward implementation of Algorithm 1 from
|
|
41
|
+
[Ukko1995]_. It constructs the suffix trie of w[:i] from that
|
|
42
|
+
of w[:i-1].
|
|
43
|
+
|
|
44
|
+
A suffix trie is modelled as a deterministic finite-state automaton
|
|
45
|
+
together with the suffix_link map. The set of states corresponds to
|
|
46
|
+
factors of the word (below we write x' for the state corresponding
|
|
47
|
+
to x); these are always 0, 1, .... The state 0 is the initial
|
|
48
|
+
state, and it corresponds to the empty word. For the purposes of
|
|
49
|
+
the algorithm, there is also an auxiliary state -1. The transition
|
|
50
|
+
function t is defined as::
|
|
51
|
+
|
|
52
|
+
t(-1,a) = 0 for all letters a; and
|
|
53
|
+
t(x',a) = y' for all x',y' \in Q such that y = xa,
|
|
54
|
+
|
|
55
|
+
and the suffix link function is defined as::
|
|
56
|
+
|
|
57
|
+
suffix_link(0) = -1;
|
|
58
|
+
suffix_link(x') = y', if x = ay for some letter a.
|
|
59
|
+
|
|
60
|
+
REFERENCES:
|
|
61
|
+
|
|
62
|
+
- [Ukko1995]_
|
|
63
|
+
|
|
64
|
+
EXAMPLES::
|
|
65
|
+
|
|
66
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
67
|
+
sage: w = Words("cao")("cacao")
|
|
68
|
+
sage: t = SuffixTrie(w); t
|
|
69
|
+
Suffix Trie of the word: cacao
|
|
70
|
+
|
|
71
|
+
::
|
|
72
|
+
|
|
73
|
+
sage: e = Words("ab")()
|
|
74
|
+
sage: t = SuffixTrie(e); t
|
|
75
|
+
Suffix Trie of the word:
|
|
76
|
+
sage: t.process_letter("a"); t
|
|
77
|
+
Suffix Trie of the word: a
|
|
78
|
+
sage: t.process_letter("b"); t
|
|
79
|
+
Suffix Trie of the word: ab
|
|
80
|
+
sage: t.process_letter("a"); t
|
|
81
|
+
Suffix Trie of the word: aba
|
|
82
|
+
|
|
83
|
+
TESTS::
|
|
84
|
+
|
|
85
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
86
|
+
sage: w = Words("cao")("cacao")
|
|
87
|
+
sage: s = SuffixTrie(w)
|
|
88
|
+
sage: loads(dumps(s))
|
|
89
|
+
Suffix Trie of the word: cacao
|
|
90
|
+
"""
|
|
91
|
+
# Create the suffix trie for the empty word.
|
|
92
|
+
self._active_state = 0
|
|
93
|
+
self._transition_function = {}
|
|
94
|
+
self._suffix_link = [-1]
|
|
95
|
+
self._alphabet = word.parent().alphabet()
|
|
96
|
+
|
|
97
|
+
# Process each letter, in order.
|
|
98
|
+
W = word.parent()
|
|
99
|
+
for letter in word:
|
|
100
|
+
self._process_letter(W([letter]))
|
|
101
|
+
|
|
102
|
+
def _process_letter(self, letter):
|
|
103
|
+
r"""
|
|
104
|
+
Process a letter. That is, modify the current suffix trie producing
|
|
105
|
+
the suffix trie for ``self.word() + letter``.
|
|
106
|
+
|
|
107
|
+
.. NOTE::
|
|
108
|
+
|
|
109
|
+
``letter`` must occur within the alphabet of the word.
|
|
110
|
+
|
|
111
|
+
EXAMPLES::
|
|
112
|
+
|
|
113
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
114
|
+
sage: t = SuffixTrie(Word("ababba"))
|
|
115
|
+
sage: t._process_letter(Words("ab")("b")); t
|
|
116
|
+
Suffix Trie of the word: ababbab
|
|
117
|
+
"""
|
|
118
|
+
r = self._active_state
|
|
119
|
+
old_s = None
|
|
120
|
+
# While r is not the auxiliary vertex, or
|
|
121
|
+
# there is not transition from r along letter, ...
|
|
122
|
+
while r != -1 and (r, letter) not in self._transition_function:
|
|
123
|
+
# adjoin a new state s
|
|
124
|
+
s = len(self._suffix_link)
|
|
125
|
+
self._suffix_link.append(None)
|
|
126
|
+
# create a transition from r to s along letter
|
|
127
|
+
self._transition_function[(r, letter)] = s
|
|
128
|
+
if r != self._active_state:
|
|
129
|
+
# update the suffix link
|
|
130
|
+
self._suffix_link[old_s] = s
|
|
131
|
+
old_s = s
|
|
132
|
+
r = self._suffix_link[r]
|
|
133
|
+
# update the suffix link for the last visited state
|
|
134
|
+
if r == -1:
|
|
135
|
+
self._suffix_link[old_s] = 0
|
|
136
|
+
else:
|
|
137
|
+
self._suffix_link[old_s] = self._transition_function[(r, letter)]
|
|
138
|
+
# update the active state
|
|
139
|
+
self._active_state = \
|
|
140
|
+
self._transition_function[(self._active_state, letter)]
|
|
141
|
+
|
|
142
|
+
def process_letter(self, letter):
|
|
143
|
+
r"""
|
|
144
|
+
Modify ``self`` to produce the suffix trie for ``self.word() +
|
|
145
|
+
letter``.
|
|
146
|
+
|
|
147
|
+
.. NOTE::
|
|
148
|
+
|
|
149
|
+
``letter`` must occur within the alphabet of the word.
|
|
150
|
+
|
|
151
|
+
EXAMPLES::
|
|
152
|
+
|
|
153
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
154
|
+
sage: w = Words("ab")("ababba")
|
|
155
|
+
sage: t = SuffixTrie(w); t
|
|
156
|
+
Suffix Trie of the word: ababba
|
|
157
|
+
sage: t.process_letter("a"); t
|
|
158
|
+
Suffix Trie of the word: ababbaa
|
|
159
|
+
|
|
160
|
+
TESTS::
|
|
161
|
+
|
|
162
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
163
|
+
sage: w = Words("cao")("cacao")
|
|
164
|
+
sage: t = SuffixTrie(w); t
|
|
165
|
+
Suffix Trie of the word: cacao
|
|
166
|
+
sage: t.process_letter("d")
|
|
167
|
+
Traceback (most recent call last):
|
|
168
|
+
...
|
|
169
|
+
ValueError: d not in alphabet
|
|
170
|
+
"""
|
|
171
|
+
# Make certain that letter is a word containing one letter.
|
|
172
|
+
letter = Words(self._alphabet)([letter])
|
|
173
|
+
self._process_letter(letter)
|
|
174
|
+
|
|
175
|
+
#####
|
|
176
|
+
# The following are not necessary for constructing the suffix trie (just
|
|
177
|
+
# the __init__ and process_letter are needed). They just add additional
|
|
178
|
+
# functionality to the class.
|
|
179
|
+
#####
|
|
180
|
+
|
|
181
|
+
def _repr_(self) -> str:
|
|
182
|
+
"""
|
|
183
|
+
TESTS::
|
|
184
|
+
|
|
185
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
186
|
+
sage: SuffixTrie(Word("abcba"))._repr_()
|
|
187
|
+
'Suffix Trie of the word: abcba'
|
|
188
|
+
"""
|
|
189
|
+
return 'Suffix Trie of the %s' % repr(self.word())
|
|
190
|
+
|
|
191
|
+
def node_to_word(self, state=0):
|
|
192
|
+
r"""
|
|
193
|
+
Return the word obtained by reading the edge labels from 0 to
|
|
194
|
+
``state``.
|
|
195
|
+
|
|
196
|
+
INPUT:
|
|
197
|
+
|
|
198
|
+
- ``state`` -- (default: 0) a state
|
|
199
|
+
|
|
200
|
+
EXAMPLES::
|
|
201
|
+
|
|
202
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
203
|
+
sage: w = Words("abc")("abcba")
|
|
204
|
+
sage: t = SuffixTrie(w)
|
|
205
|
+
sage: t.node_to_word(10)
|
|
206
|
+
word: abcba
|
|
207
|
+
sage: t.node_to_word(7)
|
|
208
|
+
word: abcb
|
|
209
|
+
"""
|
|
210
|
+
if state == 0:
|
|
211
|
+
return Words(self._alphabet)()
|
|
212
|
+
# We first invert the transition function
|
|
213
|
+
tf_inv = {b: a for a, b in self._transition_function.items()}
|
|
214
|
+
|
|
215
|
+
# Starting from the active state,
|
|
216
|
+
# read labels along the unique path to the root.
|
|
217
|
+
u, letter = tf_inv[state]
|
|
218
|
+
w = letter
|
|
219
|
+
s = u
|
|
220
|
+
while s != 0:
|
|
221
|
+
u, letter = tf_inv[s]
|
|
222
|
+
w = letter * w
|
|
223
|
+
s = u
|
|
224
|
+
return w
|
|
225
|
+
|
|
226
|
+
def word(self):
|
|
227
|
+
r"""
|
|
228
|
+
Return the word whose suffix tree this is.
|
|
229
|
+
|
|
230
|
+
EXAMPLES::
|
|
231
|
+
|
|
232
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
233
|
+
sage: w = Words("abc")("abcba")
|
|
234
|
+
sage: t = SuffixTrie(w)
|
|
235
|
+
sage: t.word()
|
|
236
|
+
word: abcba
|
|
237
|
+
sage: t.word() == w
|
|
238
|
+
True
|
|
239
|
+
"""
|
|
240
|
+
return self.node_to_word(self._active_state)
|
|
241
|
+
|
|
242
|
+
def __eq__(self, other) -> bool:
|
|
243
|
+
r"""
|
|
244
|
+
If ``self`` and ``other`` have the same transition function,
|
|
245
|
+
the same suffix link, and the same word, then they are equal.
|
|
246
|
+
|
|
247
|
+
TESTS::
|
|
248
|
+
|
|
249
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
250
|
+
sage: SuffixTrie(Word("cacao")) == SuffixTrie(Word("ababc"))
|
|
251
|
+
False
|
|
252
|
+
sage: W = Words("cao")
|
|
253
|
+
sage: s = SuffixTrie(W("cacao"))
|
|
254
|
+
sage: t = SuffixTrie(W())
|
|
255
|
+
sage: t.process_letter("c")
|
|
256
|
+
sage: t.process_letter("a")
|
|
257
|
+
sage: t.process_letter("c")
|
|
258
|
+
sage: t.process_letter("a")
|
|
259
|
+
sage: t.process_letter("o")
|
|
260
|
+
sage: t == s
|
|
261
|
+
True
|
|
262
|
+
"""
|
|
263
|
+
if not isinstance(other, SuffixTrie):
|
|
264
|
+
return False
|
|
265
|
+
return self._transition_function == other._transition_function \
|
|
266
|
+
and self._suffix_link == other._suffix_link \
|
|
267
|
+
and self.word() == other.word()
|
|
268
|
+
|
|
269
|
+
def transition_function(self, node, word):
|
|
270
|
+
r"""
|
|
271
|
+
Return the state reached by beginning at ``node`` and following the
|
|
272
|
+
arrows in the transition graph labelled by the letters of ``word``.
|
|
273
|
+
|
|
274
|
+
INPUT:
|
|
275
|
+
|
|
276
|
+
- ``node`` -- a node
|
|
277
|
+
- ``word`` -- a word
|
|
278
|
+
|
|
279
|
+
EXAMPLES::
|
|
280
|
+
|
|
281
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
282
|
+
sage: w = Words([0,1])([0,1,0,1,1])
|
|
283
|
+
sage: t = SuffixTrie(w)
|
|
284
|
+
sage: all(t.transition_function(u, letter) == v
|
|
285
|
+
....: for (u, letter), v in t._transition_function.items())
|
|
286
|
+
True
|
|
287
|
+
"""
|
|
288
|
+
if node == -1:
|
|
289
|
+
return self.transition_function(0, word[1:])
|
|
290
|
+
if word.is_empty():
|
|
291
|
+
return 0
|
|
292
|
+
if word.length() == 1:
|
|
293
|
+
return self._transition_function[(node, word)]
|
|
294
|
+
return self.transition_function(
|
|
295
|
+
self._transition_function[(node, word[0:1])], word[1:])
|
|
296
|
+
|
|
297
|
+
def states(self):
|
|
298
|
+
r"""
|
|
299
|
+
Return the states of the automaton defined by the suffix trie.
|
|
300
|
+
|
|
301
|
+
EXAMPLES::
|
|
302
|
+
|
|
303
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
304
|
+
sage: w = Words([0,1])([0,1,1])
|
|
305
|
+
sage: t = SuffixTrie(w)
|
|
306
|
+
sage: t.states()
|
|
307
|
+
[0, 1, 2, 3, 4]
|
|
308
|
+
|
|
309
|
+
::
|
|
310
|
+
|
|
311
|
+
sage: u = Words("aco")("cacao")
|
|
312
|
+
sage: s = SuffixTrie(u)
|
|
313
|
+
sage: s.states()
|
|
314
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
|
315
|
+
"""
|
|
316
|
+
return list(range(len(self._transition_function)))
|
|
317
|
+
|
|
318
|
+
def suffix_link(self, state):
|
|
319
|
+
r"""
|
|
320
|
+
Evaluate the suffix link map of the suffix trie on ``state``.
|
|
321
|
+
|
|
322
|
+
Note that the suffix link map is not defined on -1.
|
|
323
|
+
|
|
324
|
+
INPUT:
|
|
325
|
+
|
|
326
|
+
- ``state`` -- a state
|
|
327
|
+
|
|
328
|
+
EXAMPLES::
|
|
329
|
+
|
|
330
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
331
|
+
sage: w = Words("cao")("cacao")
|
|
332
|
+
sage: t = SuffixTrie(w)
|
|
333
|
+
sage: list(map(t.suffix_link, range(13)))
|
|
334
|
+
[-1, 0, 3, 0, 5, 1, 7, 2, 9, 10, 11, 12, 0]
|
|
335
|
+
sage: t.suffix_link(0)
|
|
336
|
+
-1
|
|
337
|
+
|
|
338
|
+
TESTS::
|
|
339
|
+
|
|
340
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
341
|
+
sage: w = Words("cao")("cacao")
|
|
342
|
+
sage: t = SuffixTrie(w)
|
|
343
|
+
sage: t.suffix_link([1])
|
|
344
|
+
Traceback (most recent call last):
|
|
345
|
+
...
|
|
346
|
+
TypeError: [1] is not an integer
|
|
347
|
+
sage: t.suffix_link(-1)
|
|
348
|
+
Traceback (most recent call last):
|
|
349
|
+
...
|
|
350
|
+
TypeError: suffix link is not defined for -1
|
|
351
|
+
sage: t.suffix_link(17)
|
|
352
|
+
Traceback (most recent call last):
|
|
353
|
+
...
|
|
354
|
+
TypeError: 17 is not a state
|
|
355
|
+
"""
|
|
356
|
+
if not isinstance(state, (int, Integer)):
|
|
357
|
+
raise TypeError("%s is not an integer" % state)
|
|
358
|
+
if state == -1:
|
|
359
|
+
raise TypeError("suffix link is not defined for -1")
|
|
360
|
+
if state not in range(len(self._suffix_link)):
|
|
361
|
+
raise TypeError("%s is not a state" % state)
|
|
362
|
+
return self._suffix_link[state]
|
|
363
|
+
|
|
364
|
+
def active_state(self):
|
|
365
|
+
r"""
|
|
366
|
+
Return the active state of the suffix trie.
|
|
367
|
+
|
|
368
|
+
This is the state corresponding to the word as a suffix of
|
|
369
|
+
itself.
|
|
370
|
+
|
|
371
|
+
EXAMPLES::
|
|
372
|
+
|
|
373
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
374
|
+
sage: w = Words("cao")("cacao")
|
|
375
|
+
sage: t = SuffixTrie(w)
|
|
376
|
+
sage: t.active_state()
|
|
377
|
+
8
|
|
378
|
+
|
|
379
|
+
::
|
|
380
|
+
|
|
381
|
+
sage: u = Words([0,1])([0,1,1,0,1,0,0,1])
|
|
382
|
+
sage: s = SuffixTrie(u)
|
|
383
|
+
sage: s.active_state()
|
|
384
|
+
22
|
|
385
|
+
"""
|
|
386
|
+
return self._active_state
|
|
387
|
+
|
|
388
|
+
def final_states(self):
|
|
389
|
+
r"""
|
|
390
|
+
Return the set of final states of the suffix trie.
|
|
391
|
+
|
|
392
|
+
These are the states corresponding to the suffixes of
|
|
393
|
+
``self.word()``. They are obtained be repeatedly following the
|
|
394
|
+
suffix link from the active state until we reach 0.
|
|
395
|
+
|
|
396
|
+
EXAMPLES::
|
|
397
|
+
|
|
398
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
399
|
+
sage: w = Words("cao")("cacao")
|
|
400
|
+
sage: t = SuffixTrie(w)
|
|
401
|
+
sage: t.final_states() == Set([8, 9, 10, 11, 12, 0])
|
|
402
|
+
True
|
|
403
|
+
"""
|
|
404
|
+
s = self._active_state
|
|
405
|
+
F = [s]
|
|
406
|
+
while s != 0:
|
|
407
|
+
s = self._suffix_link[s]
|
|
408
|
+
F.append(s)
|
|
409
|
+
return Set(F)
|
|
410
|
+
|
|
411
|
+
def has_suffix(self, word) -> bool:
|
|
412
|
+
r"""
|
|
413
|
+
Return ``True`` if and only if ``word`` is a suffix of ``self.word()``.
|
|
414
|
+
|
|
415
|
+
EXAMPLES::
|
|
416
|
+
|
|
417
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
418
|
+
sage: w = Words("cao")("cacao")
|
|
419
|
+
sage: t = SuffixTrie(w)
|
|
420
|
+
sage: [t.has_suffix(w[i:]) for i in range(w.length()+1)]
|
|
421
|
+
[True, True, True, True, True, True]
|
|
422
|
+
sage: [t.has_suffix(w[:i]) for i in range(w.length()+1)]
|
|
423
|
+
[True, False, False, False, False, True]
|
|
424
|
+
"""
|
|
425
|
+
# Find the state corresponding to word, and
|
|
426
|
+
# check to see if s is a final state.
|
|
427
|
+
s = self.transition_function(0, word)
|
|
428
|
+
q = self._active_state
|
|
429
|
+
if q == s:
|
|
430
|
+
return True
|
|
431
|
+
|
|
432
|
+
while q != 0:
|
|
433
|
+
q = self._suffix_link[q]
|
|
434
|
+
if q == s:
|
|
435
|
+
return True
|
|
436
|
+
return False
|
|
437
|
+
|
|
438
|
+
def to_digraph(self):
|
|
439
|
+
r"""
|
|
440
|
+
Return a ``DiGraph`` object of the transition graph of the suffix
|
|
441
|
+
trie.
|
|
442
|
+
|
|
443
|
+
EXAMPLES::
|
|
444
|
+
|
|
445
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
446
|
+
sage: w = Words("cao")("cac")
|
|
447
|
+
sage: t = SuffixTrie(w)
|
|
448
|
+
sage: d = t.to_digraph(); d # needs sage.graphs
|
|
449
|
+
Digraph on 6 vertices
|
|
450
|
+
sage: d.adjacency_matrix() # needs sage.graphs sage.modules
|
|
451
|
+
[0 1 0 1 0 0]
|
|
452
|
+
[0 0 1 0 0 0]
|
|
453
|
+
[0 0 0 0 1 0]
|
|
454
|
+
[0 0 0 0 0 1]
|
|
455
|
+
[0 0 0 0 0 0]
|
|
456
|
+
[0 0 0 0 0 0]
|
|
457
|
+
"""
|
|
458
|
+
dag = {}
|
|
459
|
+
for (u, letter), v in self._transition_function.items():
|
|
460
|
+
dag.setdefault(u, {})[v] = letter
|
|
461
|
+
return DiGraph(dag)
|
|
462
|
+
|
|
463
|
+
def plot(self, layout='tree', tree_root=0, tree_orientation='up',
|
|
464
|
+
vertex_colors=None, edge_labels=True, *args, **kwds):
|
|
465
|
+
r"""
|
|
466
|
+
Return a Graphics object corresponding to the transition graph of
|
|
467
|
+
the suffix trie.
|
|
468
|
+
|
|
469
|
+
EXAMPLES::
|
|
470
|
+
|
|
471
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
472
|
+
sage: SuffixTrie(Word("cacao")).plot() # needs sage.plot
|
|
473
|
+
Graphics object consisting of 38 graphics primitives
|
|
474
|
+
|
|
475
|
+
TESTS::
|
|
476
|
+
|
|
477
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
478
|
+
sage: type(SuffixTrie(Word("cacao")).plot()) # needs sage.plot
|
|
479
|
+
<class 'sage.plot.graphics.Graphics'>
|
|
480
|
+
"""
|
|
481
|
+
tree = self.to_digraph()
|
|
482
|
+
for u, v, label in tree.edge_iterator():
|
|
483
|
+
tree.set_edge_label(u, v, label.string_rep())
|
|
484
|
+
if vertex_colors is None:
|
|
485
|
+
suffix_nodes = self.final_states()
|
|
486
|
+
non_suffix_nodes = list(set(self.states()) - set(suffix_nodes))
|
|
487
|
+
vertex_colors = {'#fec7b8': suffix_nodes, '#ffffff': non_suffix_nodes}
|
|
488
|
+
return tree.plot(layout=layout, tree_root=tree_root,
|
|
489
|
+
tree_orientation=tree_orientation,
|
|
490
|
+
vertex_colors=vertex_colors, edge_labels=edge_labels,
|
|
491
|
+
*args, **kwds)
|
|
492
|
+
|
|
493
|
+
def show(self, *args, **kwds):
|
|
494
|
+
r"""
|
|
495
|
+
Display the output of :meth:`plot`.
|
|
496
|
+
|
|
497
|
+
EXAMPLES::
|
|
498
|
+
|
|
499
|
+
sage: from sage.combinat.words.suffix_trees import SuffixTrie
|
|
500
|
+
sage: w = Words("cao")("cac")
|
|
501
|
+
sage: t = SuffixTrie(w)
|
|
502
|
+
sage: t.show() # needs sage.plot
|
|
503
|
+
"""
|
|
504
|
+
self.plot(*args, **kwds).show()
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
# ------------
|
|
508
|
+
# Suffix Trees
|
|
509
|
+
# ------------
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
class ImplicitSuffixTree(SageObject):
|
|
513
|
+
def __init__(self, word):
|
|
514
|
+
r"""
|
|
515
|
+
Construct the implicit suffix tree of a word w.
|
|
516
|
+
|
|
517
|
+
The suffix tree of a word w is a compactification of the suffix
|
|
518
|
+
trie for w. The compactification removes all nodes that have
|
|
519
|
+
exactly one incoming edge and exactly one outgoing edge. It
|
|
520
|
+
consists of two components: a tree and a word. Thus, instead of
|
|
521
|
+
labelling the edges by factors of w, we can labelled them by
|
|
522
|
+
indices of the occurrence of the factors in w.
|
|
523
|
+
|
|
524
|
+
The following is a straightforward implementation of Ukkonen's
|
|
525
|
+
on-line algorithm for constructing the
|
|
526
|
+
implicit suffix tree [Ukko1995]_. It constructs the suffix tree for
|
|
527
|
+
w[:i] from that of w[:i-1].
|
|
528
|
+
|
|
529
|
+
GENERAL IDEA. The suffix tree of w[:i+1] can be obtained from that
|
|
530
|
+
of w[:i] by visiting each node corresponding to a suffix of w[:i]
|
|
531
|
+
and modifying the tree by applying one of two rules (either append
|
|
532
|
+
a new node to the tree, or split an edge into two). The "active
|
|
533
|
+
state" is the node where the algorithm begins and the "suffix link"
|
|
534
|
+
carries us to the next node that needs to be dealt with.
|
|
535
|
+
|
|
536
|
+
TREE. The tree is modelled as an automaton, which is stored as a
|
|
537
|
+
dictionary of dictionaries: it is keyed by the nodes of the tree,
|
|
538
|
+
and the corresponding dictionary is keyed by pairs `(i,j)` of
|
|
539
|
+
integers representing the word w[i-1:j]. This makes it faster to
|
|
540
|
+
look up a particular transition beginning at a specific node.
|
|
541
|
+
|
|
542
|
+
STATES/NODES. The states will always be -1, 0, 1, ..., n. The state
|
|
543
|
+
-1 is special and is only used for the purposes of the algorithm.
|
|
544
|
+
All transitions map -1 to 0, so this information is not explicitly
|
|
545
|
+
stored in the transition function.
|
|
546
|
+
|
|
547
|
+
EXPLICIT/IMPLICIT NODES. By definition, some of the nodes will not
|
|
548
|
+
be states, but merely locations along an edge; these are called
|
|
549
|
+
implicit nodes. A node r (implicit or explicit) is referenced as a
|
|
550
|
+
pair (s,(k,p)) where s is an ancestor of r and w[k-1:p] is the word
|
|
551
|
+
read by transitioning from s to r in the suffix trie. A reference
|
|
552
|
+
pair is canonical if s is the closest ancestor of r.
|
|
553
|
+
|
|
554
|
+
SUFFIX LINK. The algorithm makes use of a map from (some) nodes to
|
|
555
|
+
other nodes, called the suffix link. This is stored as a
|
|
556
|
+
dictionary.
|
|
557
|
+
|
|
558
|
+
ACTIVE STATE. We store as ._active_state the active state of the
|
|
559
|
+
tree, the state where the algorithm will begin when processing the
|
|
560
|
+
next letter.
|
|
561
|
+
|
|
562
|
+
RUNNING TIME. The running time and storage space of the algorithm
|
|
563
|
+
is linear in the length of the word w (whereas for a suffix tree it
|
|
564
|
+
is quadratic).
|
|
565
|
+
|
|
566
|
+
REFERENCES:
|
|
567
|
+
|
|
568
|
+
- [Ukko1995]_
|
|
569
|
+
|
|
570
|
+
EXAMPLES::
|
|
571
|
+
|
|
572
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
573
|
+
sage: w = Words("aco")("cacao")
|
|
574
|
+
sage: t = ImplicitSuffixTree(w); t
|
|
575
|
+
Implicit Suffix Tree of the word: cacao
|
|
576
|
+
sage: ababb = Words([0,1])([0,1,0,1,1])
|
|
577
|
+
sage: s = ImplicitSuffixTree(ababb); s
|
|
578
|
+
Implicit Suffix Tree of the word: 01011
|
|
579
|
+
|
|
580
|
+
TESTS::
|
|
581
|
+
|
|
582
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
583
|
+
sage: w = Words("cao")("cacao")
|
|
584
|
+
sage: s = ImplicitSuffixTree(w)
|
|
585
|
+
sage: loads(dumps(s))
|
|
586
|
+
Implicit Suffix Tree of the word: cacao
|
|
587
|
+
"""
|
|
588
|
+
# For constructing the suffix tree.
|
|
589
|
+
self._transition_function = {0: {}}
|
|
590
|
+
self._suffix_link = {0: -1}
|
|
591
|
+
self._active_state = (0, (1, 1))
|
|
592
|
+
self._letters = []
|
|
593
|
+
for letter in word:
|
|
594
|
+
self._letters.append(letter)
|
|
595
|
+
self._process_letter(letter)
|
|
596
|
+
# _word is not needed for constructing the suffix tree,
|
|
597
|
+
# but it is useful for the other methods.
|
|
598
|
+
self._word = word
|
|
599
|
+
|
|
600
|
+
def _process_letter(self, letter):
|
|
601
|
+
r"""
|
|
602
|
+
This is the main part of Ukkonen's algorithm.
|
|
603
|
+
|
|
604
|
+
This corresponds to the algorithm "update" in [Ukko1995]_.
|
|
605
|
+
|
|
606
|
+
.. NOTE::
|
|
607
|
+
|
|
608
|
+
This function is a helper and does not update ``self._data`` and
|
|
609
|
+
``self._word``.
|
|
610
|
+
|
|
611
|
+
REFERENCES:
|
|
612
|
+
|
|
613
|
+
- [Ukko1995]_
|
|
614
|
+
|
|
615
|
+
TESTS::
|
|
616
|
+
|
|
617
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
618
|
+
sage: w = Words("aco")("caca")
|
|
619
|
+
sage: t = ImplicitSuffixTree(w); t
|
|
620
|
+
Implicit Suffix Tree of the word: caca
|
|
621
|
+
sage: new_letter = "o"
|
|
622
|
+
sage: t._letters.append("o")
|
|
623
|
+
sage: t._process_letter("o")
|
|
624
|
+
sage: t._word = Words("aco")("cacao")
|
|
625
|
+
sage: t
|
|
626
|
+
Implicit Suffix Tree of the word: cacao
|
|
627
|
+
|
|
628
|
+
::
|
|
629
|
+
|
|
630
|
+
sage: W = Words([0,1])
|
|
631
|
+
sage: s = ImplicitSuffixTree(W([0,1,0,1])); s
|
|
632
|
+
Implicit Suffix Tree of the word: 0101
|
|
633
|
+
sage: s._letters.append(1)
|
|
634
|
+
sage: s._process_letter(1)
|
|
635
|
+
sage: s._word = W([0,1,0,1,1])
|
|
636
|
+
sage: s
|
|
637
|
+
Implicit Suffix Tree of the word: 01011
|
|
638
|
+
"""
|
|
639
|
+
s, (k, i) = self._active_state
|
|
640
|
+
old_r = 0
|
|
641
|
+
end_state, r = self._test_and_split(s, (k, i-1), letter)
|
|
642
|
+
while not end_state:
|
|
643
|
+
# adjoin a new state rr and create a transition from r to rr
|
|
644
|
+
rr = len(self._transition_function)
|
|
645
|
+
self._transition_function[rr] = {}
|
|
646
|
+
self._transition_function[r][(i, None)] = rr
|
|
647
|
+
# update the suffix link, if necessary
|
|
648
|
+
if old_r != 0:
|
|
649
|
+
self._suffix_link[old_r] = r
|
|
650
|
+
old_r = r
|
|
651
|
+
# follow the suffix link to the next state
|
|
652
|
+
s, k = self._canonize(self._suffix_link[s], (k, i-1))
|
|
653
|
+
end_state, r = self._test_and_split(s, (k, i-1), letter)
|
|
654
|
+
# update the suffix link, if necessary
|
|
655
|
+
if old_r != 0:
|
|
656
|
+
self._suffix_link[old_r] = s
|
|
657
|
+
# set the active state
|
|
658
|
+
s, k = self._canonize(s, (k, i))
|
|
659
|
+
self._active_state = (s, (k, i+1))
|
|
660
|
+
|
|
661
|
+
def _test_and_split(self, s, k_p, letter):
|
|
662
|
+
r"""
|
|
663
|
+
Helper function for :meth:`_process_letter`. Test to see
|
|
664
|
+
whether an edge needs to be split.
|
|
665
|
+
|
|
666
|
+
OUTPUT:
|
|
667
|
+
|
|
668
|
+
``(True, state)``, where ``state`` is the next state to
|
|
669
|
+
process (either a newly created state or the original ``s``).
|
|
670
|
+
|
|
671
|
+
TESTS::
|
|
672
|
+
|
|
673
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
674
|
+
sage: w = Words("aco")("caca")
|
|
675
|
+
sage: t = ImplicitSuffixTree(w)
|
|
676
|
+
sage: t._letters.append(w.parent().alphabet().rank("o"))
|
|
677
|
+
sage: t._test_and_split(0, (4,5), w.parent().alphabet().rank("o"))
|
|
678
|
+
(False, 3)
|
|
679
|
+
"""
|
|
680
|
+
k, p = k_p
|
|
681
|
+
if k <= p:
|
|
682
|
+
# find the transition from s that begins with k-th letter
|
|
683
|
+
(kk, pp), ss = self._find_transition(s, self._letters[k - 1])
|
|
684
|
+
if letter == self._letters[kk + p - k]:
|
|
685
|
+
return (True, s)
|
|
686
|
+
else:
|
|
687
|
+
# replace transition above by transitions
|
|
688
|
+
del self._transition_function[s][(kk, pp)]
|
|
689
|
+
r = len(self._transition_function)
|
|
690
|
+
self._transition_function[r] = {}
|
|
691
|
+
self._transition_function[s][(kk, kk+p-k)] = r
|
|
692
|
+
self._transition_function[r][(kk+p-k+1, pp)] = ss
|
|
693
|
+
return (False, r)
|
|
694
|
+
else:
|
|
695
|
+
transition = self._find_transition(s, letter)
|
|
696
|
+
if transition is None:
|
|
697
|
+
return (False, s)
|
|
698
|
+
else:
|
|
699
|
+
return (True, s)
|
|
700
|
+
|
|
701
|
+
def _canonize(self, s, k_p):
|
|
702
|
+
r"""
|
|
703
|
+
Given an implicit or explicit reference pair for a node, return
|
|
704
|
+
the canonical reference pair.
|
|
705
|
+
|
|
706
|
+
Recall that a node r is referenced as (s, (k,p)), where s is an
|
|
707
|
+
ancestor or r and w[k-1:p] is the word obtained by reading the edge
|
|
708
|
+
labels along the path from s to r. A reference pair is canonical if
|
|
709
|
+
s is the closest ancestor of r.
|
|
710
|
+
|
|
711
|
+
TESTS::
|
|
712
|
+
|
|
713
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
714
|
+
sage: t = ImplicitSuffixTree(Word("cacao"))
|
|
715
|
+
sage: t._canonize(0,(3,5))
|
|
716
|
+
(3, 5)
|
|
717
|
+
sage: t._canonize(0,(2,5))
|
|
718
|
+
(5, 3)
|
|
719
|
+
"""
|
|
720
|
+
k, p = k_p
|
|
721
|
+
if p < k:
|
|
722
|
+
return (s, k)
|
|
723
|
+
else:
|
|
724
|
+
(kk, pp), ss = self._find_transition(s, self._letters[k - 1])
|
|
725
|
+
while pp is not None and pp - kk <= p - k:
|
|
726
|
+
k = k + pp - kk + 1
|
|
727
|
+
s = ss
|
|
728
|
+
if k <= p:
|
|
729
|
+
(kk, pp), ss = self._find_transition(s, self._letters[k-1])
|
|
730
|
+
return (s, k)
|
|
731
|
+
|
|
732
|
+
def _find_transition(self, state, letter):
|
|
733
|
+
r"""
|
|
734
|
+
Return the transition from state that begins with letter.
|
|
735
|
+
|
|
736
|
+
This returns ``None`` if no such transition exists.
|
|
737
|
+
|
|
738
|
+
The transitions are stored as a dictionary of dictionaries: keyed
|
|
739
|
+
by the nodes, with the corresponding dictionary keyed by pairs
|
|
740
|
+
`(i,j)` of integers representing the word w[i-1:j].
|
|
741
|
+
|
|
742
|
+
._transition_function = {..., node: {(i,j): target_node, ...} }
|
|
743
|
+
|
|
744
|
+
TESTS::
|
|
745
|
+
|
|
746
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
747
|
+
sage: t = ImplicitSuffixTree(Word("cacao"))
|
|
748
|
+
sage: t._find_transition(-1, "c")
|
|
749
|
+
((0, 0), 0)
|
|
750
|
+
sage: t._find_transition(0, "a")
|
|
751
|
+
((2, 2), 5)
|
|
752
|
+
sage: t._find_transition(0, "c")
|
|
753
|
+
((1, 2), 3)
|
|
754
|
+
sage: t._find_transition(5, "c")
|
|
755
|
+
((3, None), 2)
|
|
756
|
+
sage: t._find_transition(5, "a")
|
|
757
|
+
|
|
758
|
+
::
|
|
759
|
+
|
|
760
|
+
sage: t = ImplicitSuffixTree(Word([0,1,0,1,1]))
|
|
761
|
+
sage: t._find_transition(3, 1)
|
|
762
|
+
((5, None), 4)
|
|
763
|
+
"""
|
|
764
|
+
if state == -1:
|
|
765
|
+
return ((0, 0), 0)
|
|
766
|
+
|
|
767
|
+
if state in self._transition_function:
|
|
768
|
+
for (k, p), s in self._transition_function[state].items():
|
|
769
|
+
if self._letters[k - 1] == letter:
|
|
770
|
+
return ((k, p), s)
|
|
771
|
+
return None
|
|
772
|
+
|
|
773
|
+
# The following are not necessary for constructing the implicit suffix
|
|
774
|
+
# tree; they add additional functionality to the class.
|
|
775
|
+
|
|
776
|
+
# -------------
|
|
777
|
+
# Visualization
|
|
778
|
+
# -------------
|
|
779
|
+
|
|
780
|
+
def _repr_(self) -> str:
|
|
781
|
+
r"""
|
|
782
|
+
TESTS::
|
|
783
|
+
|
|
784
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
785
|
+
sage: ImplicitSuffixTree(Word("abcba"))._repr_()
|
|
786
|
+
'Implicit Suffix Tree of the word: abcba'
|
|
787
|
+
"""
|
|
788
|
+
return 'Implicit Suffix Tree of the %s' % repr(self.word())
|
|
789
|
+
|
|
790
|
+
def word(self):
|
|
791
|
+
r"""
|
|
792
|
+
Return the word whose implicit suffix tree this is.
|
|
793
|
+
|
|
794
|
+
TESTS::
|
|
795
|
+
|
|
796
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
797
|
+
sage: ImplicitSuffixTree(Word([0,1,0,1,0])).word() == Word([0,1,0,1,0])
|
|
798
|
+
True
|
|
799
|
+
"""
|
|
800
|
+
return self._word
|
|
801
|
+
|
|
802
|
+
def transition_function_dictionary(self) -> dict:
|
|
803
|
+
r"""
|
|
804
|
+
Return the transition function as a dictionary of dictionaries.
|
|
805
|
+
|
|
806
|
+
The format is consistent with the input format for ``DiGraph``.
|
|
807
|
+
|
|
808
|
+
EXAMPLES::
|
|
809
|
+
|
|
810
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
811
|
+
sage: W = Words("aco")
|
|
812
|
+
sage: t = ImplicitSuffixTree(W("cac"))
|
|
813
|
+
sage: t.transition_function_dictionary()
|
|
814
|
+
{0: {1: (0, None), 2: (1, None)}}
|
|
815
|
+
|
|
816
|
+
::
|
|
817
|
+
|
|
818
|
+
sage: W = Words([0,1])
|
|
819
|
+
sage: t = ImplicitSuffixTree(W([0,1,0]))
|
|
820
|
+
sage: t.transition_function_dictionary()
|
|
821
|
+
{0: {1: (0, None), 2: (1, None)}}
|
|
822
|
+
"""
|
|
823
|
+
d = {}
|
|
824
|
+
for u, v, (i, j) in self.edge_iterator():
|
|
825
|
+
d.setdefault(u, {})[v] = (i, j)
|
|
826
|
+
return d
|
|
827
|
+
|
|
828
|
+
def to_digraph(self, word_labels=False):
|
|
829
|
+
r"""
|
|
830
|
+
Return a ``DiGraph`` object of the transition graph of the suffix tree.
|
|
831
|
+
|
|
832
|
+
INPUT:
|
|
833
|
+
|
|
834
|
+
- ``word_labels`` -- boolean (default: ``False``); if ``False``, labels
|
|
835
|
+
the edges by pairs `(i, j)`. If ``True``, labels the edges by ``word[i:j]``
|
|
836
|
+
|
|
837
|
+
EXAMPLES::
|
|
838
|
+
|
|
839
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
840
|
+
sage: W = Words([0,1,2])
|
|
841
|
+
sage: t = ImplicitSuffixTree(W([0,1,0,1,2]))
|
|
842
|
+
sage: t.to_digraph() # needs sage.graphs
|
|
843
|
+
Digraph on 8 vertices
|
|
844
|
+
"""
|
|
845
|
+
if not self._letters:
|
|
846
|
+
d = {0: {}}
|
|
847
|
+
return DiGraph(d)
|
|
848
|
+
d = self.transition_function_dictionary()
|
|
849
|
+
for u in d:
|
|
850
|
+
for v, (i, j) in d[u].items():
|
|
851
|
+
if word_labels:
|
|
852
|
+
d[u][v] = self._word[i:j]
|
|
853
|
+
elif j is None:
|
|
854
|
+
d[u][v] = (i, len(self._letters))
|
|
855
|
+
return DiGraph(d)
|
|
856
|
+
|
|
857
|
+
def plot(self, word_labels=False, layout='tree', tree_root=0,
|
|
858
|
+
tree_orientation='up', vertex_colors=None, edge_labels=True,
|
|
859
|
+
*args, **kwds):
|
|
860
|
+
r"""
|
|
861
|
+
Return a Graphics object corresponding to the transition graph of
|
|
862
|
+
the suffix tree.
|
|
863
|
+
|
|
864
|
+
INPUT:
|
|
865
|
+
|
|
866
|
+
- ``word_labels`` -- boolean (default: ``False``); if ``False``, labels
|
|
867
|
+
the edges by pairs `(i, j)`; if ``True``, labels the edges by ``word[i:j]``
|
|
868
|
+
- ``layout`` -- (default: ``'tree'``)
|
|
869
|
+
- ``tree_root`` -- (default: 0)
|
|
870
|
+
- ``tree_orientation`` -- (default: ``'up'``)
|
|
871
|
+
- ``vertex_colors`` -- (default: ``None``)
|
|
872
|
+
- ``edge_labels`` -- (default: ``True``)
|
|
873
|
+
|
|
874
|
+
EXAMPLES::
|
|
875
|
+
|
|
876
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
877
|
+
sage: ImplicitSuffixTree(Word('cacao')).plot(word_labels=True) # needs sage.graphs sage.plot
|
|
878
|
+
Graphics object consisting of 23 graphics primitives
|
|
879
|
+
sage: ImplicitSuffixTree(Word('cacao')).plot(word_labels=False) # needs sage.graphs sage.plot
|
|
880
|
+
Graphics object consisting of 23 graphics primitives
|
|
881
|
+
|
|
882
|
+
TESTS::
|
|
883
|
+
|
|
884
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
885
|
+
sage: type(ImplicitSuffixTree(Word('cacao')).plot(word_labels=True)) # needs sage.graphs sage.plot
|
|
886
|
+
<class 'sage.plot.graphics.Graphics'>
|
|
887
|
+
sage: type(ImplicitSuffixTree(Word('cacao')).plot(word_labels=False)) # needs sage.graphs sage.plot
|
|
888
|
+
<class 'sage.plot.graphics.Graphics'>
|
|
889
|
+
"""
|
|
890
|
+
tree = self.to_digraph(word_labels=word_labels)
|
|
891
|
+
if word_labels:
|
|
892
|
+
for u, v, label in tree.edge_iterator():
|
|
893
|
+
tree.set_edge_label(u, v, label.string_rep())
|
|
894
|
+
if vertex_colors is None:
|
|
895
|
+
vertex_colors = {'#fec7b8': tree.vertices(sort=True)}
|
|
896
|
+
return tree.plot(layout=layout, tree_root=tree_root,
|
|
897
|
+
tree_orientation=tree_orientation,
|
|
898
|
+
vertex_colors=vertex_colors, edge_labels=edge_labels,
|
|
899
|
+
*args, **kwds)
|
|
900
|
+
|
|
901
|
+
def show(self, word_labels=None, *args, **kwds):
|
|
902
|
+
r"""
|
|
903
|
+
Display the output of :meth:`plot`.
|
|
904
|
+
|
|
905
|
+
INPUT:
|
|
906
|
+
|
|
907
|
+
- ``word_labels`` -- (default: ``None``) if ``False``, labels the edges
|
|
908
|
+
by pairs `(i, j)`; if ``True``, labels the edges by ``word[i:j]``
|
|
909
|
+
|
|
910
|
+
EXAMPLES::
|
|
911
|
+
|
|
912
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
913
|
+
sage: w = Words("cao")("cacao")
|
|
914
|
+
sage: t = ImplicitSuffixTree(w)
|
|
915
|
+
sage: t.show(word_labels=True) # needs sage.plot
|
|
916
|
+
sage: t.show(word_labels=False) # needs sage.plot
|
|
917
|
+
"""
|
|
918
|
+
self.plot(word_labels=word_labels, *args, **kwds).show()
|
|
919
|
+
|
|
920
|
+
# ---------------
|
|
921
|
+
# Various methods
|
|
922
|
+
# ---------------
|
|
923
|
+
|
|
924
|
+
def __eq__(self, other) -> bool:
|
|
925
|
+
r"""
|
|
926
|
+
If ``self`` and ``other`` have the same transition function
|
|
927
|
+
and the same word, then they are equal.
|
|
928
|
+
|
|
929
|
+
TESTS::
|
|
930
|
+
|
|
931
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
932
|
+
sage: w = Words([0,1,2])([0,1,0,1,2])
|
|
933
|
+
sage: u = Words([0,1,2])(iter([0,1,0,1,2]))[:5]
|
|
934
|
+
sage: ImplicitSuffixTree(w) == ImplicitSuffixTree(u)
|
|
935
|
+
True
|
|
936
|
+
"""
|
|
937
|
+
if not isinstance(other, ImplicitSuffixTree):
|
|
938
|
+
return False
|
|
939
|
+
return self._transition_function == other._transition_function \
|
|
940
|
+
and self._letters == other._letters
|
|
941
|
+
|
|
942
|
+
def transition_function(self, word, node=0):
|
|
943
|
+
r"""
|
|
944
|
+
Return the node obtained by starting from ``node`` and following the
|
|
945
|
+
edges labelled by the letters of ``word``.
|
|
946
|
+
|
|
947
|
+
OUTPUT:
|
|
948
|
+
|
|
949
|
+
``("explicit", end_node)`` if we end at ``end_node``, or
|
|
950
|
+
``("implicit", edge, d)`` if we end `d` spots along an edge.
|
|
951
|
+
|
|
952
|
+
INPUT:
|
|
953
|
+
|
|
954
|
+
- ``word`` -- a word
|
|
955
|
+
- ``node`` -- (default: 0) starting node
|
|
956
|
+
|
|
957
|
+
EXAMPLES::
|
|
958
|
+
|
|
959
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
960
|
+
sage: W = Words([0,1,2])
|
|
961
|
+
sage: t = ImplicitSuffixTree(W([0,1,0,1,2]))
|
|
962
|
+
sage: t.transition_function(W([0,1,0]))
|
|
963
|
+
('implicit', (3, 1), 1)
|
|
964
|
+
sage: t.transition_function(W([0,1,2]))
|
|
965
|
+
('explicit', 4)
|
|
966
|
+
sage: t.transition_function(W([0,1,2]), 5)
|
|
967
|
+
('explicit', 2)
|
|
968
|
+
sage: t.transition_function(W([0,1]), 5)
|
|
969
|
+
('implicit', (5, 2), 2)
|
|
970
|
+
"""
|
|
971
|
+
if word.is_empty():
|
|
972
|
+
return "explicit", node
|
|
973
|
+
(k, p), s = self._find_transition(node, word[0])
|
|
974
|
+
if p is None:
|
|
975
|
+
# test that word is a prefix of self._letters[k-1:]
|
|
976
|
+
if word == self._word[k-1:(k-1)+word.length()]:
|
|
977
|
+
if word.length() == len(self._letters) - k + 1:
|
|
978
|
+
return "explicit", s
|
|
979
|
+
else:
|
|
980
|
+
edge = (node, s)
|
|
981
|
+
return "implicit", edge, word.length()
|
|
982
|
+
else:
|
|
983
|
+
# find longest common prefix
|
|
984
|
+
m = min(p-k+1, word.length())
|
|
985
|
+
i = 0
|
|
986
|
+
while i < m and self._word[k-1+i] == word[i]:
|
|
987
|
+
i += 1
|
|
988
|
+
if i == p-k+1:
|
|
989
|
+
return self.transition_function(word[p-k+1:], s)
|
|
990
|
+
else:
|
|
991
|
+
edge = (node, s)
|
|
992
|
+
return "implicit", edge, i
|
|
993
|
+
return "explicit", node
|
|
994
|
+
|
|
995
|
+
def states(self) -> list:
|
|
996
|
+
r"""
|
|
997
|
+
Return the states (explicit nodes) of the suffix tree.
|
|
998
|
+
|
|
999
|
+
EXAMPLES::
|
|
1000
|
+
|
|
1001
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
1002
|
+
sage: W = Words([0,1,2])
|
|
1003
|
+
sage: t = ImplicitSuffixTree(W([0,1,0,1,2]))
|
|
1004
|
+
sage: t.states()
|
|
1005
|
+
[0, 1, 2, 3, 4, 5, 6, 7]
|
|
1006
|
+
"""
|
|
1007
|
+
return list(range(len(self._transition_function)))
|
|
1008
|
+
|
|
1009
|
+
def suffix_link(self, state):
|
|
1010
|
+
r"""
|
|
1011
|
+
Evaluate the suffix link map of the implicit suffix tree on ``state``.
|
|
1012
|
+
|
|
1013
|
+
Note that the suffix link is not defined for all states.
|
|
1014
|
+
|
|
1015
|
+
The suffix link of a state `x'` that corresponds to the suffix `x` is
|
|
1016
|
+
defined to be -1 is `x'` is the root (0) and `y'` otherwise, where `y'`
|
|
1017
|
+
is the state corresponding to the suffix ``x[1:]``.
|
|
1018
|
+
|
|
1019
|
+
INPUT:
|
|
1020
|
+
|
|
1021
|
+
- ``state`` -- a state
|
|
1022
|
+
|
|
1023
|
+
EXAMPLES::
|
|
1024
|
+
|
|
1025
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
1026
|
+
sage: W = Words([0,1,2])
|
|
1027
|
+
sage: t = ImplicitSuffixTree(W([0,1,0,1,2]))
|
|
1028
|
+
sage: t.suffix_link(3)
|
|
1029
|
+
5
|
|
1030
|
+
sage: t.suffix_link(5)
|
|
1031
|
+
0
|
|
1032
|
+
sage: t.suffix_link(0)
|
|
1033
|
+
-1
|
|
1034
|
+
sage: t.suffix_link(-1)
|
|
1035
|
+
Traceback (most recent call last):
|
|
1036
|
+
...
|
|
1037
|
+
TypeError: there is no suffix link from -1
|
|
1038
|
+
"""
|
|
1039
|
+
if state in self._suffix_link:
|
|
1040
|
+
return self._suffix_link[state]
|
|
1041
|
+
raise TypeError("there is no suffix link from %s" % state)
|
|
1042
|
+
|
|
1043
|
+
def active_state(self):
|
|
1044
|
+
r"""
|
|
1045
|
+
Return the active state of the suffix tree.
|
|
1046
|
+
|
|
1047
|
+
EXAMPLES::
|
|
1048
|
+
|
|
1049
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
1050
|
+
sage: W = Words([0,1,2])
|
|
1051
|
+
sage: t = ImplicitSuffixTree(W([0,1,0,1,2]))
|
|
1052
|
+
sage: t.active_state()
|
|
1053
|
+
(0, (6, 6))
|
|
1054
|
+
"""
|
|
1055
|
+
return self._active_state
|
|
1056
|
+
|
|
1057
|
+
def process_letter(self, letter):
|
|
1058
|
+
r"""
|
|
1059
|
+
Modify the current implicit suffix tree producing the implicit
|
|
1060
|
+
suffix tree for ``self.word() + letter``.
|
|
1061
|
+
|
|
1062
|
+
EXAMPLES::
|
|
1063
|
+
|
|
1064
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
1065
|
+
sage: w = Words("aco")("cacao")
|
|
1066
|
+
sage: t = ImplicitSuffixTree(w[:-1]); t
|
|
1067
|
+
Implicit Suffix Tree of the word: caca
|
|
1068
|
+
sage: t.process_letter(w[-1]); t
|
|
1069
|
+
Implicit Suffix Tree of the word: cacao
|
|
1070
|
+
|
|
1071
|
+
::
|
|
1072
|
+
|
|
1073
|
+
sage: W = Words([0,1])
|
|
1074
|
+
sage: s = ImplicitSuffixTree(W([0,1,0,1])); s
|
|
1075
|
+
Implicit Suffix Tree of the word: 0101
|
|
1076
|
+
sage: s.process_letter(W([1])[0]); s
|
|
1077
|
+
Implicit Suffix Tree of the word: 01011
|
|
1078
|
+
"""
|
|
1079
|
+
self._word = self._word * self._word._parent([letter])
|
|
1080
|
+
self._letters.append(letter)
|
|
1081
|
+
self._process_letter(letter)
|
|
1082
|
+
|
|
1083
|
+
def to_explicit_suffix_tree(self):
|
|
1084
|
+
r"""
|
|
1085
|
+
Convert ``self`` to an explicit suffix tree.
|
|
1086
|
+
|
|
1087
|
+
It is obtained by processing an end of string letter as if it
|
|
1088
|
+
were a regular letter, except that no new leaf nodes are
|
|
1089
|
+
created (thus, the only thing that happens is that some
|
|
1090
|
+
implicit nodes become explicit).
|
|
1091
|
+
|
|
1092
|
+
EXAMPLES::
|
|
1093
|
+
|
|
1094
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
1095
|
+
sage: w = Words("aco")("cacao")
|
|
1096
|
+
sage: t = ImplicitSuffixTree(w)
|
|
1097
|
+
sage: t.to_explicit_suffix_tree()
|
|
1098
|
+
|
|
1099
|
+
::
|
|
1100
|
+
|
|
1101
|
+
sage: W = Words([0,1])
|
|
1102
|
+
sage: s = ImplicitSuffixTree(W([0,1,0,1,1]))
|
|
1103
|
+
sage: s.to_explicit_suffix_tree()
|
|
1104
|
+
"""
|
|
1105
|
+
# append a new unique symbol to the word and process the new letter
|
|
1106
|
+
end_of_string = object()
|
|
1107
|
+
self._letters.append(end_of_string)
|
|
1108
|
+
s, (k, i) = self._active_state
|
|
1109
|
+
end_state, r = self._test_and_split(s, (k, i-1), end_of_string)
|
|
1110
|
+
while not end_state:
|
|
1111
|
+
s, k = self._canonize(self._suffix_link[s], (k, i-1))
|
|
1112
|
+
end_state, r = self._test_and_split(s, (k, i-1), end_of_string)
|
|
1113
|
+
# remove the end of string symbol from the word
|
|
1114
|
+
self._letters.pop()
|
|
1115
|
+
|
|
1116
|
+
def edge_iterator(self):
|
|
1117
|
+
r"""
|
|
1118
|
+
Return an iterator over the edges of the suffix tree.
|
|
1119
|
+
|
|
1120
|
+
The edge from `u` to `v` labelled by `(i,j)` is yielded as
|
|
1121
|
+
the tuple `(u,v,(i,j))`.
|
|
1122
|
+
|
|
1123
|
+
EXAMPLES::
|
|
1124
|
+
|
|
1125
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
1126
|
+
sage: sorted( ImplicitSuffixTree(Word("aaaaa")).edge_iterator() )
|
|
1127
|
+
[(0, 1, (0, None))]
|
|
1128
|
+
sage: sorted( ImplicitSuffixTree(Word([0,1,0,1])).edge_iterator() )
|
|
1129
|
+
[(0, 1, (0, None)), (0, 2, (1, None))]
|
|
1130
|
+
sage: sorted( ImplicitSuffixTree(Word()).edge_iterator() )
|
|
1131
|
+
[]
|
|
1132
|
+
"""
|
|
1133
|
+
queue = [0]
|
|
1134
|
+
while queue:
|
|
1135
|
+
v = queue.pop()
|
|
1136
|
+
for (i, j), u in self._transition_function[v].items():
|
|
1137
|
+
yield (v, u, (i - 1, j))
|
|
1138
|
+
queue.append(u)
|
|
1139
|
+
|
|
1140
|
+
def number_of_factors(self, n=None):
|
|
1141
|
+
r"""
|
|
1142
|
+
Count the number of distinct factors of ``self.word()``.
|
|
1143
|
+
|
|
1144
|
+
INPUT:
|
|
1145
|
+
|
|
1146
|
+
- ``n`` -- integer or ``None``
|
|
1147
|
+
|
|
1148
|
+
OUTPUT:
|
|
1149
|
+
|
|
1150
|
+
If ``n`` is an integer, returns the number of distinct factors
|
|
1151
|
+
of length ``n``. If ``n`` is ``None``, returns the total number of
|
|
1152
|
+
distinct factors.
|
|
1153
|
+
|
|
1154
|
+
EXAMPLES::
|
|
1155
|
+
|
|
1156
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
1157
|
+
sage: t = ImplicitSuffixTree(Word([1,2,1,3,1,2,1]))
|
|
1158
|
+
sage: t.number_of_factors()
|
|
1159
|
+
22
|
|
1160
|
+
sage: t.number_of_factors(1)
|
|
1161
|
+
3
|
|
1162
|
+
sage: t.number_of_factors(9)
|
|
1163
|
+
0
|
|
1164
|
+
sage: t.number_of_factors(0)
|
|
1165
|
+
1
|
|
1166
|
+
|
|
1167
|
+
::
|
|
1168
|
+
|
|
1169
|
+
sage: t = ImplicitSuffixTree(Word("cacao"))
|
|
1170
|
+
sage: t.number_of_factors()
|
|
1171
|
+
13
|
|
1172
|
+
sage: list(map(t.number_of_factors, range(10)))
|
|
1173
|
+
[1, 3, 3, 3, 2, 1, 0, 0, 0, 0]
|
|
1174
|
+
|
|
1175
|
+
::
|
|
1176
|
+
|
|
1177
|
+
sage: t = ImplicitSuffixTree(Word("c"*1000))
|
|
1178
|
+
sage: t.number_of_factors()
|
|
1179
|
+
1001
|
|
1180
|
+
sage: t.number_of_factors(17)
|
|
1181
|
+
1
|
|
1182
|
+
sage: t.number_of_factors(0)
|
|
1183
|
+
1
|
|
1184
|
+
|
|
1185
|
+
::
|
|
1186
|
+
|
|
1187
|
+
sage: ImplicitSuffixTree(Word()).number_of_factors()
|
|
1188
|
+
1
|
|
1189
|
+
|
|
1190
|
+
::
|
|
1191
|
+
|
|
1192
|
+
sage: blueberry = ImplicitSuffixTree(Word("blueberry"))
|
|
1193
|
+
sage: blueberry.number_of_factors()
|
|
1194
|
+
43
|
|
1195
|
+
sage: list(map(blueberry.number_of_factors, range(10)))
|
|
1196
|
+
[1, 6, 8, 7, 6, 5, 4, 3, 2, 1]
|
|
1197
|
+
"""
|
|
1198
|
+
if n is None:
|
|
1199
|
+
length_word = self.word().length()
|
|
1200
|
+
num_factors = 1 # empty word
|
|
1201
|
+
for u, v, (i, j) in self.edge_iterator():
|
|
1202
|
+
if j is None:
|
|
1203
|
+
num_factors += length_word - i
|
|
1204
|
+
else:
|
|
1205
|
+
num_factors += j - i
|
|
1206
|
+
elif isinstance(n, (int, Integer)):
|
|
1207
|
+
num_factors = 0
|
|
1208
|
+
queue = [(0, 0)]
|
|
1209
|
+
while queue:
|
|
1210
|
+
v, l = queue.pop()
|
|
1211
|
+
if l == n:
|
|
1212
|
+
num_factors += 1
|
|
1213
|
+
if l < n:
|
|
1214
|
+
if self._transition_function[v]:
|
|
1215
|
+
for (i, j), u in self._transition_function[v].items():
|
|
1216
|
+
if j is None:
|
|
1217
|
+
j = self.word().length()
|
|
1218
|
+
if j - i >= n - l:
|
|
1219
|
+
num_factors += 1
|
|
1220
|
+
else:
|
|
1221
|
+
queue.append((u, l + j - i + 1))
|
|
1222
|
+
else:
|
|
1223
|
+
raise TypeError("not an integer or None: %s" % n)
|
|
1224
|
+
return num_factors
|
|
1225
|
+
|
|
1226
|
+
def factor_iterator(self, n=None):
|
|
1227
|
+
r"""
|
|
1228
|
+
Generate distinct factors of ``self``.
|
|
1229
|
+
|
|
1230
|
+
INPUT:
|
|
1231
|
+
|
|
1232
|
+
- ``n`` -- integer or ``None``
|
|
1233
|
+
|
|
1234
|
+
OUTPUT:
|
|
1235
|
+
|
|
1236
|
+
If ``n`` is an integer, returns an iterator over all distinct
|
|
1237
|
+
factors of length ``n``. If ``n`` is ``None``, returns an iterator
|
|
1238
|
+
generating all distinct factors.
|
|
1239
|
+
|
|
1240
|
+
EXAMPLES::
|
|
1241
|
+
|
|
1242
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree
|
|
1243
|
+
sage: sorted( ImplicitSuffixTree(Word("cacao")).factor_iterator() )
|
|
1244
|
+
[word: , word: a, word: ac, word: aca, word: acao, word: ao, word: c, word: ca, word: cac, word: caca, word: cacao, word: cao, word: o]
|
|
1245
|
+
sage: sorted( ImplicitSuffixTree(Word("cacao")).factor_iterator(1) )
|
|
1246
|
+
[word: a, word: c, word: o]
|
|
1247
|
+
sage: sorted( ImplicitSuffixTree(Word("cacao")).factor_iterator(2) )
|
|
1248
|
+
[word: ac, word: ao, word: ca]
|
|
1249
|
+
sage: sorted( ImplicitSuffixTree(Word([0,0,0])).factor_iterator() )
|
|
1250
|
+
[word: , word: 0, word: 00, word: 000]
|
|
1251
|
+
sage: sorted( ImplicitSuffixTree(Word([0,0,0])).factor_iterator(2) )
|
|
1252
|
+
[word: 00]
|
|
1253
|
+
sage: sorted( ImplicitSuffixTree(Word([0,0,0])).factor_iterator(0) )
|
|
1254
|
+
[word: ]
|
|
1255
|
+
sage: sorted( ImplicitSuffixTree(Word()).factor_iterator() )
|
|
1256
|
+
[word: ]
|
|
1257
|
+
sage: sorted( ImplicitSuffixTree(Word()).factor_iterator(2) )
|
|
1258
|
+
[]
|
|
1259
|
+
"""
|
|
1260
|
+
# Every factor is a prefix of a suffix, so we do a depth
|
|
1261
|
+
# first search of the implicit suffix tree of the word.
|
|
1262
|
+
w = self.word()
|
|
1263
|
+
wlen = self.word().length()
|
|
1264
|
+
if n is None:
|
|
1265
|
+
queue = [(0, 0, -1, 0)]
|
|
1266
|
+
yield w[0:0]
|
|
1267
|
+
while queue:
|
|
1268
|
+
v, i, j, l = queue.pop()
|
|
1269
|
+
for k in range(i, j+1):
|
|
1270
|
+
yield w[j-l:k]
|
|
1271
|
+
for (i, j), u in self._transition_function[v].items():
|
|
1272
|
+
if j is None:
|
|
1273
|
+
j = wlen
|
|
1274
|
+
queue.append((u, i, j, l+j-i+1))
|
|
1275
|
+
elif isinstance(n, (int, Integer)):
|
|
1276
|
+
queue = [(0, 0, -1, 0)]
|
|
1277
|
+
while queue:
|
|
1278
|
+
v, i, j, l = queue.pop()
|
|
1279
|
+
if l == n:
|
|
1280
|
+
yield w[j-l:j]
|
|
1281
|
+
if l < n:
|
|
1282
|
+
for (i, j), u in self._transition_function[v].items():
|
|
1283
|
+
if j is None:
|
|
1284
|
+
j = wlen
|
|
1285
|
+
if j - i >= n - l:
|
|
1286
|
+
yield w[i-l-1:i-l+n-1]
|
|
1287
|
+
else:
|
|
1288
|
+
queue.append((u, i, j, l+j-i+1))
|
|
1289
|
+
else:
|
|
1290
|
+
raise TypeError("not an integer or None: %s" % n)
|
|
1291
|
+
|
|
1292
|
+
def LZ_decomposition(self):
|
|
1293
|
+
r"""
|
|
1294
|
+
Return a list of index of the beginning of the block of the Lempel-Ziv
|
|
1295
|
+
decomposition of ``self.word``
|
|
1296
|
+
|
|
1297
|
+
The *Lempel-Ziv decomposition* is the factorisation `u_1...u_k` of a
|
|
1298
|
+
word `w=x_1...x_n` such that `u_i` is the longest prefix of `u_i...u_k`
|
|
1299
|
+
that has an occurrence starting before `u_i` or a letter if this prefix
|
|
1300
|
+
is empty.
|
|
1301
|
+
|
|
1302
|
+
OUTPUT:
|
|
1303
|
+
|
|
1304
|
+
Return a list ``iB`` of index such that the blocks of the decomposition
|
|
1305
|
+
are ``self.word()[iB[k]:iB[k+1]]``
|
|
1306
|
+
|
|
1307
|
+
EXAMPLES::
|
|
1308
|
+
|
|
1309
|
+
sage: w = Word('abababb')
|
|
1310
|
+
sage: T = w.suffix_tree()
|
|
1311
|
+
sage: T.LZ_decomposition()
|
|
1312
|
+
[0, 1, 2, 6, 7]
|
|
1313
|
+
sage: w = Word('abaababacabba')
|
|
1314
|
+
sage: T = w.suffix_tree()
|
|
1315
|
+
sage: T.LZ_decomposition()
|
|
1316
|
+
[0, 1, 2, 3, 6, 8, 9, 11, 13]
|
|
1317
|
+
sage: w = Word([0, 0, 0, 1, 1, 0, 1])
|
|
1318
|
+
sage: T = w.suffix_tree()
|
|
1319
|
+
sage: T.LZ_decomposition()
|
|
1320
|
+
[0, 1, 3, 4, 5, 7]
|
|
1321
|
+
sage: w = Word('0000100101')
|
|
1322
|
+
sage: T = w.suffix_tree()
|
|
1323
|
+
sage: T.LZ_decomposition()
|
|
1324
|
+
[0, 1, 4, 5, 9, 10]
|
|
1325
|
+
"""
|
|
1326
|
+
iB = [0]
|
|
1327
|
+
i = 0
|
|
1328
|
+
w = self.word()
|
|
1329
|
+
while i < len(w):
|
|
1330
|
+
l = 0
|
|
1331
|
+
(x, y), successor = self._find_transition(0, w[i])
|
|
1332
|
+
x = x-1
|
|
1333
|
+
while x < i+l:
|
|
1334
|
+
if y is None:
|
|
1335
|
+
l = len(w)-i
|
|
1336
|
+
else:
|
|
1337
|
+
l += y-x
|
|
1338
|
+
if i+l >= len(w):
|
|
1339
|
+
l = len(w)-i
|
|
1340
|
+
break
|
|
1341
|
+
(x, y), successor = self._find_transition(successor, w[i+l])
|
|
1342
|
+
x = x-1
|
|
1343
|
+
i += max(1, l)
|
|
1344
|
+
iB.append(i)
|
|
1345
|
+
return iB
|
|
1346
|
+
|
|
1347
|
+
def _count_and_skip(self, node, i, j):
|
|
1348
|
+
r"""
|
|
1349
|
+
Use count and skip trick to follow the path starting at ``node`` and
|
|
1350
|
+
reading ``self.word()[i:j]``. We assume that reading
|
|
1351
|
+
``self.word()[i:j]`` is possible from ``node``
|
|
1352
|
+
|
|
1353
|
+
INPUT:
|
|
1354
|
+
|
|
1355
|
+
- ``node`` -- explicit node of ``self``
|
|
1356
|
+
- ``i`` -- beginning of factor ``T.word()[i:j]``
|
|
1357
|
+
- ``j`` -- end of factor ``T.word()[i:j]``
|
|
1358
|
+
|
|
1359
|
+
OUTPUT:
|
|
1360
|
+
|
|
1361
|
+
The node obtained by starting at ``node`` and following the edges
|
|
1362
|
+
labeled by the letters of ``T.word()[i:j]``.
|
|
1363
|
+
Return ``("explicit", end_node)`` if w ends at the node ``end_node``,
|
|
1364
|
+
and ``("implicit", edge, d)`` if it ends after reading ``d`` letters along
|
|
1365
|
+
the edge ``edge``.
|
|
1366
|
+
|
|
1367
|
+
EXAMPLES::
|
|
1368
|
+
|
|
1369
|
+
sage: T = Word('00110111011').suffix_tree()
|
|
1370
|
+
sage: T._count_and_skip(5, 2, 5)
|
|
1371
|
+
('implicit', (9, 10), 2)
|
|
1372
|
+
sage: T._count_and_skip(0, 1, 4)
|
|
1373
|
+
('explicit', 7)
|
|
1374
|
+
sage: T._count_and_skip(0, 8, 10)
|
|
1375
|
+
('implicit', (2, 7), 1)
|
|
1376
|
+
sage: T = Word('cacao').suffix_tree()
|
|
1377
|
+
sage: T._count_and_skip(3, 2, 5)
|
|
1378
|
+
('explicit', 1)
|
|
1379
|
+
"""
|
|
1380
|
+
trans = self._find_transition(node, self._letters[i])
|
|
1381
|
+
while trans[0][1] is not None and trans[0][1] - trans[0][0] + 1 <= j - i:
|
|
1382
|
+
node = trans[1]
|
|
1383
|
+
i += trans[0][1] - trans[0][0] + 1
|
|
1384
|
+
if i == j:
|
|
1385
|
+
return ('explicit', node)
|
|
1386
|
+
else:
|
|
1387
|
+
trans = self._find_transition(node, self._letters[i])
|
|
1388
|
+
if trans[0][1] is None and len(self.word()) - trans[0][0] + 1 <= j - i:
|
|
1389
|
+
return ('explicit', trans[1])
|
|
1390
|
+
else:
|
|
1391
|
+
return ('implicit', (node, trans[1]), j - i)
|
|
1392
|
+
|
|
1393
|
+
def suffix_walk(self, edge, l):
|
|
1394
|
+
r"""
|
|
1395
|
+
Return the state of "w" if the input state is "aw".
|
|
1396
|
+
|
|
1397
|
+
If the input state ``(edge, l)`` is path labeled "aw" with "a" a letter, the output is
|
|
1398
|
+
the state which is path labeled "w".
|
|
1399
|
+
|
|
1400
|
+
INPUT:
|
|
1401
|
+
|
|
1402
|
+
- ``edge`` -- the edge containing the state
|
|
1403
|
+
- ``l`` -- the string-depth of the state on edge (``l``>0)
|
|
1404
|
+
|
|
1405
|
+
OUTPUT:
|
|
1406
|
+
|
|
1407
|
+
Return ``("explicit", end_node)`` if the state of w is an explicit
|
|
1408
|
+
state and ``("implicit", edge, d)`` is obtained by reading ``d``
|
|
1409
|
+
letters on ``edge``.
|
|
1410
|
+
|
|
1411
|
+
EXAMPLES::
|
|
1412
|
+
|
|
1413
|
+
sage: T = Word('00110111011').suffix_tree()
|
|
1414
|
+
sage: T.suffix_walk((0, 5), 1)
|
|
1415
|
+
('explicit', 0)
|
|
1416
|
+
sage: T.suffix_walk((7, 3), 1)
|
|
1417
|
+
('implicit', (9, 4), 1)
|
|
1418
|
+
"""
|
|
1419
|
+
start, end = edge
|
|
1420
|
+
# Select the transition that corresponds to edge
|
|
1421
|
+
ij = next(ij for ij, target in self._transition_function[start].items()
|
|
1422
|
+
if target == end)
|
|
1423
|
+
|
|
1424
|
+
# self.word()[i-1:j] is the word on the edges
|
|
1425
|
+
i = ij[0] - 1
|
|
1426
|
+
parent = self.suffix_link(start)
|
|
1427
|
+
return self._count_and_skip(parent, i, i + l)
|
|
1428
|
+
|
|
1429
|
+
def leftmost_covering_set(self):
|
|
1430
|
+
r"""
|
|
1431
|
+
Compute the leftmost covering set of square pairs in ``self.word()``.
|
|
1432
|
+
Return a square as a pair ``(i,l)`` designating factor
|
|
1433
|
+
``self.word()[i:i+l]``.
|
|
1434
|
+
|
|
1435
|
+
A leftmost covering set is a set such that the leftmost occurrence
|
|
1436
|
+
`(j,l)` of a square in ``self.word()`` is covered by a pair
|
|
1437
|
+
`(i,l)` in the set for all types of squares. We say that `(j,l)` is
|
|
1438
|
+
covered by `(i,l)` if `(i,l)` (i+1,l), \ldots, (j,l)` are all
|
|
1439
|
+
squares.
|
|
1440
|
+
|
|
1441
|
+
The set is returned in the form of a list ``P`` such that ``P[i]``
|
|
1442
|
+
contains all the lengths of squares starting at ``i`` in the set.
|
|
1443
|
+
The lists ``P[i]`` are sorted in decreasing order.
|
|
1444
|
+
|
|
1445
|
+
The algorithm used is described in [DS2004]_.
|
|
1446
|
+
|
|
1447
|
+
EXAMPLES::
|
|
1448
|
+
|
|
1449
|
+
sage: w = Word('abaabaabbaaabaaba')
|
|
1450
|
+
sage: T = w.suffix_tree()
|
|
1451
|
+
sage: T.leftmost_covering_set()
|
|
1452
|
+
[[6], [6], [2], [], [], [], [], [2], [], [], [6, 2], [], [], [], [], [], []]
|
|
1453
|
+
sage: w = Word('abaca')
|
|
1454
|
+
sage: T = w.suffix_tree()
|
|
1455
|
+
sage: T.leftmost_covering_set()
|
|
1456
|
+
[[], [], [], [], []]
|
|
1457
|
+
sage: T = Word('aaaaa').suffix_tree()
|
|
1458
|
+
sage: T.leftmost_covering_set()
|
|
1459
|
+
[[4, 2], [], [], [], []]
|
|
1460
|
+
"""
|
|
1461
|
+
|
|
1462
|
+
def condition1_square_pairs(i):
|
|
1463
|
+
r"""
|
|
1464
|
+
Compute the squares that have their center (the last letter of the
|
|
1465
|
+
first occurrence of ``w`` in ``ww``) in the `i`-th block of the
|
|
1466
|
+
LZ-decomposition and that start in the `i`-th block and end in the
|
|
1467
|
+
`(i+1)`-th.
|
|
1468
|
+
"""
|
|
1469
|
+
for k in range(1, B[i+1]-B[i]+1):
|
|
1470
|
+
q = B[i+1]-k
|
|
1471
|
+
k1 = w.longest_forward_extension(B[i+1], q) if B[i+1] < len(w) else 0
|
|
1472
|
+
k2 = w.longest_backward_extension(B[i+1]-1, q-1) if q > 0 else 0
|
|
1473
|
+
start = max(q-k2, q-k+1)
|
|
1474
|
+
if k1+k2 >= k and k1 > 0 and start >= B[i]:
|
|
1475
|
+
yield (start, 2*k)
|
|
1476
|
+
|
|
1477
|
+
def condition2_square_pairs(i):
|
|
1478
|
+
r"""
|
|
1479
|
+
Compute the squares that have their center (the last letter of the
|
|
1480
|
+
first occurrence of ``w`` in ``ww``) in the `i`-th block of the
|
|
1481
|
+
LZ-decomposition and that starts in the `(i-1)`-th block or before.
|
|
1482
|
+
Their end is either in the `i`-th or the `(i+1)`-th block.
|
|
1483
|
+
"""
|
|
1484
|
+
if i+2 < len(B):
|
|
1485
|
+
end = B[i+2] - B[i] + 1
|
|
1486
|
+
else:
|
|
1487
|
+
end = B[i+1] - B[i] + 1
|
|
1488
|
+
for k in range(2, end):
|
|
1489
|
+
q = B[i] + k
|
|
1490
|
+
k1 = w.longest_forward_extension(B[i], q) if q < len(w) else 0
|
|
1491
|
+
k2 = w.longest_backward_extension(B[i]-1, q-1) if B[i] > 0 else 0
|
|
1492
|
+
start = max(B[i]-k2, B[i]-k+1)
|
|
1493
|
+
if k1+k2 >= k and k1 > 0 and start+k <= B[i+1] and k2 > 0:
|
|
1494
|
+
yield (start, 2*k)
|
|
1495
|
+
|
|
1496
|
+
w = self.word()
|
|
1497
|
+
B = self.LZ_decomposition()
|
|
1498
|
+
P = [[] for _ in w]
|
|
1499
|
+
for i in range(len(B) - 1):
|
|
1500
|
+
for i, l in chain(condition2_square_pairs(i), condition1_square_pairs(i)):
|
|
1501
|
+
P[i].append(l)
|
|
1502
|
+
for l in P:
|
|
1503
|
+
l.reverse()
|
|
1504
|
+
return P
|
|
1505
|
+
|
|
1506
|
+
# ---------------------
|
|
1507
|
+
# Miscellaneous methods
|
|
1508
|
+
# ---------------------
|
|
1509
|
+
|
|
1510
|
+
def uncompactify(self):
|
|
1511
|
+
r"""
|
|
1512
|
+
Return the tree obtained from ``self`` by splitting edges so that they
|
|
1513
|
+
are labelled by exactly one letter.
|
|
1514
|
+
|
|
1515
|
+
The resulting tree is isomorphic to the suffix trie.
|
|
1516
|
+
|
|
1517
|
+
EXAMPLES::
|
|
1518
|
+
|
|
1519
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree, SuffixTrie
|
|
1520
|
+
sage: abbab = Words("ab")("abbab")
|
|
1521
|
+
sage: s = SuffixTrie(abbab)
|
|
1522
|
+
sage: t = ImplicitSuffixTree(abbab)
|
|
1523
|
+
sage: t.uncompactify().is_isomorphic(s.to_digraph()) # needs sage.graphs
|
|
1524
|
+
True
|
|
1525
|
+
"""
|
|
1526
|
+
tree = self.to_digraph(word_labels=True)
|
|
1527
|
+
newtree = DiGraph()
|
|
1528
|
+
newtree.add_vertices(range(tree.order()))
|
|
1529
|
+
new_node = tree.order() + 1
|
|
1530
|
+
for u, v, label in tree.edge_iterator():
|
|
1531
|
+
if len(label) == 1:
|
|
1532
|
+
newtree.add_edge(u, v)
|
|
1533
|
+
else:
|
|
1534
|
+
newtree.add_edge(u, new_node, label[0])
|
|
1535
|
+
for w in label[1:-1]:
|
|
1536
|
+
newtree.add_edge(new_node, new_node+1, w)
|
|
1537
|
+
new_node += 1
|
|
1538
|
+
newtree.add_edge(new_node, v, label[-1])
|
|
1539
|
+
new_node += 1
|
|
1540
|
+
return newtree
|
|
1541
|
+
|
|
1542
|
+
def trie_type_dict(self):
|
|
1543
|
+
r"""
|
|
1544
|
+
Return a dictionary in a format compatible with that of the suffix
|
|
1545
|
+
trie transition function.
|
|
1546
|
+
|
|
1547
|
+
EXAMPLES::
|
|
1548
|
+
|
|
1549
|
+
sage: from sage.combinat.words.suffix_trees import ImplicitSuffixTree, SuffixTrie
|
|
1550
|
+
sage: W = Words("ab")
|
|
1551
|
+
sage: t = ImplicitSuffixTree(W("aba"))
|
|
1552
|
+
sage: d = t.trie_type_dict()
|
|
1553
|
+
sage: len(d)
|
|
1554
|
+
5
|
|
1555
|
+
sage: d # random
|
|
1556
|
+
{(4, word: b): 5, (0, word: a): 4, (0, word: b): 3, (5, word: a): 1, (3, word: a): 2}
|
|
1557
|
+
"""
|
|
1558
|
+
d = {}
|
|
1559
|
+
new_node = len(self._transition_function)
|
|
1560
|
+
for u, dd in self._transition_function.items():
|
|
1561
|
+
for sl, v in dd.items():
|
|
1562
|
+
w = self._word[sl[0]-1:sl[1]]
|
|
1563
|
+
if w.length() == 1:
|
|
1564
|
+
d[u, w] = v
|
|
1565
|
+
else:
|
|
1566
|
+
d[u, w[0:1]] = new_node
|
|
1567
|
+
for i in range(1, w.length()-1):
|
|
1568
|
+
d[new_node, w[i:i+1]] = new_node + 1
|
|
1569
|
+
new_node += 1
|
|
1570
|
+
d[new_node, w[-1:]] = v
|
|
1571
|
+
new_node += 1
|
|
1572
|
+
return d
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
# ---------------------
|
|
1576
|
+
# Decorated Suffix Tree
|
|
1577
|
+
# ---------------------
|
|
1578
|
+
|
|
1579
|
+
|
|
1580
|
+
class DecoratedSuffixTree(ImplicitSuffixTree):
|
|
1581
|
+
r"""
|
|
1582
|
+
The decorated suffix tree of a word.
|
|
1583
|
+
|
|
1584
|
+
A *decorated suffix tree* of a word `w` is the suffix tree of `w`
|
|
1585
|
+
marked with the end point of all squares in the `w`.
|
|
1586
|
+
|
|
1587
|
+
The symbol ``$`` is appended to ``w`` to ensure that each final
|
|
1588
|
+
state is a leaf of the suffix tree.
|
|
1589
|
+
|
|
1590
|
+
INPUT:
|
|
1591
|
+
|
|
1592
|
+
- ``w`` -- a finite word
|
|
1593
|
+
|
|
1594
|
+
EXAMPLES::
|
|
1595
|
+
|
|
1596
|
+
sage: from sage.combinat.words.suffix_trees import DecoratedSuffixTree
|
|
1597
|
+
sage: w = Word('0011001')
|
|
1598
|
+
sage: DecoratedSuffixTree(w)
|
|
1599
|
+
Decorated suffix tree of : 0011001$
|
|
1600
|
+
sage: w = Word('0011001', '01')
|
|
1601
|
+
sage: DecoratedSuffixTree(w)
|
|
1602
|
+
Decorated suffix tree of : 0011001$
|
|
1603
|
+
|
|
1604
|
+
ALGORITHM:
|
|
1605
|
+
|
|
1606
|
+
When using ``'pair'`` as output, the squares are retrieved in linear
|
|
1607
|
+
time. The algorithm is an implementation of the one proposed in
|
|
1608
|
+
[DS2004]_.
|
|
1609
|
+
"""
|
|
1610
|
+
def __init__(self, w):
|
|
1611
|
+
"""
|
|
1612
|
+
Initialize ``self``.
|
|
1613
|
+
|
|
1614
|
+
EXAMPLES::
|
|
1615
|
+
|
|
1616
|
+
sage: from sage.combinat.words.suffix_trees import DecoratedSuffixTree
|
|
1617
|
+
sage: w = Word('0011001')
|
|
1618
|
+
sage: DST = DecoratedSuffixTree(w)
|
|
1619
|
+
|
|
1620
|
+
We skip the ``_test_and_split`` test because it is not a test meant
|
|
1621
|
+
for the ``TestSuite``::
|
|
1622
|
+
|
|
1623
|
+
sage: TestSuite(DST).run(skip='_test_and_split')
|
|
1624
|
+
|
|
1625
|
+
Test that we do not allow ``'$'`` to appear in the word::
|
|
1626
|
+
|
|
1627
|
+
sage: w = Word('0011001$')
|
|
1628
|
+
sage: DecoratedSuffixTree(w)
|
|
1629
|
+
Traceback (most recent call last):
|
|
1630
|
+
...
|
|
1631
|
+
ValueError: the symbol '$' is reserved for this class
|
|
1632
|
+
"""
|
|
1633
|
+
if "$" in w:
|
|
1634
|
+
raise ValueError("the symbol '$' is reserved for this class")
|
|
1635
|
+
end_symbol = '$'
|
|
1636
|
+
w = Word(str(w) + end_symbol)
|
|
1637
|
+
ImplicitSuffixTree.__init__(self, w)
|
|
1638
|
+
self.labeling = self._complete_labeling()
|
|
1639
|
+
|
|
1640
|
+
def __repr__(self):
|
|
1641
|
+
"""
|
|
1642
|
+
Return a string representation of ``self``.
|
|
1643
|
+
|
|
1644
|
+
EXAMPLES::
|
|
1645
|
+
|
|
1646
|
+
sage: from sage.combinat.words.suffix_trees import DecoratedSuffixTree
|
|
1647
|
+
sage: w = Word('0011001')
|
|
1648
|
+
sage: t = DecoratedSuffixTree(w)
|
|
1649
|
+
sage: t.__repr__()
|
|
1650
|
+
'Decorated suffix tree of : 0011001$'
|
|
1651
|
+
"""
|
|
1652
|
+
w = self.word()
|
|
1653
|
+
if len(w) > 40:
|
|
1654
|
+
w = str(w[:40]) + '...'
|
|
1655
|
+
return f"Decorated suffix tree of : {w}"
|
|
1656
|
+
|
|
1657
|
+
def _partial_labeling(self):
|
|
1658
|
+
r"""
|
|
1659
|
+
Make a depth-first search in the suffix tree and mark some squares of a
|
|
1660
|
+
leftmost covering set of the tree.
|
|
1661
|
+
|
|
1662
|
+
This is used by :meth:`_complete_labeling`.
|
|
1663
|
+
|
|
1664
|
+
EXAMPLES::
|
|
1665
|
+
|
|
1666
|
+
sage: from sage.combinat.words.suffix_trees import DecoratedSuffixTree
|
|
1667
|
+
sage: w = Word('abaababbabba')
|
|
1668
|
+
sage: T = DecoratedSuffixTree(w)
|
|
1669
|
+
sage: T._partial_labeling()
|
|
1670
|
+
{(3, 4): [1], (5, 1): [3], (5, 6): [1], (11, 17): [1], (13, 8): [1], (15, 10): [2]}
|
|
1671
|
+
"""
|
|
1672
|
+
def node_processing(node, parent, head):
|
|
1673
|
+
r"""
|
|
1674
|
+
Marks points along the edge ``(parent, node)`` if the string depth
|
|
1675
|
+
of parent is smaller than the length of the square at the head of
|
|
1676
|
+
``P(node)``.
|
|
1677
|
+
Make it for all such square pairs and remove them from ``P(node)``.
|
|
1678
|
+
|
|
1679
|
+
INPUT:
|
|
1680
|
+
|
|
1681
|
+
- ``node`` -- a node of ``self``
|
|
1682
|
+
- ``parent`` -- the parent of a node in ``self``
|
|
1683
|
+
- ``head`` -- tuple indicating the head of the list ``P(node)``
|
|
1684
|
+
|
|
1685
|
+
OUTPUT: ``(i, pos)``, the new head of ``P(node)``
|
|
1686
|
+
"""
|
|
1687
|
+
i, pos = head
|
|
1688
|
+
pano = (parent, node)
|
|
1689
|
+
while pos < len(P[i]) and P[i][pos] > string_depth[parent]:
|
|
1690
|
+
label = P[i][pos] - string_depth[parent]
|
|
1691
|
+
if pano in labeling:
|
|
1692
|
+
labeling[pano].append(label)
|
|
1693
|
+
else:
|
|
1694
|
+
labeling[pano] = [label]
|
|
1695
|
+
pos += 1
|
|
1696
|
+
return (i, pos)
|
|
1697
|
+
|
|
1698
|
+
def treat_node(current_node, parent):
|
|
1699
|
+
r"""
|
|
1700
|
+
Proceed to a depth-first search in ``self``, counting the
|
|
1701
|
+
string_depth of each node and processing each node for marking.
|
|
1702
|
+
|
|
1703
|
+
To initiate the depth first search call ``self.treat_node(0,None)``
|
|
1704
|
+
|
|
1705
|
+
INPUT:
|
|
1706
|
+
|
|
1707
|
+
- ``current_node`` -- a node
|
|
1708
|
+
- ``parent`` -- parent of ``current_node`` in ``self``
|
|
1709
|
+
|
|
1710
|
+
OUTPUT:
|
|
1711
|
+
|
|
1712
|
+
The resulting list P(current_node) with current_node have been
|
|
1713
|
+
processed by ``node_processing``. The output is a pair ``(i,
|
|
1714
|
+
pos)`` such that ``P[i][pos:]`` is the list of current_node.
|
|
1715
|
+
"""
|
|
1716
|
+
|
|
1717
|
+
# Call recursively on children of current_node
|
|
1718
|
+
if current_node in D:
|
|
1719
|
+
node_list = (n, 0)
|
|
1720
|
+
for child in D[current_node]:
|
|
1721
|
+
i, j = D[current_node][child]
|
|
1722
|
+
if j is None:
|
|
1723
|
+
j = n
|
|
1724
|
+
string_depth[child] = string_depth[current_node] + j - i
|
|
1725
|
+
child_list = treat_node(child, current_node)
|
|
1726
|
+
if child_list[0] < node_list[0]:
|
|
1727
|
+
node_list = child_list
|
|
1728
|
+
else: # The node is a child
|
|
1729
|
+
node_list = (n - string_depth[current_node], 0)
|
|
1730
|
+
# Make treatment on current node head
|
|
1731
|
+
return node_processing(current_node, parent, node_list)
|
|
1732
|
+
|
|
1733
|
+
P = self.leftmost_covering_set()
|
|
1734
|
+
D = self.transition_function_dictionary()
|
|
1735
|
+
string_depth = {0: 0}
|
|
1736
|
+
n = len(self.word())
|
|
1737
|
+
labeling = {}
|
|
1738
|
+
treat_node(0, None)
|
|
1739
|
+
return labeling
|
|
1740
|
+
|
|
1741
|
+
def _complete_labeling(self):
|
|
1742
|
+
r"""
|
|
1743
|
+
Return a dictionary of edges of ``self``, with marked points for the end
|
|
1744
|
+
of each distinct squares that can be found in ``self.word()``.
|
|
1745
|
+
|
|
1746
|
+
EXAMPLES::
|
|
1747
|
+
|
|
1748
|
+
sage: from sage.combinat.words.suffix_trees import DecoratedSuffixTree
|
|
1749
|
+
sage: w = Word('aabbaaba')
|
|
1750
|
+
sage: DecoratedSuffixTree(w)._complete_labeling()
|
|
1751
|
+
{(2, 7): [1], (5, 4): [1]}
|
|
1752
|
+
"""
|
|
1753
|
+
|
|
1754
|
+
def walk_chain(u, v, l, start):
|
|
1755
|
+
r"""
|
|
1756
|
+
Execute a chain of suffix walk until a walk is unsuccessful or it
|
|
1757
|
+
got to a point already registered in ``QP``. Registers all visited
|
|
1758
|
+
point in ``Q``.
|
|
1759
|
+
|
|
1760
|
+
INPUT:
|
|
1761
|
+
|
|
1762
|
+
- ``(u, v)`` -- edge on which the point is registered
|
|
1763
|
+
- ``l`` -- depth of the registered point on (u,v)
|
|
1764
|
+
- ``start`` -- beginning of the squares registered by the label
|
|
1765
|
+
``(u, v), l``
|
|
1766
|
+
"""
|
|
1767
|
+
# Mark the point in labeling
|
|
1768
|
+
if (u, v) in labeling:
|
|
1769
|
+
labeling[(u, v)].append(l)
|
|
1770
|
+
else:
|
|
1771
|
+
labeling[(u, v)] = [l]
|
|
1772
|
+
# Make the walk
|
|
1773
|
+
final_state = self.suffix_walk((u, v), l)
|
|
1774
|
+
successful = False
|
|
1775
|
+
if final_state[0] == 'explicit':
|
|
1776
|
+
parent = final_state[1]
|
|
1777
|
+
transition = self._find_transition(parent, self._letters[start])
|
|
1778
|
+
if transition is not None:
|
|
1779
|
+
child = transition[1]
|
|
1780
|
+
successful = True
|
|
1781
|
+
depth = 1
|
|
1782
|
+
else:
|
|
1783
|
+
parent = final_state[1][0]
|
|
1784
|
+
child = final_state[1][1]
|
|
1785
|
+
depth = final_state[2]
|
|
1786
|
+
next_letter = self._letters[D[parent][child][0]+depth]
|
|
1787
|
+
if next_letter == self._letters[start]:
|
|
1788
|
+
successful = True
|
|
1789
|
+
depth += 1
|
|
1790
|
+
# If needed start a new walk
|
|
1791
|
+
if successful:
|
|
1792
|
+
if (parent, child) in prelabeling:
|
|
1793
|
+
if depth not in prelabeling[(parent, child)]:
|
|
1794
|
+
walk_chain(parent, child, depth, start+1)
|
|
1795
|
+
else:
|
|
1796
|
+
walk_chain(parent, child, depth, start+1)
|
|
1797
|
+
|
|
1798
|
+
def treat_node(current_node, i, j):
|
|
1799
|
+
r"""
|
|
1800
|
+
Execute a depth-first search on ``self`` and start a suffix walk
|
|
1801
|
+
for labeled points on each edges of T.
|
|
1802
|
+
|
|
1803
|
+
The function is recursive, call
|
|
1804
|
+
``treat_node(0,0,0)`` to initiate the search.
|
|
1805
|
+
|
|
1806
|
+
INPUT:
|
|
1807
|
+
|
|
1808
|
+
- ``current_node`` -- the node to treat
|
|
1809
|
+
- ``(i, j)`` -- pair of index such that the path from 0 to
|
|
1810
|
+
``current_node`` reads ``self.word()[i:j]``
|
|
1811
|
+
"""
|
|
1812
|
+
|
|
1813
|
+
if current_node in D:
|
|
1814
|
+
for child in D[current_node]:
|
|
1815
|
+
edge = (current_node, child)
|
|
1816
|
+
edge_label = D[edge[0]][edge[1]]
|
|
1817
|
+
treat_node(child, edge_label[0]-(j-i), edge_label[1])
|
|
1818
|
+
if (current_node, child) in prelabeling:
|
|
1819
|
+
for l in prelabeling[edge]:
|
|
1820
|
+
square_start = edge_label[0] - (j - i)
|
|
1821
|
+
walk_chain(current_node, child, l, square_start)
|
|
1822
|
+
|
|
1823
|
+
prelabeling = self._partial_labeling()
|
|
1824
|
+
labeling = {}
|
|
1825
|
+
D = self.transition_function_dictionary()
|
|
1826
|
+
treat_node(0, 0, 0)
|
|
1827
|
+
return labeling
|
|
1828
|
+
|
|
1829
|
+
def square_vocabulary(self, output='pair'):
|
|
1830
|
+
r"""
|
|
1831
|
+
Return the list of distinct squares of ``self.word``.
|
|
1832
|
+
|
|
1833
|
+
Two types of outputs are available `pair` and `word`. The algorithm
|
|
1834
|
+
is only truly linear if `output` is set to `pair`. A pair is a tuple
|
|
1835
|
+
`(i, l)` that indicates the factor ``self.word()[i:i+l]``.
|
|
1836
|
+
The option ``'word'`` return word objects.
|
|
1837
|
+
|
|
1838
|
+
INPUT:
|
|
1839
|
+
|
|
1840
|
+
- ``output`` -- (default: ``'pair'``) either ``'pair'`` or ``'word'``
|
|
1841
|
+
|
|
1842
|
+
EXAMPLES::
|
|
1843
|
+
|
|
1844
|
+
sage: from sage.combinat.words.suffix_trees import DecoratedSuffixTree
|
|
1845
|
+
sage: w = Word('aabb')
|
|
1846
|
+
sage: sorted(DecoratedSuffixTree(w).square_vocabulary())
|
|
1847
|
+
[(0, 0), (0, 2), (2, 2)]
|
|
1848
|
+
sage: w = Word('00110011010')
|
|
1849
|
+
sage: sorted(DecoratedSuffixTree(w).square_vocabulary(output='word'))
|
|
1850
|
+
[word: , word: 00, word: 00110011, word: 01100110, word: 1010, word: 11]
|
|
1851
|
+
"""
|
|
1852
|
+
def treat_node(current_node, i, j):
|
|
1853
|
+
if current_node in D:
|
|
1854
|
+
for child in D[current_node]:
|
|
1855
|
+
edge = (current_node, child)
|
|
1856
|
+
edge_label = (D[edge[0]][edge[1]])
|
|
1857
|
+
treat_node(child, edge_label[0]-(j-i), edge_label[1])
|
|
1858
|
+
if (current_node, child) in Q:
|
|
1859
|
+
for l in Q[(current_node, child)]:
|
|
1860
|
+
square_start = edge_label[0]-(j-i)
|
|
1861
|
+
pair = (square_start, edge_label[0]+l-square_start)
|
|
1862
|
+
squares.append(pair)
|
|
1863
|
+
|
|
1864
|
+
if output not in ["pair", "word"]:
|
|
1865
|
+
msg = f"output should be 'pair' or 'word'; got {output}"
|
|
1866
|
+
raise ValueError(msg)
|
|
1867
|
+
D = self.transition_function_dictionary()
|
|
1868
|
+
Q = self.labeling
|
|
1869
|
+
squares = [(0, 0)]
|
|
1870
|
+
treat_node(0, 0, 0)
|
|
1871
|
+
if output == "pair":
|
|
1872
|
+
return squares
|
|
1873
|
+
return [self.word()[i:i + l] for i, l in squares]
|