最好的方法是将字符串分成最大长度的行,而不中断单词
本文关键字:单词 中断 方法 字符串 | 更新日期: 2023-09-27 17:49:45
我想把一个字符串分成指定最大长度的行,如果可能的话,不拆分任何单词(如果有一个单词超过了最大行长度,那么它将不得不被拆分)。
与往常一样,我敏锐地意识到字符串是不可变的,最好使用StringBuilder类。我看到过一些例子,其中字符串被分割成单词,然后使用StringBuilder类构建行,但下面的代码对我来说似乎"更整洁"。我在描述中提到了"最佳"而不是"最有效",因为我对代码的"口才"也很感兴趣。字符串永远不会太大,通常分成2行或3行,也不会发生数千行。
下面的代码真的很糟糕吗?
private static IEnumerable<string> SplitToLines(string stringToSplit, int maximumLineLength)
{
stringToSplit = stringToSplit.Trim();
var lines = new List<string>();
while (stringToSplit.Length > 0)
{
if (stringToSplit.Length <= maximumLineLength)
{
lines.Add(stringToSplit);
break;
}
var indexOfLastSpaceInLine = stringToSplit.Substring(0, maximumLineLength).LastIndexOf(' ');
lines.Add(stringToSplit.Substring(0, indexOfLastSpaceInLine >= 0 ? indexOfLastSpaceInLine : maximumLineLength).Trim());
stringToSplit = stringToSplit.Substring(indexOfLastSpaceInLine >= 0 ? indexOfLastSpaceInLine + 1 : maximumLineLength);
}
return lines.ToArray();
}
即使这篇文章是3年前的,我也想用Regex
来实现更好的解决方案:
如果你想分割字符串,然后使用要显示的文本,你可以使用:
public string SplitToLines(string stringToSplit, int maximumLineLength)
{
return Regex.Replace(stringToSplit, @"(.{1," + maximumLineLength +@"})(?:'s|$)", "$1'n");
}
另一方面,如果你需要一个集合,你可以这样:
public MatchCollection SplitToLines(string stringToSplit, int maximumLineLength)
{
return Regex.Matches(stringToSplit, @"(.{1," + maximumLineLength +@"})(?:'s|$)");
}
指出记得导入regex (using System.Text.RegularExpressions;
)
你可以在匹配中使用字符串插值:$@"(.{{1,{maximumLineLength}}})(?:'s|$)"
MatchCollection
几乎和Array
一样工作
在这里匹配示例与解释
如何作为一个解决方案:
IEnumerable<string> SplitToLines(string stringToSplit, int maximumLineLength)
{
var words = stringToSplit.Split(' ').Concat(new [] { "" });
return
words
.Skip(1)
.Aggregate(
words.Take(1).ToList(),
(a, w) =>
{
var last = a.Last();
while (last.Length > maximumLineLength)
{
a[a.Count() - 1] = last.Substring(0, maximumLineLength);
last = last.Substring(maximumLineLength);
a.Add(last);
}
var test = last + " " + w;
if (test.Length > maximumLineLength)
{
a.Add(w);
}
else
{
a[a.Count() - 1] = test;
}
return a;
});
}
我重做了这个,更喜欢这样:
IEnumerable<string> SplitToLines(string stringToSplit, int maximumLineLength)
{
var words = stringToSplit.Split(' ');
var line = words.First();
foreach (var word in words.Skip(1))
{
var test = $"{line} {word}";
if (test.Length > maximumLineLength)
{
yield return line;
line = word;
}
else
{
line = test;
}
}
yield return line;
}
我认为你的解决方案还不错。然而,我认为你应该把你的三进制分解成if else,因为你要测试同样的条件两次。您的代码也可能有bug。根据您的描述,似乎您希望行<= maxLineLength,但您的代码计算最后一个单词后的空间,并在<=比较中使用它,从而有效地<裁剪字符串的行为。>
这是我的解决方案。
private static IEnumerable<string> SplitToLines(string stringToSplit, int maxLineLength)
{
string[] words = stringToSplit.Split(' ');
StringBuilder line = new StringBuilder();
foreach (string word in words)
{
if (word.Length + line.Length <= maxLineLength)
{
line.Append(word + " ");
}
else
{
if (line.Length > 0)
{
yield return line.ToString().Trim();
line.Clear();
}
string overflow = word;
while (overflow.Length > maxLineLength)
{
yield return overflow.Substring(0, maxLineLength);
overflow = overflow.Substring(maxLineLength);
}
line.Append(overflow + " ");
}
}
yield return line.ToString().Trim();
}
它比你的解决方案长一点,但它应该更直接。它还使用了StringBuilder,因此对于大字符串来说速度要快得多。我对20,000个从1到11个字符不等的单词进行了基准测试,每个单词分成10个字符宽的行。我的方法在14ms内完成,而你的方法在1373ms内完成。
Try this(未经测试)
private static IEnumerable<string> SplitToLines(string value, int maximumLineLength)
{
var words = value.Split(' ');
var line = new StringBuilder();
foreach (var word in words)
{
if ((line.Length + word.Length) >= maximumLineLength)
{
yield return line.ToString();
line = new StringBuilder();
}
line.AppendFormat("{0}{1}", (line.Length>0) ? " " : "", word);
}
yield return line.ToString();
}
- ~比接受的答案快6倍
- 在释放模式(取决于行长) 比Regex版本快1.5倍以上
- 可选保留行尾空格或不保留行尾空格(regex版本总是保留空格)
static IEnumerable<string> SplitToLines(string stringToSplit, int maximumLineLength, bool removeSpace = true)
{
int start = 0;
int end = 0;
for (int i = 0; i < stringToSplit.Length; i++)
{
char c = stringToSplit[i];
if (c == ' ' || c == ''n')
{
if (i - start > maximumLineLength)
{
string substring = stringToSplit.Substring(start, end - start); ;
start = removeSpace ? end + 1 : end; // + 1 to remove the space on the next line
yield return substring;
}
else
end = i;
}
}
yield return stringToSplit.Substring(start); // remember last line
}
下面是用于测试速度的示例代码(同样,在您自己的机器上运行并在发布模式下测试以获得准确的计时)https://dotnetfiddle.net/h5I1GC
我的机器在发布模式下的时间。net 4.8
Accepted Answer: 667ms
Regex: 368ms
My Version: 117ms
我的要求是在30字符限制之前的最后一个空格处有一个换行符。我是这样做的。
private string LineBreakLongString(string input)
{
var outputString = string.Empty;
var found = false;
int pos = 0;
int prev = 0;
while (!found)
{
var p = input.IndexOf(' ', pos);
{
if (pos <= 30)
{
pos++;
if (p < 30) { prev = p; }
}
else
{
found = true;
}
}
outputString = input.Substring(0, prev) + System.Environment.NewLine + input.Substring(prev, input.Length - prev).Trim();
}
return outputString;
}
使用递归方法和ReadOnlySpan(已测试)的方法
public static void SplitToLines(ReadOnlySpan<char> stringToSplit, int index, ref List<string> values)
{
if (stringToSplit.IsEmpty || index < 1) return;
var nextIndex = stringToSplit.IndexOf(' ');
var slice = stringToSplit.Slice(0, nextIndex < 0 ? stringToSplit.Length : nextIndex);
if (slice.Length <= index)
{
values.Add(slice.ToString());
nextIndex++;
}
else
{
values.Add(slice.Slice(0, index).ToString());
nextIndex = index;
}
if (stringToSplit.Length <= index) return;
SplitToLines(stringToSplit.Slice(nextIndex), index, ref values);
}