【问题标题】:Best way to split string into lines with maximum length, without breaking words将字符串拆分为最大长度的行而不破坏单词的最佳方法
【发布时间】:2014-03-13 03:23:04
【问题描述】:

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

与往常一样,我非常清楚字符串是不可变的,最好使用 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();
}

【问题讨论】:

    标签: c# string split stringbuilder


    【解决方案1】:

    即使这篇文章已经发布了 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|$)");
    }
    

    注意事项

    记得导入正则表达式(using System.Text.RegularExpressions;

    您可以在比赛中使用字符串插值:
    $@"(.{{1,{maximumLineLength}}})(?:\s|$)"

    MatchCollection 的工作方式几乎与Array 相似

    匹配示例与解释here

    【讨论】:

    【解决方案2】:

    如何解决这个问题:

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

    【讨论】:

    • 这当然很聪明,但它不会拆分超过最大行长的单词,因此不满足条件。我还怀疑大多数人很难一眼看出它在做什么——它是为我做的。
    • @ToboldHornblower - 它确实会拆分超过最大长度的单词。中间的while 循环就是这样做的。另外,我正在考虑编写一个扩展方法来清理此逻辑以使其更简单,因为我发现自己编写了很多此类函数。
    • 我在一些测试数据上运行它,它没有拆分长词。如果我错过了什么,我很抱歉,我会再试一次。
    • 只用一个词(ThisIsAReallLongWordThatShouldEasilyExceedTheMaximumPermissibleLineLength)试了一下,它没有拆分它,它打印出来。当它是句子的一部分时也会发生同样的情况。
    • 啊,我明白了 - 如果它不是句子中的第一个或最后一个单词,则拆分该单词,否则,保持原样。
    【解决方案3】:

    我不认为你的解决方案太糟糕了。但是,我确实认为您应该将三元组分解为 if else,因为您要测试相同的条件两次。您的代码也可能有错误。根据您的描述,您似乎需要行

    这是我的解决方案。

    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 个字符宽度的行。我的方法在 14 毫秒内完成,而您的方法是 1373 毫秒。

    【讨论】:

    • 感谢您对我两次测试相同条件的观察,这是我必须解决的问题。
    【解决方案4】:

    试试这个(未经测试)

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

    【讨论】:

    • 这与我看到的采用拆分成单词方法的示例一致。这里的一个问题是任何超过最大行长度的单词(尽可能不可能)都不会被拆分(这将违反没有行超过最大值的约束)。
    • 您可以将其添加为特殊情况/条件并按照您的喜好进行处理。抛出异常? (如果不允许呢?)
    • 这段代码有两个问题(1)它在每行的开头放置一个空格,(2)它总是错过最后一行。
    • 好多了,但仍然不会拆分超过最大行长的单个单词。
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      一种使用递归方法和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);
      }
      

      【讨论】:

        猜你喜欢
        • 2017-08-18
        • 1970-01-01
        • 1970-01-01
        • 2010-12-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-31
        相关资源
        最近更新 更多