forked from linkdotnet/StringBuilder
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathValueStringBuilder.Replace.cs
187 lines (166 loc) · 8.03 KB
/
ValueStringBuilder.Replace.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
using System.Runtime.CompilerServices;
using System.Text;
namespace LinkDotNet.StringBuilder;
public ref partial struct ValueStringBuilder
{
/// <summary>
/// Replaces all instances of one character with another in this builder.
/// </summary>
/// <param name="oldValue">The character to replace.</param>
/// <param name="newValue">The character to replace <paramref name="oldValue"/> with.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Replace(char oldValue, char newValue) => Replace(oldValue, newValue, 0, Length);
/// <summary>
/// Replaces all instances of one character with another in this builder.
/// </summary>
/// <param name="oldValue">The character to replace.</param>
/// <param name="newValue">The character 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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Replace(char oldValue, char newValue, int startIndex, int count)
{
ArgumentOutOfRangeException.ThrowIfLessThan(startIndex, 0);
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex + count, Length);
for (var i = startIndex; i < startIndex + count; i++)
{
if (buffer[i] == oldValue)
{
buffer[i] = newValue;
}
}
}
/// <summary>
/// Replaces all instances of one rune with another in this builder.
/// </summary>
/// <param name="oldValue">The rune to replace.</param>
/// <param name="newValue">The rune to replace <paramref name="oldValue"/> with.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Replace(Rune oldValue, Rune newValue) => Replace(oldValue, newValue, 0, Length);
/// <summary>
/// Replaces all instances of one rune with another in this builder.
/// </summary>
/// <param name="oldValue">The rune to replace.</param>
/// <param name="newValue">The rune 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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Replace(Rune oldValue, Rune newValue, int startIndex, int count)
{
Span<char> oldValueChars = stackalloc char[2];
var oldValueCharsWritten = oldValue.EncodeToUtf16(oldValueChars);
ReadOnlySpan<char> oldValueCharsSlice = oldValueChars[..oldValueCharsWritten];
Span<char> newValueChars = stackalloc char[2];
var newValueCharsWritten = newValue.EncodeToUtf16(newValueChars);
ReadOnlySpan<char> newValueCharsSlice = newValueChars[..newValueCharsWritten];
Replace(oldValueCharsSlice, newValueCharsSlice, startIndex, count);
}
/// <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>empty</c>, instances of <paramref name="oldValue"/> are removed.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Replace(scoped ReadOnlySpan<char> oldValue, scoped ReadOnlySpan<char> newValue)
=> Replace(oldValue, newValue, 0, Length);
/// <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>
/// <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>empty</c>, instances of <paramref name="oldValue"/> are removed.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Replace(scoped ReadOnlySpan<char> oldValue, scoped ReadOnlySpan<char> newValue, int startIndex, int count)
{
ArgumentOutOfRangeException.ThrowIfLessThan(startIndex, 0);
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex + count, Length);
var length = startIndex + count;
var slice = buffer[startIndex..length];
if (oldValue.SequenceEqual(newValue))
{
return;
}
// We might want to check whether or not we want to introduce different
// string search algorithms for longer strings.
// I had checked initially with Boyer-Moore but it didn't make that much sense as we
// don't expect very long strings and then the performance is literally the same. So I went with the easier solution.
var hits = NaiveSearch.FindAll(slice, oldValue);
if (hits.IsEmpty)
{
return;
}
var delta = newValue.Length - oldValue.Length;
for (var i = 0; i < hits.Length; i++)
{
var index = startIndex + hits[i] + (delta * i);
// newValue is smaller than old value
// We can insert the slice and remove the overhead
if (delta < 0)
{
newValue.CopyTo(buffer[index..]);
Remove(index + newValue.Length, -delta);
}
// Same length -> We can just replace the memory slice
else if (delta == 0)
{
newValue.CopyTo(buffer[index..]);
}
// newValue is larger than the old value
// First add until the old memory region
// and insert afterwards the rest
else
{
newValue[..oldValue.Length].CopyTo(buffer[index..]);
Insert(index + oldValue.Length, newValue[oldValue.Length..]);
}
}
}
/// <summary>
/// Replaces all instances of one string with another in this builder.
/// </summary>
/// <param name="oldValue">The string to replace.</param>
/// <param name="newValue">Object to replace <paramref name="oldValue"/> with.</param>
/// <remarks>
/// If <paramref name="newValue"/> is from type <see cref="ISpanFormattable"/> an optimized version is taken.
/// Otherwise the ToString method is called.
/// </remarks>
/// /// <typeparam name="T">Any type.</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReplaceGeneric<T>(ReadOnlySpan<char> oldValue, T newValue)
=> ReplaceGeneric(oldValue, newValue, 0, Length);
/// <summary>
/// Replaces all instances of one string with another in this builder.
/// </summary>
/// <param name="oldValue">The string to replace.</param>
/// <param name="newValue">Object 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 from type <see cref="ISpanFormattable"/> an optimized version is taken.
/// Otherwise the ToString method is called.
/// </remarks>
/// /// <typeparam name="T">Any type.</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReplaceGeneric<T>(ReadOnlySpan<char> oldValue, T newValue, int startIndex, int count)
{
if (newValue is ISpanFormattable spanFormattable)
{
Span<char> tempBuffer = stackalloc char[24];
if (spanFormattable.TryFormat(tempBuffer, out var written, default, null))
{
Replace(oldValue, tempBuffer[..written], startIndex, count);
}
}
else
{
Replace(oldValue, (newValue?.ToString() ?? string.Empty).AsSpan(), startIndex, count);
}
}
}