| | 1 | | using MoreStructures.Utilities; |
| | 2 | |
|
| | 3 | | namespace MoreStructures; |
| | 4 | |
|
| | 5 | | /// <summary> |
| | 6 | | /// A text string with a terminator character which has been rotated leftwards or rightwards, of a number of positions |
| | 7 | | /// (0 included). |
| | 8 | | /// </summary> |
| | 9 | | /// <param name="Underlying">The <see cref="TextWithTerminator"/> instance which has been rotated.</param> |
| | 10 | | /// <param name="Rotation"> |
| | 11 | | /// The number of characters to rotate: positive = rightwards, negative = leftwards. |
| | 12 | | /// </param> |
| | 13 | | /// <remarks> |
| | 14 | | /// A virtually rotated terminator-terminated text is required by Burrows-Wheeler Transform construction, when the |
| | 15 | | /// length of the text is too high to build the Burrows-Wheeler Matrix, which would have n^2 items. |
| | 16 | | /// </remarks> |
| 184614 | 17 | | public record VirtuallyRotatedTextWithTerminator(RotatedTextWithTerminator Underlying, int Rotation) |
| 10440 | 18 | | : IValueEnumerable<char>, IComparable<VirtuallyRotatedTextWithTerminator> |
| 10440 | 19 | | { |
| 10440 | 20 | | // CharOrTerminatorComparer is a record, so compared by value |
| 10440 | 21 | | private readonly IComparer<char> _charsComparer = CharOrTerminatorComparer.Build(Underlying.Terminator); |
| 10440 | 22 | |
|
| 10440 | 23 | | private sealed class Enumerator : IEnumerator<char> |
| 10440 | 24 | | { |
| 10440 | 25 | | private readonly RotatedTextWithTerminator _underlying; |
| 10440 | 26 | | private readonly int _rotation; |
| 10440 | 27 | | private int _current; |
| 10440 | 28 | |
|
| 62769 | 29 | | public Enumerator(RotatedTextWithTerminator underlying, int rotation) |
| 62769 | 30 | | { |
| 62769 | 31 | | _underlying = underlying; |
| 62769 | 32 | | _rotation = rotation; |
| 62769 | 33 | | Reset(); |
| 62769 | 34 | | } |
| 10440 | 35 | |
|
| 10440 | 36 | | public char Current |
| 10440 | 37 | | { |
| 10440 | 38 | | get |
| 170318 | 39 | | { |
| 170318 | 40 | | if (_current < 0) |
| 1 | 41 | | throw new InvalidOperationException($"Enumeration has not started. Call {nameof(MoveNext)}."); |
| 170317 | 42 | | if (_current >= _underlying.Length) |
| 1 | 43 | | throw new InvalidOperationException("Enumeration already finished"); |
| 170316 | 44 | | var index = (_current - _rotation) % _underlying.Length; |
| 170316 | 45 | | return _underlying[index >= 0 ? index : _underlying.Length + index]; |
| 170316 | 46 | | } |
| 10440 | 47 | | } |
| 10440 | 48 | |
|
| 15 | 49 | | object System.Collections.IEnumerator.Current => Current; |
| 10440 | 50 | |
|
| 10440 | 51 | | public void Dispose() |
| 62767 | 52 | | { |
| 10440 | 53 | | // Nothing, for the time being |
| 62767 | 54 | | } |
| 107608 | 55 | | public bool MoveNext() => ++_current < _underlying.Length; |
| 62771 | 56 | | public void Reset() => _current = -1; |
| 10440 | 57 | | } |
| 10440 | 58 | |
|
| 10440 | 59 | | /// <summary> |
| 10440 | 60 | | /// Select a part of <see cref="Underlying"/> by the provided index (either w.r.t. the start or to the end of the |
| 10440 | 61 | | /// text), applying the <see cref="Rotation"/>. Treat <paramref name="index"/> as circular, over modulo the length |
| 10440 | 62 | | /// of <see cref="Underlying"/>. |
| 10440 | 63 | | /// </summary> |
| 10440 | 64 | | /// <param name="index">The index applied to the underlying string.</param> |
| 10440 | 65 | | /// <returns>A char containing the selected part.</returns> |
| 10440 | 66 | | public char this[Index index] |
| 10440 | 67 | | { |
| 10440 | 68 | | get |
| 9252 | 69 | | { |
| 9252 | 70 | | var length = Underlying.Length; |
| 9252 | 71 | | var rotatedIndex = (index.GetOffset(length) - Rotation) % length; |
| 9252 | 72 | | return Underlying[rotatedIndex >= 0 ? rotatedIndex : length + rotatedIndex]; |
| 9252 | 73 | | } |
| 10440 | 74 | | } |
| 10440 | 75 | |
|
| 10440 | 76 | | /// <inheritdoc/> |
| 10440 | 77 | | public IEnumerator<char> GetEnumerator() => |
| 62769 | 78 | | new Enumerator(Underlying, Rotation); |
| 10440 | 79 | |
|
| 10440 | 80 | | /// <inheritdoc/> |
| 10440 | 81 | | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => |
| 2 | 82 | | GetEnumerator(); |
| 10440 | 83 | |
|
| 10440 | 84 | | /// <inheritdoc/> |
| 10440 | 85 | | public int CompareTo(VirtuallyRotatedTextWithTerminator? other) |
| 31383 | 86 | | { |
| 31383 | 87 | | if (other == null) |
| 1 | 88 | | throw new ArgumentNullException(nameof(other), "Cannot compare to null."); |
| 10440 | 89 | |
|
| 31382 | 90 | | using var enumerator = GetEnumerator(); |
| 31382 | 91 | | using var otherEnumerator = other.GetEnumerator(); |
| 10440 | 92 | |
|
| 31382 | 93 | | var moveNext = enumerator.MoveNext(); |
| 31382 | 94 | | var otherMoveNext = otherEnumerator.MoveNext(); |
| 10440 | 95 | |
|
| 53783 | 96 | | while (moveNext && otherMoveNext && enumerator.Current == otherEnumerator.Current) |
| 22401 | 97 | | { |
| 22401 | 98 | | moveNext = enumerator.MoveNext(); |
| 22401 | 99 | | otherMoveNext = otherEnumerator.MoveNext(); |
| 22401 | 100 | | } |
| 10440 | 101 | |
|
| 31383 | 102 | | if (moveNext && !otherMoveNext) return 1; |
| 31382 | 103 | | if (!moveNext && otherMoveNext) return -1; |
| 62751 | 104 | | if (moveNext) return _charsComparer.Compare(enumerator.Current, otherEnumerator.Current); |
| 9 | 105 | | return 0; |
| 31382 | 106 | | } |
| 10440 | 107 | | } |