【问题标题】:Divide long string into 60 character long lines but don't break words将长字符串分成 60 个字符的长行,但不要分词
【发布时间】:2010-12-13 06:41:08
【问题描述】:

必须有更好的方法来做到这一点。 我只想将长字符串拆分为 60 个字符行,但不要分词。因此,它不必添加最多 60 个字符,只需少于 60 个即可。

下面的代码是我所拥有的,它可以工作,但我认为有更好的方法。有人吗?

修改为使用 StringBuilder 并修复了删除重复单词的问题。 也不想使用正则表达式,因为我认为这会比我现在的效率低。

public static List<String> FormatMe(String Message)
{
    Int32 MAX_WIDTH = 60;
    List<String> Line = new List<String>();
    String[] Words;

    Message = Message.Trim();
    Words = Message.Split(" ".ToCharArray());

    StringBuilder s = new StringBuilder();
    foreach (String Word in Words)
    {
        s.Append(Word + " ");
        if (s.Length > MAX_WIDTH)
        {
            s.Replace(Word, "", 0, s.Length - Word.Length);
            Line.Add(s.ToString().Trim());
            s = new StringBuilder(Word + " ");
        }
    }

    if (s.Length > 0)
        Line.Add(s.ToString().Trim());

    return Line;
}

谢谢

【问题讨论】:

  • 您是在寻找更高效的算法还是更下一个编码器来阅读这种友好的方法?
  • 有什么理由不使用泛型?
  • 1.您的代码无法按预期工作。 s.Replace(Word,"") 不仅会替换最后一个,还会替换字符串中 Word 的任何部分匹配项。 2. s+=... 你最终会创建太多的临时字符串对象,因为字符串在 C# 中是不可变的。尝试使用 stringbuilder 或 string.Join() 方法。
  • 我正在寻找更高效的东西。 Chansik 我感谢您指出这一点。我修好了它。并决定在我使用它时使用字符串生成器。
  • 我将如何使用泛型来使其变得更好?

标签: c# .net string


【解决方案1】:

另一个(现已测试)示例,与Keith approach 非常相似:

static void Main(string[] args)
{
    const Int32 MAX_WIDTH = 60;

    int offset = 0;
    string text = Regex.Replace(File.ReadAllText("oneline.txt"), @"\s{2,}", " ");
    List<string> lines = new List<string>();
    while (offset < text.Length)
    {
        int index = text.LastIndexOf(" ", 
                         Math.Min(text.Length, offset + MAX_WIDTH));
        string line = text.Substring(offset,
            (index - offset <= 0 ? text.Length : index) - offset );
        offset += line.Length + 1;
        lines.Add(line);
    }
}

我在this file 上运行了它,所有换行符手动替换为“”。

【讨论】:

  • String.LastIndexOf(字符串值,int startIndex)。如何保证 text.Substring(offset, text.LastIndexOf(" ", MAX_WIDTH)) 返回的字符串少于 60 个字符?
  • 我改成 LastIndexOf(" ", offset+MAX_WIDTH);现在应该可以工作(仍未测试)
  • @Rubens,更改不保证返回少于 60 个字符的字符串,因为您没有更改“文本”。假设您有 10K 字符串 'text' 以 " " 结尾,text.LastIndexOf(" ", offset + MAX_WIDTH) 将返回最后一个 " " 的索引,而不是在 60 个字符的限制内。
  • @Chansik Im,现在我测试了它;请再看一遍; ty
  • @Rubens,我刚刚抓取了这个网页的 html 源代码并在上面运行了你的代码。您的方法似乎可以正常工作,直到它点击超过 60 个字符的 http 链接(希望 OP 不希望那里有超链接 :) + OP 也没有明确指定如何处理这种情况 :) 不完整的要求 :)) 。此外,即使最后一个单词符合 60 个字符的限制,您也可以在查找“”的最后一个索引时从该行中排除最后一个单词。顺便说一句,我认为这是一个不错的解决方案,尤其是对于大字符串对象。
【解决方案2】:

在正则表达式中,Match Evaluator 函数(一种匿名方法)完成繁重的工作并将新调整大小的行存储到 StringBuilder 中。我们不使用 Regex.Replace 方法的返回值,因为我们只是使用它的 Match Evaluator 函数作为从正则表达式调用内部完成换行的功能 - 只是为了它,因为我认为它很酷。

using System;
using System.Text;
using System.Text.RegularExpressions;

strInput 是您要转换的行。

int MAX_LEN = 60;
StringBuilder sb = new StringBuilder();
int bmark = 0; //bookmark position

Regex.Replace(strInput, @".*?\b\w+\b.*?", 
    delegate(Match m) {
        if (m.Index - bmark + m.Length + m.NextMatch().Length > MAX_LEN 
                || m.Index == bmark && m.Length >= MAX_LEN) {
            sb.Append(strInput.Substring(bmark, m.Index - bmark + m.Length).Trim() + Environment.NewLine);
            bmark = m.Index + m.Length;
        } return null;
    }, RegexOptions.Singleline);

if (bmark != strInput.Length) // last portion
    sb.Append(strInput.Substring(bmark));

string strModified = sb.ToString(); // get the real string from builder

