Skip to content

Commit 6a503cc

Browse files
author
LinkDotNet Bot
committed
Updating to newest release
2 parents 64c3039 + 55cb052 commit 6a503cc

File tree

6 files changed

+100
-241
lines changed

6 files changed

+100
-241
lines changed

CHANGELOG.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@ All notable changes to **ValueStringBuilder** will be documented in this file. T
66

77
## [Unreleased]
88

9+
## [2.4.0] - 2025-02-21
10+
11+
### Added
12+
13+
- Added `ToString(int)` (by @Joy-less in #239)
14+
- Added `AsSpan(int)`, `AsSpan(int, int)`, `AsSpan(Range)` (by @Joy-less in #239)
15+
16+
### Changed
17+
18+
- Optimized and simplified `Replace` (by @Joy-less in #238)
19+
- Simplified `IndexOf` and `LastIndexOf` (by @Joy-less in #238)
20+
21+
### Fixed
22+
23+
- Fixed `IndexOf` and `LastIndexOf` allowing out-of-bounds index when the string to find is empty (by @Joy-less in #238)
24+
925
## [2.3.1] - 2025-02-20
1026

1127
### Changed
@@ -473,7 +489,8 @@ This release brings extensions to the `ValueStringBuilder` API. For `v1.0` the `
473489

474490
- Initial release
475491

476-
[unreleased]: https://github.com/linkdotnet/StringBuilder/compare/2.3.1...HEAD
492+
[unreleased]: https://github.com/linkdotnet/StringBuilder/compare/2.4.0...HEAD
493+
[2.4.0]: https://github.com/linkdotnet/StringBuilder/compare/2.3.1...2.4.0
477494
[2.3.1]: https://github.com/linkdotnet/StringBuilder/compare/2.3.0...2.3.1
478495
[2.3.0]: https://github.com/linkdotnet/StringBuilder/compare/2.2.0...2.3.0
479496
[2.2.0]: https://github.com/linkdotnet/StringBuilder/compare/2.1.0...2.2.0

src/LinkDotNet.StringBuilder/IntegerSpanList.cs

Lines changed: 0 additions & 50 deletions
This file was deleted.

src/LinkDotNet.StringBuilder/NaiveSearch.cs

Lines changed: 0 additions & 127 deletions
This file was deleted.

src/LinkDotNet.StringBuilder/ValueStringBuilder.Replace.cs

Lines changed: 28 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -92,53 +92,45 @@ public void Replace(scoped ReadOnlySpan<char> oldValue, scoped ReadOnlySpan<char
9292
ArgumentOutOfRangeException.ThrowIfLessThan(startIndex, 0);
9393
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex + count, Length);
9494

95-
var length = startIndex + count;
96-
var slice = buffer[startIndex..length];
97-
98-
if (oldValue.SequenceEqual(newValue))
95+
if (oldValue.IsEmpty || oldValue.Equals(newValue, StringComparison.Ordinal))
9996
{
10097
return;
10198
}
10299

103-
// We might want to check whether or not we want to introduce different
104-
// string search algorithms for longer strings.
105-
// I had checked initially with Boyer-Moore but it didn't make that much sense as we
106-
// don't expect very long strings and then the performance is literally the same. So I went with the easier solution.
107-
var hits = NaiveSearch.FindAll(slice, oldValue);
100+
var index = startIndex;
101+
var remainingChars = count;
108102

109-
if (hits.IsEmpty)
103+
while (remainingChars > 0)
110104
{
111-
return;
112-
}
113-
114-
var delta = newValue.Length - oldValue.Length;
105+
var foundSubIndex = buffer.Slice(index, remainingChars).IndexOf(oldValue, StringComparison.Ordinal);
106+
if (foundSubIndex < 0)
107+
{
108+
break;
109+
}
115110

116-
for (var i = 0; i < hits.Length; i++)
117-
{
118-
var index = startIndex + hits[i] + (delta * i);
111+
index += foundSubIndex;
112+
remainingChars -= foundSubIndex;
119113

120-
// newValue is smaller than old value
121-
// We can insert the slice and remove the overhead
122-
if (delta < 0)
114+
if (newValue.Length == oldValue.Length)
123115
{
116+
// Just replace the old slice
124117
newValue.CopyTo(buffer[index..]);
125-
Remove(index + newValue.Length, -delta);
126118
}
127-
128-
// Same length -> We can just replace the memory slice
129-
else if (delta == 0)
119+
else if (newValue.Length < oldValue.Length)
130120
{
121+
// Replace the old slice and trim the unused slice
131122
newValue.CopyTo(buffer[index..]);
123+
Remove(index + newValue.Length, oldValue.Length - newValue.Length);
132124
}
133-
134-
// newValue is larger than the old value
135-
// First add until the old memory region
136-
// and insert afterwards the rest
137125
else
138126
{
127+
// Replace the old slice and append the extra slice
139128
newValue[..oldValue.Length].CopyTo(buffer[index..]);
140129
Insert(index + oldValue.Length, newValue[oldValue.Length..]);
141130
}
131+
132+
index += newValue.Length;
133+
remainingChars -= oldValue.Length;
142134
}
143135
}
144136

@@ -153,7 +145,7 @@ public void Replace(scoped ReadOnlySpan<char> oldValue, scoped ReadOnlySpan<char
153145
/// </remarks>
154146
/// /// <typeparam name="T">Any type.</typeparam>
155147
[MethodImpl(MethodImplOptions.AggressiveInlining)]
156-
public void ReplaceGeneric<T>(ReadOnlySpan<char> oldValue, T newValue)
148+
public void ReplaceGeneric<T>(scoped ReadOnlySpan<char> oldValue, T newValue)
157149
=> ReplaceGeneric(oldValue, newValue, 0, Length);
158150

159151
/// <summary>
@@ -164,24 +156,23 @@ public void ReplaceGeneric<T>(ReadOnlySpan<char> oldValue, T newValue)
164156
/// <param name="startIndex">The index to start in this builder.</param>
165157
/// <param name="count">The number of characters to read in this builder.</param>
166158
/// <remarks>
167-
/// If <paramref name="newValue"/> is from type <see cref="ISpanFormattable"/> an optimized version is taken.
168-
/// Otherwise the ToString method is called.
159+
/// If <paramref name="newValue"/> is <see cref="ISpanFormattable"/>, <c>TryFormat</c> is used.
160+
/// Otherwise, <c>ToString</c> is used.
169161
/// </remarks>
170162
/// /// <typeparam name="T">Any type.</typeparam>
171163
[MethodImpl(MethodImplOptions.AggressiveInlining)]
172-
public void ReplaceGeneric<T>(ReadOnlySpan<char> oldValue, T newValue, int startIndex, int count)
164+
public void ReplaceGeneric<T>(scoped ReadOnlySpan<char> oldValue, T newValue, int startIndex, int count)
173165
{
174166
if (newValue is ISpanFormattable spanFormattable)
175167
{
176-
Span<char> tempBuffer = stackalloc char[24];
168+
Span<char> tempBuffer = stackalloc char[128];
177169
if (spanFormattable.TryFormat(tempBuffer, out var written, default, null))
178170
{
179171
Replace(oldValue, tempBuffer[..written], startIndex, count);
172+
return;
180173
}
181174
}
182-
else
183-
{
184-
Replace(oldValue, (newValue?.ToString() ?? string.Empty).AsSpan(), startIndex, count);
185-
}
175+
176+
Replace(oldValue, newValue?.ToString() ?? string.Empty, startIndex, count);
186177
}
187178
}

0 commit comments

Comments
 (0)