从 C# 中的句子中修剪过长的单词

本文关键字:单词 句子 修剪 | 更新日期: 2023-09-27 18:21:54

我有包含句子的C#字符串。有时这些句子没问题,有时它们只是用户生成的随机字符。我想做的是修剪这些句子中的单词。例如,给定以下字符串:

var stringWithLongWords = "Here's a text with tooooooooooooo long words";

我想通过过滤器运行它:

var trimmed = TrimLongWords(stringWithLongWords, 6);

要获得每个单词最多只能包含 6 个字符的输出:

"Here's a text with tooooo long words"

任何想法如何以良好的性能完成?.NET 中是否有任何内容可以自动处理此问题?

我目前正在使用以下代码:

    private static string TrimLongWords(string original, int maxCount)
    {
        return string.Join(" ", original.Split(' ').Select(x => x.Substring(0, x.Length > maxCount ? maxCount : x.Length)));
    }

这在理论上是有效的,但如果长字以空格以外的分隔符结尾,它会提供错误的输出。例如:

这是呵呵呵!还有更多。

最终看起来像这样:

这是 sweeeeeeee 还有更多的东西。

更新:

好吧,评论非常好,我意识到这可能有太多的"如果"。如果忘记分隔符可能会更好。相反,如果一个单词被修剪,它可以用三个点显示。下面是一些将单词修剪为最多 5 个字符的示例:

现在启示录

! -> 启示录...现在!

启示录! -> 启示录...

!例!-> !考试。。。

这是呵呵呵!还有更多。-> 这是斯威...还有...更多。

从 C# 中的句子中修剪过长的单词

编辑:由于要求发生了变化,我将保持正则表达式的精神:

Regex.Replace(original, string.Format(@"('p{{L}}{{{0}}})'p{{L}}+", maxLength), "$1...");

最大长度 = 6 时的输出:

Here's a text with tooooo... long words
This is sweeee...! And someth... more.

