< Summary

Information
Class: MoreStructures.SuffixTrees.Builders.Ukkonen.IterationState
Assembly: MoreStructures
File(s): /home/runner/work/MoreStructures/MoreStructures/MoreStructures/SuffixTrees/Builders/Ukkonen/IterationState.cs
Line coverage
100%
Covered lines: 132
Uncovered lines: 0
Coverable lines: 132
Total lines: 423
Line coverage: 100%
Branch coverage
100%
Covered branches: 34
Total branches: 34
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/MoreStructures/MoreStructures/MoreStructures/SuffixTrees/Builders/Ukkonen/IterationState.cs

#LineLine coverage
 1namespace MoreStructures.SuffixTrees.Builders.Ukkonen;
 2
 3/// <summary>
 4/// An object which keeps the state of execution of the Ukkonen algorithm, and enforce its coherency.
 5/// </summary>
 6internal class IterationState
 7{
 8    /// <summary>
 9    /// Builds a fresh <see cref="IterationState"/> object, to execute the Ukkonen algorithm on the provided
 10    /// <paramref name="text"/>.
 11    /// </summary>
 12    /// <param name="text">The text, to build the Suffix Tree of.</param>
 13    /// <remarks>
 14    ///     <para id="algorithm">
 15    ///     ALGORITHM
 16    ///     <br/>
 17    ///     - Creates a <see cref="Root"/> object, which will be used as root for the entire execution of the
 18    ///       algorithm.
 19    ///       <br/>
 20    ///     - The <see cref="Root"/> node is also set as current <see cref="ActiveNode"/>, while
 21    ///       <see cref="ActiveEdgeStart"/> is set to a negative value and <see cref="ActiveLength"/> to 0 (as there
 22    ///       is no a valid <see cref="ActiveEdge"/> yet - it is going to be set when Rule 3 is triggered).
 23    ///       <br/>
 24    ///     - <see cref="Phase"/> is initially set to a negative value: the first phase has to be explicitely
 25    ///       started with <see cref="StartPhaseIncreasingRemainingAndGlobalEnd"/>.
 26    ///     </para>
 27    /// </remarks>
 5728    public IterationState(TextWithTerminator text)
 5729    {
 5730        NextLeafStart = 0;
 5731        NextNodeId = 0;
 32
 5733        Text = text;
 5734        Root = new(NextNodeId++, null, null, null); // The root is never a leaf and has no incoming edge
 35
 5736        Phase = -1;
 5737        RemainingSuffixes = 0;
 5738        PreviousInternalNodeInTheSamePhase = null;
 5739        GlobalEnd = new(-1);
 40
 5741        ActiveNode = Root;
 5742        ActiveEdgeStart = -1;
 5743        ActiveLength = 0;
 5744    }
 45
 46    #region General readonly properties
 47
 48    /// <summary>
 49    /// The <see cref="TextWithTerminator"/> to build the <see cref="SuffixTreeNode"/> structure of.
 50    /// </summary>
 669651    public TextWithTerminator Text { get; }
 52
 53    /// <summary>
 54    /// The root node of the Suffix Tree, temporarily used during tree construction.
 55    /// </summary>
 126556    public MutableNode Root { get; }
 57
 58    #endregion
 59
 60    #region Cursors
 61
 62    /// <summary>
 63    /// The top-level iteration. There are as many phases as chars in <see cref="Text"/>.
 64    /// </summary>
 65    private int Phase;
 66
 67    /// <summary>
 68    /// How many suffixes still need to be created within the current phase.
 69    /// </summary>
 70    /// <remarks>
 71    /// Every time a leaf is created, a suffix has been fully processed, so <see cref="RemainingSuffixes"/> is reduced
 72    /// by one, whereas when the active edge and length are changed, because RULE 3 EXTENSION is trigger, the tree is
 73    /// in implicit state, i.e. there is no edge with termination character and no internal node with only one edge
 74    /// going out of it.
 75    /// </remarks>
 76    private int RemainingSuffixes;
 77
 78    /// <summary>
 79    /// The internal node created in the previous iteration of the same phase, or <see langword="null"/>, if
 80    /// there hasn't been a previous iteration in the same phase where an internal node has been created.
 81    /// </summary>
 82    private MutableNode? PreviousInternalNodeInTheSamePhase;
 83
 84    /// <summary>
 85    /// The end of the text, which is a moving target - it gets increased every time a new char from
 86    /// <see cref="Text"/> is processed.
 87    /// </summary>
 88    /// <remarks>
 89    /// Since it is kept by reference from edges, increasing ends at
 90    /// <see cref="StartPhaseIncreasingRemainingAndGlobalEnd"/> performs the RULE 1 EXTENSION on all leafs at no
 91    /// additional cost.
 92    /// <br/>
 93    /// This is one of the mechanisms that make time Ukkonen algorithm complexity linear.
 94    /// </remarks>
 95    private readonly MovingEnd GlobalEnd;
 96
 97    private int NextLeafStart;
 98    private int NextNodeId;
 99
 100    /// <summary>
 101    /// The char in <see cref="Text"/> at index <see cref="Phase"/>.
 102    /// </summary>
 103    private char CurrentChar
 104    {
 105        get
 1346106        {
 1346107            if (Phase < 0)
 1108                throw new InvalidOperationException("No phase started yet");
 1345109            return Text[Phase];
 1345110        }
 111    }
 112
 113    #endregion
 114
 115    #region Active Point
 116
 117    /// <summary>
 118    /// The node in the tree currently being built, where to start from when looking for <see cref="CurrentChar"/>.
 119    /// </summary>
 120    /// <remarks>
 121    /// At the beginning it is set to the root of the about-to-be-built tree. The root doesn't have a Suffix Link.
 122    /// Unlike <see cref="ActiveEdge"/> and <see cref="ActiveLength"/>, <see cref="ActiveNode"/> is always defined.
 123    /// <br/>
 124    /// It's changed by <see cref="JumpActivePointIfNecessary"/>, when the current <see cref="ActiveEdge"/> has been
 125    /// fully traversed.
 126    /// <br/>
 127    /// It is also changed by <see cref="CreateLeafAndPossiblyIntermediateAndDecrementRemainingSuffixes"/> in Rule
 128    /// 2 Extension, and set to its <see cref="MutableNode.SuffixLink"/>, when an internal node has been created
 129    /// and the <see cref="ActiveNode"/> is not the <see cref="Root"/> node.
 130    /// </remarks>
 131    private MutableNode ActiveNode;
 132
 133    /// <summary>
 134    /// The index in the text, of the 1st char identifying the edge starting from <see cref="ActiveNode"/>, where
 135    /// to start from when looking for <see cref="CurrentChar"/>. It's -1 if no edge has been selected yet.
 136    /// </summary>
 137    /// <remarks>
 138    /// For example, if the edge is labelled as "abc" and the 1st "a" corresponds to the 1st char of the text
 139    /// "abcxyx$", the active edge is 0. At the beginning no edge has been selected from the active node, so
 140    /// there is no active edge, and the value -1 is set. In this scenario <see cref="ActiveLength"/> is also
 141    /// reset to 0.
 142    /// </remarks>
 143    private int ActiveEdgeStart;
 144
 145    /// <summary>
 146    /// The amount of chars which have been processed on the <see cref="ActiveEdge"/>.
 147    /// </summary>
 148    /// <remarks>
 149    /// At the beginning, no edge has been selected from the active node, so its value is 0.
 150    /// </remarks>
 151    private int ActiveLength;
 152
 153    /// <summary>
 154    /// The edge coming out of <see cref="ActiveNode"/> which starts with <see cref="ActiveEdgeStart"/>.
 155    /// </summary>
 156    /// <exception cref="InvalidOperationException">
 157    /// If <see cref="ActiveEdgeStart"/> is invalid in the current context (i.e. there is no active point).
 158    /// </exception>
 159    private MutableEdge ActiveEdge
 160    {
 161        get
 700162        {
 700163            var activeEdgeOrDefault = ActiveNode.Children.Keys.SingleOrDefault(
 2473164                edge => Text[edge.Start] == Text[ActiveEdgeStart]);
 700165            if (activeEdgeOrDefault is not MutableEdge activeEdge)
 1166                throw new InvalidOperationException($"No edge from {ActiveNode} starting with {ActiveEdgeStart}");
 699167            return activeEdge;
 699168        }
 169    }
 170
 171    #endregion
 172
 173    /// <summary>
 174    /// Checks whether there are still more top-level iterations to execute, or not.
 175    /// </summary>
 176    /// <returns>True if there are still phases to go, false otherwise.</returns>
 394177    public bool IsThereANextPhase() => Phase < Text.Length - 1;
 178
 179    /// <summary>
 180    /// Start a new phase, i.e. to the next char in <see cref="Text"/>, increasing <see cref="RemainingSuffixes"/>
 181    /// and <see cref="GlobalEnd"/> and resetting <see cref="PreviousInternalNodeInTheSamePhase"/>.
 182    /// </summary>
 183    public void StartPhaseIncreasingRemainingAndGlobalEnd()
 355184    {
 355185        Phase++;
 355186        RemainingSuffixes++;
 355187        GlobalEnd.Value++;
 355188        PreviousInternalNodeInTheSamePhase = null;
 355189    }
 190
 191    /// <summary>
 192    /// Whether there still are suffixes to be processed in the current <see cref="Phase"/>.
 193    /// </summary>
 194    /// <returns>True if there are, false otherwise.</returns>
 195    public bool StillRemainingSuffixesInCurrentPhase()
 685196    {
 685197        if (Phase < 0)
 1198            throw new InvalidOperationException("No phase started yet");
 684199        return RemainingSuffixes > 0;
 684200    }
 201
 202    /// <summary>
 203    /// If no active point is currently defined (non-positive <see cref="ActiveLength"/>), and there is an
 204    /// edge coming out of the <see cref="ActiveNode"/> which starts with <see cref="CurrentChar"/>, gives back
 205    /// that edge.
 206    /// </summary>
 207    /// <returns>The edge, if found. Otherwise null.</returns>
 208    public MutableEdge? NoActivePointAndEdgeStartingFromActiveNodeWithCurrentChar()
 496209    {
 496210        if (ActiveLength > 0)
 163211            return null;
 212
 1012213        return ActiveNode.Children.Keys.SingleOrDefault(edge => Text[edge.Start] == CurrentChar);
 496214    }
 215
 216    /// <summary>
 217    /// Sets the <see cref="ActiveEdge"/>, by actually storing its <see cref="ActiveEdgeStart"/>. It also set
 218    /// <see cref="ActiveLength"/> to 1, since activating an edge means consuming the 1st char of the edge, as it
 219    /// corresponds to the current char of <see cref="Text"/>.
 220    /// </summary>
 221    /// <param name="edge">The new <see cref="ActiveEdge"/> to set.</param>
 222    public void InitializeActiveEdgeAndLength(MutableEdge edge)
 116223    {
 116224        ActiveEdgeStart = edge.Start;
 116225        ActiveLength = 1;
 226
 116227        JumpActivePointIfNecessary(edge);
 115228    }
 229
 230    /// <summary>
 231    /// Whether there is an active point (active node + edge + length), followed by the <see cref="CurrentChar"/>
 232    /// being processed in the current <see cref="Phase"/>.
 233    /// </summary>
 234    /// <returns>True if there is an active point which respects the condition, false otherwise.</returns>
 235    /// <exception cref="InvalidOperationException">If the active point state is inconsistent.</exception>
 236    public bool ActivePointFollowedByCurrentChar()
 396237    {
 396238        if (ActiveEdgeStart < 0)
 225239            return false;
 240
 171241        return Text[ActiveEdge.Start + ActiveLength] == CurrentChar;
 395242    }
 243
 244    /// <summary>
 245    /// Increment <see cref="ActiveLength"/>, updating the Active Point if necessary.
 246    /// Used when <see cref="ActivePointFollowedByCurrentChar"/> is satified, to trigger Rule 3 Extension, which is
 247    /// a showstopper for the current phase.
 248    /// </summary>
 249    public void IncrementActiveLength()
 47250    {
 47251        ActiveLength++;
 47252        JumpActivePointIfNecessary(ActiveEdge);
 47253    }
 254
 255    /// <summary>
 256    /// Makes the active point properties consistent with each other, jumping <see cref="ActiveNode"/> if necessary
 257    /// and resetting <see cref="ActiveEdgeStart"/> and <see cref="ActiveLength"/> when appropriate.
 258    /// </summary>
 259    /// <remarks>
 260    ///     <para id="algo">
 261    ///     ALGORITHM
 262    ///     <br/>
 263    ///     Performed at the end of every iteration.
 264    ///     <br/>
 265    ///     - If there is no Active Point, i.e. negative <see cref="ActiveEdgeStart"/> or zero
 266    ///       <see cref="ActiveLength"/>, there's nothing to update. Therefore, exit.
 267    ///       <br/>
 268    ///     - Scenario 1: If there are still chars not traversed on the current <see cref="ActiveEdge"/>, the Active
 269    ///       Point is still valid. Therefore, exit. Otherwise, perform the update.
 270    ///       <br/>
 271    ///    <br/>
 272    ///    There are two scenarios when all chars have been consumed on the current Active Edge, and an update of the
 273    ///    Active Point is required.
 274    ///    <br/>
 275    ///     - Scenario 2: the Active Point points to the last char of the <see cref="ActiveEdge"/>. This can happen
 276    ///       when Rule 3 Extension is applied and <see cref="ActiveLength"/> is increased by 1, reaching the end of
 277    ///       the current <see cref="ActiveEdge"/>. In this scenario, change <see cref="ActiveNode"/>, jumping to the
 278    ///       node pointed by the current <see cref="ActiveEdge"/>, and reset the Active Point.
 279    ///       <br/>
 280    ///     - Scenario 3: the Active Point overflows, with the ActiveLength going beyond the <see cref="ActiveEdge"/>.
 281    ///       This can happen when Rule 2 Extension is applied and <see cref="ActiveEdgeStart"/> is increased by 1,
 282    ///       changing active edge, and <see cref="ActiveLength"/> is decreased by 1, but the edge which was unique on
 283    ///       the previous branch is split into a sequence of edges on the new branch. In this scenario, change
 284    ///       <see cref="ActiveNode"/>, jumping to the node pointed by the current <see cref="ActiveEdge"/>, and set
 285    ///       the Active Point accordingly.
 286    ///     </para>
 287    /// </remarks>
 288    public void JumpActivePointIfNecessary(MutableEdge? previousActiveEdge)
 505289    {
 519290        while (true)
 519291        {
 519292            if (ActiveEdgeStart < 0 || ActiveLength <= 0)
 277293            {
 277294                ActiveEdgeStart = -1;
 277295                ActiveLength = 0;
 277296                return;
 297            }
 298
 242299            var currentActiveEdge = ActiveEdge;
 241300            var remainingCharsOnActiveEdgeAfterActivePoint =
 241301                currentActiveEdge.End.Value - (currentActiveEdge.Start + ActiveLength - 1);
 302
 303            // Scenario 1
 241304            if (remainingCharsOnActiveEdgeAfterActivePoint > 0)
 170305            {
 170306                return;
 307            }
 308
 309            // Scenario 2
 71310            if (remainingCharsOnActiveEdgeAfterActivePoint == 0)
 57311            {
 57312                ActiveNode = ActiveNode.Children[currentActiveEdge];
 57313                ActiveEdgeStart = -1;
 57314                ActiveLength = 0;
 57315                return;
 316            }
 317
 318            // Scenario 3
 14319            var firstCharOverflowingCurrentActiveEdge = previousActiveEdge!.Start + currentActiveEdge.Length;
 14320            ActiveNode = ActiveNode.Children[currentActiveEdge];
 14321            ActiveEdgeStart = ActiveNode.Children.Keys.Single(
 46322                edge => Text[edge.Start] == Text[firstCharOverflowingCurrentActiveEdge]).Start;
 14323            ActiveLength = -remainingCharsOnActiveEdgeAfterActivePoint;
 324
 14325            previousActiveEdge = currentActiveEdge;
 14326        }
 504327    }
 328
 329    /// <summary>
 330    /// Performs Rule 2 Extension.
 331    /// </summary>
 332    public void CreateLeafAndPossiblyIntermediateAndDecrementRemainingSuffixes()
 342333    {
 342334        var leafEdge = new MutableEdge(Phase, GlobalEnd);
 342335        var leafNode = new MutableNode(NextNodeId++, NextLeafStart++, null, leafEdge); // Leaves don't have Suffix Link
 336
 337        MutableNode internalNode;
 338
 342339        if (ActiveLength == 0)
 222340        {
 341            // No current active edge => branch out from the active node directly
 222342            ActiveNode.Children[leafEdge] = leafNode;
 343
 344            // Stop edge to ActiveNode from tracking the end of the text, as ActiveNode transitions from being a leaf
 345            // to an intermediate node
 222346            if (ActiveNode.IncomingEdge != null) // False on the root
 34347            {
 34348                ActiveNode.IncomingEdge.End = new(ActiveNode.IncomingEdge.End.Value);
 34349            }
 350
 222351            internalNode = ActiveNode;
 222352        }
 353        else
 120354        {
 355            // There is an active edge => split the active edge into two, with an intern node in the middle
 120356            var activeEdge = ActiveEdge;
 120357            MutableEdge sharedEdge = new(activeEdge.Start, new(activeEdge.Start + ActiveLength - 1));
 120358            MutableEdge reminderEdge = new(activeEdge.Start + ActiveLength, activeEdge.End);
 359
 120360            internalNode = new(NextNodeId++, null, Root, sharedEdge);
 120361            internalNode.Children[reminderEdge] = ActiveNode.Children[activeEdge];
 120362            internalNode.Children[leafEdge] = leafNode;
 363
 120364            ActiveNode.Children[sharedEdge] = internalNode;
 120365            ActiveNode.Children.Remove(activeEdge);
 120366        }
 367
 368        // If an internal node has been created in a previous Rule 2 Extension of the current phase, update
 369        // the SuffixLink of the previous internal node to be the just created internal node, and store the
 370        // just create internal store PreviousInternalNodeInTheSamePhase, to be able to set the Suffix Link
 371        // on next internal node if a new Rule 2 Extension happens after this one, in the same phase
 342372        if (PreviousInternalNodeInTheSamePhase is MutableNode previousInternalNode)
 129373            previousInternalNode.SuffixLink = internalNode;
 342374        PreviousInternalNodeInTheSamePhase = internalNode;
 375
 342376        var previousActiveEdge = ActiveEdgeStart >= 0 ? ActiveEdge : null;
 377
 378        // Now that the leaf x has been created, and possibly the internal node abc..z (length = RemainingSuffixes)
 379        // has also been created, and the previous internal node updated (if any), there are still RemainingSuffixes
 380        // suffixes to be processed: bc..zx, c..zx, ... x. Those can be easily found in the tree by using Suffix Links.
 342381        if (ActiveNode == Root && ActiveEdgeStart >= 0)
 82382        {
 383            // To move from abc..zx to bc..zx when the active node is the root, increment ActiveEdgeStart
 384            // (which means that the start of the new ActiveEdge is now b) and decrement ActiveLength (because
 385            // now the ActiveEdge is one char shorter.
 386            // The edge bc..z is guaranteed to exist already because bc..z it's a suffix already processed in
 387            // previous iterations and Rule 1 Extension implicitely extend bc..z to bc..zx.
 82388            ActiveEdgeStart++;
 82389            ActiveLength--;
 82390        }
 391        else
 260392        {
 393            // To move from abc..zx to bc..zx when the active node is an internal node N, move the active node
 394            // to the Suffix Link of N, which is by definition the node having path bc..zx. Active edge and
 395            // length don't change, because the new active node has the suffix bc..zx.
 260396            ActiveNode = ActiveNode.SuffixLink ?? Root;
 260397        }
 398
 342399        JumpActivePointIfNecessary(previousActiveEdge);
 400
 401        // Because a leaf has been created, a suffix has been processed, and the number of remaining suffixes
 402        // (which is > 1 in case previous phases have terminated with a showstopper due to Rule 3 Extension)
 403        // is decreased by 1.
 342404        RemainingSuffixes--;
 342405    }
 406
 407    /// <inheritdoc path="//*[not(self::summary)]"/>
 408    /// <summary>
 409    ///     <inheritdoc/>
 410    ///     <br/>
 411    ///     Visualization of the main state variables, such as <see cref="Phase"/>, <see cref="RemainingSuffixes"/>
 412    ///     and active point data.
 413    /// </summary>
 414    public override string ToString() =>
 496415        string.Join(", ",
 496416            $"{nameof(Phase)} = {Phase,-2}",
 496417            $"{nameof(CurrentChar)} = {CurrentChar}",
 496418            $"{nameof(RemainingSuffixes)} = {RemainingSuffixes,-2}",
 496419            $"{nameof(ActiveNode)} = {ActiveNode,-2}",
 496420            $"{nameof(ActiveEdgeStart)} = {ActiveEdgeStart,-2}",
 496421            $"{nameof(ActiveLength)} = {ActiveLength,-2}") + Environment.NewLine +
 496422        $"Tree = {Root.Dump(new(Text, GlobalEnd))}";
 423}