Skip to content

Commit

Permalink
Add Utf16ValueStringBuilder.Replace()
Browse files Browse the repository at this point in the history
  • Loading branch information
udaken committed Jul 3, 2020
1 parent 58d200d commit b735e58
Show file tree
Hide file tree
Showing 4 changed files with 535 additions and 0 deletions.
189 changes: 189 additions & 0 deletions sandbox/PerfBenchmark/Benchmarks/ReplaceBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
using BenchmarkDotNet.Attributes;
using Cysharp.Text;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Formatting;

namespace PerfBenchmark.Benchmarks
{
[Config(typeof(BenchmarkConfig))]
public class ReplaceBenchmark
{
StringBuilder bcl;

string text = "The quick brown fox jumped over the lazy dogs.";
string largeText;

string guid = Guid.NewGuid().ToString();

readonly string[] csharpKeywords =
{
"abstract",
"as",
"async",
"await",
"base",
"bool",
"break",
"byte",
"case",
"catch",
"char",
"checked",
"class",
"const",
"continue",
"decimal",
"default",
"delegate",
"do",
"double",
"else",
"enum",
"event",
"explicit",
"extern",
"false",
"finally",
"fixed",
"float",
"for",
"foreach",
"goto",
"if",
"implicit",
"in",
"int",
"interface",
"internal",
"is",
"lock",
"long",
"namespace",
"new",
"null",
"object",
"operator",
"out",
"override",
"params",
"private",
"protected",
"public",
"readonly",
"ref",
"return",
"sbyte",
"sealed",
"short",
"sizeof",
"stackalloc",
"static",
"string",
"struct",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"uint",
"ulong",
"unchecked",
"unsafe",
"ushort",
"using",
"virtual",
"volatile",
"void",
"while",
};

private static string GetThisFilePath([System.Runtime.CompilerServices.CallerFilePath] string path = null) => path;

public ReplaceBenchmark()
{
bcl = new StringBuilder();
largeText = System.IO.File.ReadAllText(GetThisFilePath()); //read this file
if (largeText.Length < 2048)
throw new Exception();
}

[Benchmark]
public int ReplaceChar()
{
bcl.Clear();
return bcl.Append(text).Replace(' ', '\n').Length;
}

[Benchmark]
public int ZReplaceChar()
{
using var zsb = ZString.CreateStringBuilder(true);
zsb.Append(text);
zsb.Replace(' ', '\n');
return zsb.Length; // Use Length to avoid omitting it
}

[Benchmark]
public int ReplaceString()
{
bcl.Clear();
return bcl.Append(text).Replace(" ", "\r\n").Length; // Use Length to avoid omitting it
}

[Benchmark]
public int ZReplaceString()
{
using var zsb = ZString.CreateStringBuilder(true);
zsb.Append(text);
zsb.Replace(" ", "\r\n");
return zsb.Length; // Use Length to avoid omitting it
}

[Benchmark]
public int NotReplaced()
{
bcl.Clear();
bcl.Append(largeText);
bcl.Replace(guid, "XXXXXX"); // GUID value should not be included in this file.
return bcl.Length; // Use Length to avoid omitting it
}

[Benchmark]
public int ZNotReplaced()
{
using var zsb = ZString.CreateStringBuilder(true);
zsb.Append(text);
zsb.Replace(guid, "XXXXXX"); // GUID value should not be included in this file.
return zsb.Length; // Use Length to avoid omitting it
}

[Benchmark]
public int ManyTimesReplace()
{
bcl.Clear();
bcl.Append(largeText);
// remove all keywords
foreach (var keyword in csharpKeywords)
{
bcl.Replace(keyword, "");
}
return bcl.Length; // Use Length to avoid omitting it
}

[Benchmark]
public int ZManyTimesReplace()
{
using var zsb = ZString.CreateStringBuilder(true);
zsb.Append(text);
// remove all keywords
foreach (var keyword in csharpKeywords)
{
zsb.Replace(keyword, "");
}
return zsb.Length; // Use Length to avoid omitting it
}
}
}
135 changes: 135 additions & 0 deletions src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,141 @@ public void AppendLine<T>(T value)
AppendLine();
}

/// <summary>
/// Replaces all instances of one character with another in this builder.
/// </summary>
/// <param name="oldChar">The character to replace.</param>
/// <param name="newChar">The character to replace <paramref name="oldChar"/> with.</param>
public void Replace(char oldChar, char newChar) => Replace(oldChar, newChar, 0, Length);