下面的旧答案,因为我喜欢这种方法,即使它有点......凌乱:-(。


我拼凑了一个小正则表达式替代品来做到这一点。它现在在PowerShell中(用于原型设计;之后我会转换为 C#(:

'Here''s a text with tooooooooooooo long words','This is sweeeeeeeeeeeeeeeet! And something more.' |
  % {
    [Regex]::Replace($_, '('w*?)('w)'2{2,}('w*)',
      {
        $m = $args[0]
        if ($m.Value.Length -gt 6) {
          $l = 6 - $m.Groups[1].Length - $m.Groups[3].Length
          $m.Groups[1].Value + $m.Groups[2].Value * $l + $m.Groups[3].Value
        }
      })
  }

输出为:

Here's a text with tooooo long words
This is sweeet! And something more.

这样做是找到遵循模式(something)(repeated character more than two times)(something else)的字符(暂时'w;应该更改为合理的字符(。对于替换,它使用一个函数来检查长度是否超过所需的最大长度,然后计算重复部分实际上可以容纳在总长度中的长度,然后仅将重复部分削减到该长度。

太乱了。它将无法截断原本很长的单词(例如第二个测试句中的"某物"(,并且构成单词的字符集也需要更改。如果您想走这条路,请考虑这可能是一个起点,但不是完成的解决方案。

C# 代码:

public static string TrimLongWords(this string original, int maxCount)
{
    return Regex.Replace(original, @"('w*?)('w)'2{2,}('w*)",
        delegate(Match m) {
            var first = m.Groups[0].Value;
            var rep = m.Groups[1].Value;
            var last = m.Groups[2].Value;
            if (m.Value.Length > maxCount) {
                var l = maxCount - first.Length - last.Length;
                return first + new string(rep[0], l) + last;
            }
            return m.Value;
        });
}

字符类的更好选择可能是类似 'p{L} ,具体取决于您的需求。

我建议将StringBuilder与循环一起使用:

public string TrimLongWords(string input, int maxWordLength)
{
    StringBuilder sb = new StringBuilder(input.Length);
    int currentWordLength = 0;
    bool stopTripleDot = false;
    foreach (char c in input)
    {
        bool isLetter = char.IsLetter(c);
        if (currentWordLength < maxWordLength || !isLetter)
        {
            sb.Append(c);
            stopTripleDot = false;
            if (isLetter)
                currentWordLength++;
            else
                currentWordLength = 0;
        }
        else if (!stopTripleDot)
        {
            sb.Append("...");
            stopTripleDot = true;
        }
    }
    return sb.ToString();
}

这将比Regex或Linq更快。
maxWordLength == 6的预期成果:

"UltraLongWord"           -> "UltraL..."
"This-is-not-a-long-word" -> "This-is-not-a-long-word"

边缘情况maxWordLength == 0将导致:

"Please don't trim me!!!" -> "... ...'... ... ...!!!" // poor, poor string...

[此答案已更新,以适应问题中要求"..."]

(我刚刚意识到用 "..." 替换修剪后的子字符串引入了很多错误,修复它们使我的代码有点笨重,抱歉(

试试这个:

private static string TrimLongWords(string original, int maxCount)
{
   return string.Join(" ", 
   original.Split(' ')
   .Select(x => { 
     var r = Regex.Replace(x, @"'W", ""); 
     return r.Substring(0, r.Length > maxCount ? maxCount : r.Length) + Regex.Replace(x, @"'w", ""); 
   }));
}

然后TrimLongWords("This is sweeeeeeeeeeeeeeeet! And something more.", 5)就变成了"This is sweee! And somet more."

您可以使用正则表达式来查找这些重复项:


string test = "This is sweeeeeeeeeeeeeeeet! And sooooooomething more.";
string result = Regex.Replace(test, @"('w)'1+", delegate(Match match)
{
    string v = match.ToString();
    return v[0].ToString();
});

结果将是:


This is swet! And something more.

也许您可以使用拼写检查器服务检查纵的单词:http://wiki.webspellchecker.net/doku.php?id=installationandconfiguration:web_service

试试这个:

class Program
{
    static void Main(string[] args)
    {
        var stringWithLongWords = "Here's a text with tooooooooooooo long words";
        var trimmed = TrimLongWords(stringWithLongWords, 6);
    }
    private static string TrimLongWords(string stringWithLongWords, int p)
    {
        return Regex.Replace(stringWithLongWords, String.Format(@"['w]{{{0},}}", p), m =>
        {
            return m.Value.Substring(0, p-1) + "...";
        });
    }
}

使用带有零宽度正后看断言的简单正则表达式(LinqPad 就绪的示例代码(:

void Main()
{
    foreach(var s in new [] { "Here's a text with tooooooooooooo long words", 
                              "This is sweeeeeeeeeeeeeeeet! And something more.",
                              "Apocalypse now!",
                              "Apocalypse!",
                              "!Example!"})
        Regex.Replace(s, @"(?<='w{5,})'S+", "...").Dump();
}

它查找 5 个单词字符后的任何非空格字符,并将匹配项替换为 ...

结果:

这是一段文字,太...长词
这是啪...还有...更多。
天启...现在!
天启...
!唰...

更实用的方法可能是@Curt评论中建议的那样。

我无法立即想到任何连续包含 3 个相同字母的英语单词。 与其简单地在 6 个字符后截掉一个单词,不如尝试这种方法:每当您连续两次遇到同一个字符时,请删除它的任何其他连续出现。 因此,"sweeeeeet"变成了"甜","too"变成了"too"。

这将产生额外的副作用,即将相同标点符号或空格的数量限制为 2,以防有人对这些标点符号或空格过于热心!!!!!!!

如果你想考虑省略号 (...(,那么只需使"最大连续字符"计数 == 3,而不是 2。

这比正则表达式或 Linq 方法更有效。但是,它不会按单词拆分或添加...。恕我直言,空格(包括换行符或制表符(也应缩短。

public static string TrimLongWords(string original, int maxCount)
{
    if (null == original || original.Length <= maxCount) return original;
    StringBuilder builder = new StringBuilder(original.Length);
    int occurence = 0;
    for (int i = 0; i < original.Length; i++)
    {
        Char current = original[i];
        if (current == original.ElementAtOrDefault(i-1))
            occurence++;
        else
            occurence = 1;
        if (occurence <= maxCount)
            builder.Append(current);
    }
    return builder.ToString();
}

以下内容会将重复字符的数量限制为 6。因此,对于您的输入"这是sweeeeeeeet!还有更多",输出将是:

"这是斯威伊特!还有更多。

string s = "heloooooooooooooooooooooo worrrllllllllllllld!";
char[] chr = s.ToCharArray();
StringBuilder sb = new StringBuilder();
char currentchar = new char();
int charCount = 0;
foreach (char c in chr)
{
     if (c == currentchar)
     {
         charCount++;
     }
     else
     {
         charCount = 0;
     }
     if ( charCount < 6)
     {
         sb.Append(c);
     }
     currentchar = c;
 }
 Console.WriteLine(sb.ToString());
 //Output heloooooo worrrlllllld!

编辑:截断超过6个字符的单词:

string s = "This is sweeeeeeeeeeeeeeeet! And something more.";
string[] words = s.Split(' ');
StringBuilder sb = new StringBuilder();
foreach (string word in words)
{
    char[] chars = word.ToCharArray();
    if (chars.Length > 6)
    {
        for (int i = 0; i < 6; i++)
        {
            sb.Append(chars[i]);
        }
        sb.Append("...").Append(" ");
    }
    else { sb.Append(word).Append(" "); }
}
sb.Remove(sb.Length - 1, 1);
Console.WriteLine(sb.ToString());
//Output: "This is sweeee... And someth... more."