还值得注意的是,匹配评估器m.Index == bmark &amp;&amp; m.Length &gt;= MAX_LEN 中的 if 表达式中的第二个条件是一个例外条件,以防单词长度超过 60 个字符(或超过设置的最大长度) - 它不会在这里被分解,但只单独存储在一行中 - 我想您可能想在现实世界中为该条件创建第二个公式以将其连字符或其他东西。

【讨论】:

  • 正则表达式可能需要调整以正确处理标点符号,但我已经完成了这个示例并继续前进 - 它很灵活,并提供了另一种完成换行的方法的要点 - 核心逻辑(内部匿名方法)只有几行,比作者发布的核心逻辑短,所以我认为这是一个可行的答案。是它周围的所有支撑物使它变得更大。
  • 如果您想将每个文本行捕获到集合元素或数组元素中,您可以轻松地将 List.Add(..) 替换为 StringBuilder.Append(..)。
【解决方案3】:

试试这个:

const Int32 MAX_WIDTH = 60;

string text = "...";
List<string> lines = new List<string>();
StringBuilder line = new StringBuilder();
foreach(Match word in Regex.Matches(text, @"\S+", RegexOptions.ECMAScript))
{
    if (word.Value.Length + line.Length + 1 > MAX_WIDTH)
    {
        lines.Add(line.ToString());
        line.Length = 0;
    }
    line.Append(String.Format("{0} ", word.Value));
}

if (line.Length > 0)
    line.Append(word.Value);

请同时查看:How do I use a regular expression to add linefeeds?

【讨论】:

    【解决方案4】:

    我将从保存原始字符串的长度开始。然后,从倒数开始,然后做减法,因为从最后一个单词开始,然后倒回去,我可能会比累积更快地低于 60。

    一旦我知道了多长时间,那么只需使用 StringBuilder 并为新字符串构建字符串。

    【讨论】:

      【解决方案5】:
      List<string> lines = new List<string>();
      while (message.Length > 60) {
        int idx = message.LastIndexOf(' ', 60);
        lines.Add(message.Substring(0, idx));
        message = message.Substring(idx + 1, message.Length - (idx + 1));
      }
      lines.Add(message);
      

      您可能需要稍作修改以处理多个空格,其中包含 >60 个字符的单词,...

      【讨论】:

      • message = message.Substring(idx + 1, message.Length - (idx + 1))
      • 一切都取决于 Substring 是否实际进行复制,或者它是否只是保留指向父字符串的指针(我不知道 C# 在这方面做了什么)。鲁本斯的回答通过保留起始索引而不是显式子串来避免这个问题。
      • 在 C# 中,字符串是不可变的,因此我认为 Substring 将返回一个新的字符串对象,而不是保留父字符串的指针。关于鲁本的回答,我也想和你一样,但意识到 LastIndexOf(char value, int startIndex) 会在从 startIndex 搜索时返回字符串中 value char 的最后一个索引,而不是 startIndex。因此,您的和 Ruben 的都不能按预期工作。
      • Java 具有不可变的字符串,并且执行我所描述的操作(请参阅docjar.com/html/api/java/lang/String.java.html)。你也错了 LastIndexOf ("azzzza".LastIndexOf('a', 3) == 0)。
      • 是的..我对 LastIndexOf 确实错了。这就是我来帖子更新评论的原因。但是,即使使用 Java,您仍然对子字符串有误。根据您提供的链接,子字符串“..Returns a new string that is a substring of this string”在Java中。
      【解决方案6】:

      另一个...

      public static string SplitLongWords(string text, int maxWordLength)
      {
          var reg = new Regex(@"\S{" + (maxWordLength + 1) + ",}");
          bool replaced;
          do
          {
              replaced = false;
              text = reg.Replace(text, (m) =>
              {
                  replaced = true;
                  return m.Value.Insert(maxWordLength, " ");                    
              });
          } while (replaced);
      
          return text;
      }
      

      【讨论】:

      • 欢迎来到stackoverflow!最好为示例代码提供简短描述以提高发布准确性:)
      【解决方案7】:

      我尝试了最初的解决方案,发现它不太奏效。我对其进行了一些修改以使其正常工作。它现在对我有用并解决了我遇到的问题。谢谢。 吉姆。

      public static List<String> FormatMe(String message)
          {
              int maxLength = 10;
              List<String> Line = new List<String>();
              String[] words;
      
              message = message.Trim();
              words = message.Split(" ".ToCharArray());
      
              StringBuilder sentence = new StringBuilder();
              foreach (String word in words)
              {
                  if((sentence.Length + word.Length) <= maxLength)
                  {
                      sentence.Append(word + " ");
      
                  }
                  else
                  {
                      Line.Add(sentence.ToString().Trim());
                      sentence = new StringBuilder(word + " ");
                  }
              }
      
              if (sentence.Length > 0)
                  Line.Add(sentence.ToString().Trim());
      
              return Line;
          }
      
          private void btnSplitText_Click(object sender, EventArgs e)
          {
              List<String> Line = new List<string>();
              string message = "The quick brown fox jumps over the lazy dog.";
              Line = FormatMe(message);
          }
      

      【讨论】:

        猜你喜欢
        • 2017-03-05
        • 2019-03-14
        • 2011-09-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-13
        • 1970-01-01
        • 2020-01-31
        • 2013-11-26
        相关资源
        最近更新 更多