/// <summary>
/// Replaces all instances of one character with another in this builder.
/// </summary>
/// <param name="oldChar">The character to replace.</param>
/// <param name="newChar">The character to replace <paramref name="oldChar"/> with.</param>
/// <param name="startIndex">The index to start in this builder.</param>
/// <param name="count">The number of characters to read in this builder.</param>
public void Replace(char oldChar, char newChar, int startIndex, int count)
{
int currentLength = Length;
if ((uint)startIndex > (uint)currentLength)
{
throw new ArgumentOutOfRangeException(nameof(startIndex));
}

if (count < 0 || startIndex > currentLength - count)
{
throw new ArgumentOutOfRangeException(nameof(count));
}

int endIndex = startIndex + count;

for (int i = startIndex; i < endIndex; i++)
{
if (buffer[i] == oldChar)
{
buffer[i] = newChar;
}
}
}

/// <summary>
/// Replaces all instances of one string with another in this builder.
/// </summary>
/// <param name="oldValue">The string to replace.</param>
/// <param name="newValue">The string to replace <paramref name="oldValue"/> with.</param>
/// <remarks>
/// If <paramref name="newValue"/> is <c>null</c>, instances of <paramref name="oldValue"/>
/// are removed from this builder.
/// </remarks>
public void Replace(string oldValue, string newValue) => Replace(oldValue, newValue, 0, Length);

/// <summary>
/// Replaces all instances of one string with another in part of this builder.
/// </summary>
/// <param name="oldValue">The string to replace.</param>
/// <param name="newValue">The string to replace <paramref name="oldValue"/> with.</param>
/// <param name="startIndex">The index to start in this builder.</param>
/// <param name="count">The number of characters to read in this builder.</param>
/// <remarks>
/// If <paramref name="newValue"/> is <c>null</c>, instances of <paramref name="oldValue"/>
/// are removed from this builder.
/// </remarks>
public void Replace(string oldValue, string newValue, int startIndex, int count)
{
int currentLength = Length;

if ((uint)startIndex > (uint)currentLength)
{
throw new ArgumentOutOfRangeException(nameof(startIndex));
}

if (count < 0 || startIndex > currentLength - count)
{
throw new ArgumentOutOfRangeException(nameof(count));
}

if (oldValue == null)
{
throw new ArgumentNullException(nameof(oldValue));
}

if (oldValue.Length == 0)
{
throw new ArgumentException("oldValue.Length is 0", nameof(oldValue));
}

newValue = newValue ?? string.Empty;

var readOnlySpan = AsSpan();
int endIndex = startIndex + count;
int matchCount = 0;

for (int i = startIndex; i < endIndex; i += oldValue.Length)
{
var span = readOnlySpan.Slice(i, endIndex - i);
var pos = span.IndexOf(oldValue.AsSpan(), StringComparison.Ordinal);
if (pos == -1)
{
break;
}
i += pos;
matchCount++;
}

if (matchCount == 0)
return;

var newBuffer = ArrayPool<char>.Shared.Rent(Math.Max(DefaultBufferSize, Length + (newValue.Length - oldValue.Length) * matchCount));

buffer.AsSpan(0, startIndex).CopyTo(newBuffer);
int newBufferIndex = startIndex;

for (int i = startIndex; i < endIndex; i += oldValue.Length)
{
var span = readOnlySpan.Slice(i, endIndex - i);
var pos = span.IndexOf(oldValue.AsSpan(), StringComparison.Ordinal);
if (pos == -1)
{
var remain = readOnlySpan.Slice(i);
remain.CopyTo(newBuffer.AsSpan(newBufferIndex));
newBufferIndex += remain.Length;
break;
}
readOnlySpan.Slice(i, pos).CopyTo(newBuffer.AsSpan(newBufferIndex));
newValue.AsSpan().CopyTo(newBuffer.AsSpan(newBufferIndex + pos));
newBufferIndex += pos + newValue.Length;
i += pos;
}

if (buffer.Length != ThreadStaticBufferSize)
{
ArrayPool<char>.Shared.Return(buffer);
}
buffer = newBuffer;
index = newBufferIndex;
}

// Output

/// <summary>Copy inner buffer to the destination span.</summary>
Expand Down
Loading

0 comments on commit b735e58

Please sign in to comment.