将字符串拆分为具有最大长度的行的最佳方法,而不会破坏单词

时间:2014-03-13 03:23:04

标签: c# string split stringbuilder

我希望将一个字符串分成指定最大长度的行,如果可能的话,不分割任何单词(如果有一个超过最大行长度的单词,则必须将其拆分)。

与往常一样,我敏锐地意识到字符串是不可变的,并且最好使用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();
}

6 个答案:

答案 0 :(得分:8)

即使这篇文章是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|$)");
}

MatchCollection的工作方式几乎与Array

相同

答案 1 :(得分:6)

这是一个解决方案:

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;
                });
}

答案 2 :(得分:6)

我认为你的解决方案太糟糕了。但是,我确实认为你应该将你的三元分解成if if因为你正在测试两次相同的条件。您的代码也可能有错误。根据您的描述,您似乎需要行&lt; = maxLineLength,但您的代码会计算最后一个单词后的空格,并在&lt; =比较中使用它,从而有效地&lt;修剪过的字符串的行为。

这是我的解决方案。

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。

答案 3 :(得分:2)

试试这个(未经测试的)

    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();
    }

答案 4 :(得分:0)

我的要求是在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;
        }

答案 5 :(得分:0)

使用递归方法和ReadOnlySpan<T>(已测试)的方法

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);
}