Last active
January 28, 2018 00:49
-
-
Save tintoy/0b78faf9dd7655f747ef8dbbd00a9571 to your computer and use it in GitHub Desktop.
Rope-like data structure based on Span<T>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
namespace SpanString | |
{ | |
public static class Rope | |
{ | |
public static Rope<T> FromArray<T>(T[] array) where T : struct | |
=> Rope<T>.FromArray(array); | |
public static Rope<T> CopyFrom<T>(T[] array) where T : struct | |
=> Rope<T>.CopyFrom(array); | |
public static Rope<T> CopyFrom<T>(T[] array, int startIndex, int count) where T : struct | |
=> Rope<T>.CopyFrom(array, startIndex, count); | |
public static Rope<byte> FromString(string str) => FromString(str, Encoding.Unicode); | |
public static Rope<byte> FromString(string str, Encoding encoding) => FromArray(encoding.GetBytes(str)); | |
} | |
public abstract class Rope<T> | |
: IEnumerable<T> | |
where T : struct | |
{ | |
public abstract int Count { get; } | |
public abstract T this[int index] { get; } | |
public abstract Rope<T> Append(Rope<T> other); | |
public abstract T[] ToArray(); | |
public IEnumerator<T> GetEnumerator() => Enumerate().GetEnumerator(); | |
protected abstract IEnumerable<T> Enumerate(); | |
protected abstract ReadOnlySpan<T>[] GetSpans(); | |
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | |
class SingleSpanRope | |
: Rope<T> | |
{ | |
readonly ReadOnlySpan<T> _span; | |
public SingleSpanRope(ReadOnlySpan<T> span) => _span = span; | |
public override int Count => _span.Length; | |
public override T this[int index] => _span[index]; | |
public override Rope<T> Append(Rope<T> other) | |
{ | |
ReadOnlySpan<T>[] otherSpans = other.GetSpans(); | |
ReadOnlySpan<T>[] combinedSpans = new ReadOnlySpan<T>[otherSpans.Length + 1]; | |
combinedSpans[0] = _span; | |
Array.Copy(otherSpans, 0, combinedSpans, 1, otherSpans.Length); | |
return new MultiSpanRope(combinedSpans); | |
} | |
public override T[] ToArray() => _span.ToArray(); | |
protected override ReadOnlySpan<T>[] GetSpans() => new ReadOnlySpan<T>[1] { _span }; | |
protected override IEnumerable<T> Enumerate() | |
{ | |
for (int index = 0; index < _span.Length; index++) | |
yield return _span[index]; | |
} | |
} | |
class MultiSpanRope | |
: Rope<T> | |
{ | |
readonly ReadOnlySpan<T>[] _spans; | |
public MultiSpanRope(ReadOnlySpan<T>[] spans) | |
{ | |
_spans = spans; | |
Count = _spans.Sum(span => spans.Length); | |
} | |
public override int Count { get; } | |
public override T this[int index] | |
{ | |
get | |
{ | |
(int spansIndex, int spanIndex) = GetSpanAndIndex(index); | |
return _spans[spansIndex][spanIndex]; | |
} | |
} | |
public override Rope<T> Append(Rope<T> other) | |
{ | |
ReadOnlySpan<T>[] otherSpans = other.GetSpans(); | |
ReadOnlySpan<T>[] combinedSpans = new ReadOnlySpan<T>[otherSpans.Length + _spans.Length]; | |
Array.Copy(_spans, 0, combinedSpans, 0, otherSpans.Length); | |
Array.Copy(otherSpans, 0, combinedSpans, otherSpans.Length, otherSpans.Length); | |
return new MultiSpanRope(combinedSpans); | |
} | |
public override T[] ToArray() | |
{ | |
T[] array = new T[Count]; | |
int startIndex = 0; | |
foreach (ReadOnlySpan<T> span in _spans) | |
{ | |
for (int index = 0; index < span.Length; index++) | |
array[startIndex + index] = span[index]; | |
startIndex += span.Length; | |
} | |
return array; | |
} | |
protected override ReadOnlySpan<T>[] GetSpans() => (ReadOnlySpan<T>[])_spans.Clone(); | |
protected override IEnumerable<T> Enumerate() | |
{ | |
foreach (ReadOnlySpan<T> span in _spans) | |
{ | |
for (int index = 0; index < span.Length; index++) | |
yield return span[index]; | |
} | |
} | |
(int spansIndex, int spanIndex) GetSpanAndIndex(int index) | |
{ | |
if (index == 0) | |
return (spansIndex: 0, spanIndex: 0); | |
int spanIndex = 0; | |
for (int spansIndex = 0; spansIndex < _spans.Length; spansIndex++) | |
{ | |
var span = _spans[spansIndex]; | |
if (spanIndex < span.Length) | |
return (spansIndex, spanIndex); | |
spanIndex -= span.Length; | |
} | |
throw new IndexOutOfRangeException($"Index {index} lies outside the bounds of the current rope (0..{Count - 1})."); | |
} | |
} | |
public static Rope<T> FromArray(T[] array) => new SingleSpanRope(array.AsSpan()); | |
public static Rope<T> CopyFrom(T[] array) => CopyFrom(array, 0, array.Length); | |
public static Rope<T> CopyFrom(T[] array, int startIndex, int count) | |
{ | |
T[] copy = new T[count]; | |
Array.Copy( | |
sourceArray: array, | |
sourceIndex: 0, | |
destinationArray: copy, | |
destinationIndex: 0, | |
length: count | |
); | |
return new SingleSpanRope(copy); | |
} | |
} | |
public static class RopeExtensions | |
{ | |
public static string ToString(this Rope<byte> rope) => rope.ToString(Encoding.Unicode); | |
public static string ToString(this Rope<byte> rope, Encoding encoding) | |
{ | |
// Must combine all spans before decoding, or we risk hitting the case where the bytes representing a multi-byte character are split across 2 instances of Span<byte>. | |
byte[] buffer = rope.ToArray(); | |
return encoding.GetString(buffer); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment