Skip to content

Instantly share code, notes, and snippets.

@tintoy
Last active January 28, 2018 00:49
Show Gist options
  • Save tintoy/0b78faf9dd7655f747ef8dbbd00a9571 to your computer and use it in GitHub Desktop.
Save tintoy/0b78faf9dd7655f747ef8dbbd00a9571 to your computer and use it in GitHub Desktop.
Rope-like data structure based on Span<T>
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