< Summary

Information
Class: MoreStructures.TextWithTerminator
Assembly: MoreStructures
File(s): /home/runner/work/MoreStructures/MoreStructures/MoreStructures/TextWithTerminator.cs
Line coverage
100%
Covered lines: 179
Uncovered lines: 0
Coverable lines: 179
Total lines: 212
Line coverage: 100%
Branch coverage
100%
Covered branches: 22
Total branches: 22
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
.ctor(...)100%6100%
get_ValidateInput()100%1100%
get_Text()100%1100%
get_Terminator()100%1100%
get_TerminatorIndex()100%1100%
get_Item(...)100%1100%
get_Item(...)100%2100%
get_Item(...)100%2100%
get_Length()100%4100%
StartsWith(...)100%4100%
EndsWith(...)100%4100%
GetEnumerator()100%1100%
System.Collections.IEnumerable.GetEnumerator()100%1100%

File(s)

/home/runner/work/MoreStructures/MoreStructures/MoreStructures/TextWithTerminator.cs

#LineLine coverage
 1using MoreStructures.Utilities;
 2using System.Collections;
 3
 4namespace MoreStructures;
 5
 6/// <summary>
 7/// A text string with a terminator character, not present in the text.
 8/// </summary>
 9/// <param name="Text">A sequence of chars, of any length (including the empty sequence).</param>
 10/// <param name="Terminator">
 11/// A terminator character, not present in <paramref name="Text"/>. If not specified <see cref="DefaultTerminator"/> is
 12/// used.
 13/// </param>
 14/// <param name="ValidateInput">
 15/// Whether the input, and in particular <see cref="Text"/> should be validated, while this object is created.
 16/// Validation takes O(n) time, where n = number of chars in <see cref="Text"/> and can be an heavy operation.
 17/// </param>
 18/// <remarks>
 19///     <para id="usecases">
 20///     USECASES
 21///     <br/>
 22///     - A terminator-terminated text is required by data structures like Suffix Tries, Trees or Arrays.
 23///       <br/>
 24///     - This object provides type safety, as it allows to tell apart terminator-terminated strings from generic ones.
 25///       <br/>
 26///     - Consistently using <see cref="TextWithTerminator"/>, rather than <see cref="string"/>, in all library
 27///       functionalities ensures that the invariant of a terminator-terminated string is always respected.
 28///       <br/>
 29///     - Most string-related functionalities provided by <see cref="TextWithTerminator"/>, such as
 30///       <see cref="Length"/> and <see cref="this[Index]"/>, as well as <see cref="IEnumerable{T}"/> and
 31///       <see cref="IEnumerable"/> support, are delegated to the underlying string.
 32///     </para>
 33/// </remarks>
 179034public record TextWithTerminator(
 179035    IEnumerable<char> Text,
 179036    char Terminator = TextWithTerminator.DefaultTerminator,
 180837    bool ValidateInput = true)
 179038    : IValueEnumerable<char>
 179039{
 179040    // Wrapped into a value enumerable to preserve value equality.
 180841    private readonly IEnumerable<char> TextAndTerminator =
 180842        (Text is string textStr
 180843        ? textStr + Terminator
 180844        : Text.Append(Terminator)).AsValue();
 179045
 179046    // Lazy initialized
 180847    private int? _length = null;
 179048
 179049    /// <summary>
 179050    /// A selector of a part of a <see cref="TextWithTerminator"/> or <see cref="RotatedTextWithTerminator"/>.
 179051    /// </summary>
 179052    public interface ISelector
 179053    {
 179054        /// <summary>
 179055        /// Extract the substring identified by this selector, out of the provided <see cref="TextWithTerminator"/>.
 179056        /// </summary>
 179057        /// <param name="text">The text with terminator, to extract a substring of.</param>
 179058        /// <returns>A substring, whose length depends on the selector.</returns>
 179059        string Of(TextWithTerminator text);
 179060
 179061        /// <summary>
 179062        /// Extract the substring identified by this selector, out of the provided
 179063        /// <see cref="RotatedTextWithTerminator"/>.
 179064        /// </summary>
 179065        /// <param name="text">The text with terminator, to extract a substring of.</param>
 179066        /// <returns>A substring, whose length depends on the selector.</returns>
 179067        string OfRotated(RotatedTextWithTerminator text);
 179068    }
 179069
 179070    /// <summary>
 179071    /// The special character used as a default terminator for the text to build the Suffix Tree of, when no custom
 179072    /// terminator is specified. Should not be present in the text.
 179073    /// </summary>
 179074    /// <value>
 179075    /// A single char.
 179076    /// </value>
 179077    public const char DefaultTerminator = '$';
 179078
 179079    /// <summary>
 179080    /// <inheritdoc cref="TextWithTerminator" path="/param[@name='Text']"/>
 179081    /// </summary>
 179082    /// <remarks>
 179083    /// Wrapped into a <see cref="IValueEnumerable{T}"/> to preserve value equality.
 179084    /// </remarks>
 179085    /// <value>
 179086    /// A sequence of chars.
 179087    /// </value>
 197388    public IEnumerable<char> Text { get; init; } = Text.AsValue();
 179089
 179090    /// <summary>
 179091    /// <inheritdoc cref="TextWithTerminator" path="/param[@name='Terminator']"/>
 179092    /// </summary>
 179093    /// <value>
 179094    /// A single char.
 179095    /// </value>
 1252596    public char Terminator { get; init; } =
 180897        !ValidateInput || !Text.Contains(Terminator)
 180898        ? Terminator
 180899        : throw new ArgumentException($"{nameof(Terminator)} shouldn't be included in {nameof(Text)}.");
 1790100
 1790101    /// <summary>
 1790102    /// Returns the index of <see cref="Terminator"/> in this <see cref="TextAndTerminator"/>.
 1790103    /// </summary>
 1790104    /// <value>
 1790105    /// A 0-based index. 0 when <see cref="Text"/> is empty, positive otherwise.
 1790106    /// </value>
 59107    public int TerminatorIndex => Length - 1;
 1790108
 1790109    /// <summary>
 1790110    /// Select a part of this text by the provided selector.
 1790111    /// </summary>
 1790112    /// <param name="selector">Any selector acting on a <see cref="TextWithTerminator"/>.</param>
 1790113    /// <returns>A string containing the selected part.</returns>
 5107114    public string this[ISelector selector] => selector.Of(this);
 1790115
 1790116    /// <summary>
 1790117    /// Select a part of this text by the provided range (start index included, end index excluded).
 1790118    /// </summary>
 1790119    /// <param name="range">The range applied to the underlying string.</param>
 1790120    /// <returns>An <see cref="IEnumerable{T}"/> of chars containing the selected part.</returns>
 1790121    public IEnumerable<char> this[Range range] =>
 10137122        TextAndTerminator is StringValueEnumerable { StringValue: var str }
 10137123        ? str[range]
 10137124        : TextAndTerminator.Take(range).AsValue();
 1790125
 1790126    /// <summary>
 1790127    /// Select a part of this text by the provided index (either w.r.t. the start or to the end of the text).
 1790128    /// </summary>
 1790129    /// <param name="index">The index applied to the underlying string.</param>
 1790130    /// <returns>A char containing the selected part.</returns>
 1790131    public char this[Index index] =>
 10104132        TextAndTerminator is StringValueEnumerable { StringValue: var str }
 10104133        ? str[index]
 10104134        : TextAndTerminator.ElementAtO1(index);
 1790135
 1790136    /// <summary>
 1790137    /// The total length of this text, including the terminator.
 1790138    /// </summary>
 1790139    /// <value>
 1790140    /// A positive integer (at least 1).
 1790141    /// </value>
 1790142    /// <remarks>
 1790143    ///     <para id="caching">
 1790144    ///     CACHING
 1790145    ///     <br/>
 1790146    ///     Calculated just once, and cached for later use.
 1790147    ///     <br/>
 1790148    ///     Immutability guarantees correctness.
 1790149    ///     </para>
 1790150    ///     <para id="complexity">
 1790151    ///     COMPLEXITY
 1790152    ///     <br/>
 1790153    ///     - If the text was built with a <see cref="string"/> as input, the operation is O(1) in time.
 1790154    ///       <br/>
 1790155    ///     - If the text was built with a type optimized by
 1790156    ///       <see cref="EnumerableExtensions.CountO1{TSource}(IEnumerable{TSource})"/>, such as an <see cref="IList"/>
 1790157    ///       or <see cref="IList{T}"/> the operation is O(1) as well.
 1790158    ///       <br/>
 1790159    ///     - Otherwise, the operation is O(n), where n is the length of <see cref="Text"/>.
 1790160    ///     </para>
 1790161    /// </remarks>
 1790162    public int Length
 1790163    {
 1790164        get
 4044165        {
 4044166            if (_length == null)
 333167            {
 333168                _length =
 333169                    TextAndTerminator is StringValueEnumerable { StringValue: var str }
 333170                    ? str.Length
 333171                    : TextAndTerminator.CountO1();
 333172            }
 4044173            return _length.Value;
 4044174        }
 1790175    }
 1790176
 1790177    /// <summary>
 1790178    /// Whether this text starts with the provided suffix.
 1790179    /// </summary>
 1790180    /// <param name="prefix">A terminator-included <see cref="IEnumerable{T}"/> of <see cref="char"/>.</param>
 1790181    /// <returns>True if this text starts by the prefix.</returns>
 1790182    public bool StartsWith(IEnumerable<char> prefix) =>
 3183        TextAndTerminator is StringValueEnumerable { StringValue: var str } && prefix is string prefixStr
 3184        ? str.StartsWith(prefixStr)
 3185        : TextAndTerminator.Take(prefix.CountO1()).SequenceEqual(prefix);
 1790186
 1790187    /// <summary>
 1790188    /// Whether this text ends with the provided suffix.
 1790189    /// </summary>
 1790190    /// <param name="suffix">A terminator-included <see cref="IEnumerable{T}"/> of <see cref="char"/>.</param>
 1790191    /// <returns>True if this text ends by the suffix.</returns>
 1790192    public bool EndsWith(IEnumerable<char> suffix) =>
 47193        TextAndTerminator is StringValueEnumerable { StringValue: var str } && suffix is string suffixStr
 47194        ? str.EndsWith(suffixStr)
 47195        : TextAndTerminator.TakeLast(suffix.CountO1()).SequenceEqual(suffix);
 1790196
 1790197    /// <inheritdoc path="//*[not(self::summary)]"/>
 1790198    /// <summary>
 1790199    /// Returns an enumerator that iterates through the collection of chars of the underlying <see cref="Text"/>
 1790200    /// string, including the <see cref="Terminator"/> char.
 1790201    /// </summary>
 1790202    public IEnumerator<char> GetEnumerator() =>
 190965203        TextAndTerminator.GetEnumerator();
 1790204
 1790205    /// <inheritdoc path="//*[not(self::summary)]"/>
 1790206    /// <summary>
 1790207    /// Returns an enumerator that iterates through the collection of chars of the underlying <see cref="Text"/>
 1790208    /// string, including the <see cref="Terminator"/> char.
 1790209    /// </summary>
 1790210    IEnumerator IEnumerable.GetEnumerator() =>
 1211        ((IEnumerable)TextAndTerminator).GetEnumerator();
 1790212}