Skip to content

Instantly share code, notes, and snippets.

@svick
Created June 20, 2017 02:08
Show Gist options
  • Select an option

  • Save svick/0c11c752ee59890c5b9f94bf3bfeb4a4 to your computer and use it in GitHub Desktop.

Select an option

Save svick/0c11c752ee59890c5b9f94bf3bfeb4a4 to your computer and use it in GitHub Desktop.
A simple implementtion of StringBuilder.EnumerateChunks
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var sb = new StringBuilder
{
m_ChunkChars = "cd".ToCharArray(),
m_ChunkLength = 2,
m_ChunkPrevious = new StringBuilder { m_ChunkChars = "ab".ToCharArray(), m_ChunkLength = 2 }
};
foreach (var chunk in sb.EnumerateChunks())
{
Console.WriteLine(new string(chunk.ToArray()));
}
sb.ForEachChunk(
c =>
{
Console.WriteLine(new string(c.ToArray()));
return true;
});
}
}
public sealed class StringBuilder
{
// A StringBuilder is internally represented as a linked list of blocks each of which holds
// a chunk of the string. It turns out string as a whole can also be represented as just a chunk,
// so that is what we do.
/// <summary>
/// The character buffer for this chunk.
/// </summary>
internal char[] m_ChunkChars;
/// <summary>
/// The chunk that logically precedes this chunk.
/// </summary>
internal StringBuilder m_ChunkPrevious;
/// <summary>
/// The number of characters in this chunk.
/// This is the number of elements in <see cref="m_ChunkChars"/> that are in use, from the start of the buffer.
/// </summary>
internal int m_ChunkLength;
/// <summary>
/// The logical offset of this chunk's characters in the string it is a part of.
/// This is the sum of the number of characters in preceding blocks.
/// </summary>
//internal int m_ChunkOffset;
public delegate TResult SpanFunc<T1, TResult>(ReadOnlySpan<T1> input);
public void ForEachChunk(SpanFunc<char, bool> callback)
{
//VerifyClassInvariant();
ForEachChunk(callback, this);
}
private bool ForEachChunk(SpanFunc<char, bool> callback, StringBuilder chunk)
{
// The chunks are last written first, so we need to output the others first.
var next = chunk.m_ChunkPrevious;
if (next != null && !ForEachChunk(callback, next))
return false;
// The fields here might be changing and inaccurate if threads are racing, but the validation that Span does on
// construction insures that the values, even if inaccurate, are 'in bounds'.
return callback(new Span<char>(chunk.m_ChunkChars, 0, chunk.m_ChunkLength));
}
public ChunkEnumerable EnumerateChunks() => new ChunkEnumerable(this);
public struct ChunkEnumerable
{
private readonly StringBuilder builder;
public ChunkEnumerable(StringBuilder builder) => this.builder = builder;
public ChunkEnumerator GetEnumerator() => new ChunkEnumerator(builder);
}
public struct ChunkEnumerator
{
private readonly Stack<StringBuilder> builders;
public ChunkEnumerator(StringBuilder builder)
{
builders = new Stack<StringBuilder>();
while (builder != null)
{
builders.Push(builder);
builder = builder.m_ChunkPrevious;
}
builders.Push(null);
}
public bool MoveNext()
{
if (builders.Count == 0)
return false;
builders.Pop();
return builders.Count > 0;
}
public ReadOnlySpan<char> Current
{
get
{
var sb = builders.Peek();
return new ReadOnlySpan<char>(sb.m_ChunkChars, 0, sb.m_ChunkLength);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment