Проблема
Мне нужно иметь возможность захватывать подмножество символов, но при этом следить за тем, чтобы каждый набор не превышал указанный предел и должен заканчиваться определенным набором символов. Например, скажем, у меня есть целая книга символов, мне нужно разбить ее на набор из 300 символов, но убедиться, что ни один из этих фрагментов не разделен на середину предложения.
Чтобы поместить это в псевдокод, мне в основном нужны:
foreach(var chunk in text.Chunk(x).ButAlsoSplitOnFirst('.', '!', '?', etc...);
Мое решение
public static class StringExtensions
{
public static IEnumerable<string> ChunkWithDelimeters(this string source, int maxChar, params char[] delimeters)
{
var sourceSpan = source.AsSpan();
var items = new List<string>();
var pointer = 0;
var lazyPointer = 0;
while (pointer <= sourceSpan.Length)
{
bool foundMatch = false;
pointer = Math.Min(lazyPointer + maxChar, sourceSpan.Length);
if (pointer == sourceSpan.Length)
{
items.Add(sourceSpan.Slice(lazyPointer, pointer - lazyPointer).TrimStart().ToString());
break;
}
for (var j = pointer; j >= lazyPointer; j--)
{
var tempChar = sourceSpan[j];
if (delimeters.Contains(tempChar))
{
foundMatch = true;
items.Add(sourceSpan.Slice(lazyPointer, j + 1 - lazyPointer).TrimStart().ToString());
lazyPointer = j + 1;
}
}
if (!foundMatch)
{
items.Add(sourceSpan.Slice(lazyPointer, pointer - lazyPointer).Trim().ToString());
lazyPointer = pointer;
}
}
return items;
}
}
Контрольные точки
| source.Length | maxChar | разделители | Среднее (мкс) | Ошибка (мкс) | StdDev (мкс) |
|---|---|---|---|---|---|
| 7320000 | 30 | ‘.’ | 73 622,739 | 1 412 4035 | 1,252,0589 |
| 439200 | 30 | ‘.’ | 4,122,574 | 68,6392 | 60,8468 |
| 731 | 30 | ‘.’ | 5,125 | 0,0466 | 0,0436 |
Мой вопрос
Какое оптимальное решение этой проблемы? Есть ли способ улучшить производительность моего решения?
1 ответ
Разве здесь не нужно тестировать разделители? Читая ваш код, я предполагаю, что мы хотим максимально увеличить фрагмент, но если внутри фрагмента есть разделители, мы хотим его разбить. Если последний раздел помещается во весь кусок, он не проверяет разделители.
if (pointer == sourceSpan.Length)
{
items.Add(sourceSpan.Slice(lazyPointer, pointer - lazyPointer).TrimStart().ToString());
break;
}
Также вы можете использовать IndexOfAny вместо цикла в конце
ПРЕДУПРЕЖДЕНИЕ этот код не тестировался и просто использовал пример
var slice = sourceSpan.Slice(lazyPointer, pointer - lazyPointer);
var contains = slice.IndexOfAny(delimeters);
while (contains > -1)
{
items.Add(slice.Slice(0, contains + 1).ToString());
slice = slice.Slice(contains + 1);
contains = slice.IndexOfAny(delimeters);
}
Теперь еще несколько моментов, которые, я думаю, вам следует сначала разделить по разделителям, а затем по кускам. Вместо кусков — разделители. Если у меня есть строка «12345.6789.8.654321», разделенная на 7. Первые два выходящих будут «12345»

Также, если хотите пофантазировать, можете заглянуть в этот блог. meziantou.net/split-a-string-into-lines-without-allocation.htm
— CharlesNRice