在C#中如何从句子中删除过长的单词?

6
我有一些包含句子的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)));
    }

理论上这种方法可行,但是如果长单词以空格以外的分隔符结尾,则会输出错误。例如:

This is sweeeeeeeeeeeeeeeet! And something more.

最终会变成这样:

This is sweeeeeeee And something more.

更新:
好吧,评论太好了,我意识到这可能有太多“如果”。也许忘记分隔符会更好。如果一个单词被修剪,可以用三个点来表示。以下是一些将单词修剪为最大5个字符的示例:

Apocalypse now! -> Apoca... now!

Apocalypse! -> Apoca...

!Example! -> !Exam...

This is sweeeeeeeeeeeeeeeet! And something more. - > This is sweee... And somet... more.


4
只有6个字符”意思是仅需要输出前六个字符,因此对于Antidisestablishmentarianism,您想要的输出仅为Antidi?你脑子里怎么会想到这么奇怪的想法? - http://imgc.allpostersimages.com/images/P-473-488-90/60/6067/4NZD100Z/posters/sam-gross-it-sort-of-makes-you-stop-and-think-doesn-t-it-new-yorker-cartoon.jpg :) - balexandre
而对于Pseudopseudohypoparathyroidism,您想要使用 Pseudo 吗? - devnull
2
也许你应该将字母缩减到最多2或3个?这样Sweeeeeeeeet就变成了Sweeet。 - Curtis
我觉得你需要更详细地解释一下你的要求。对于像!Example!Example!!!Exa?!mple这样的字符串,应该发生什么情况? - Chris
1
在您最后的示例中,结果不应该是这是非常好的...和一些其他的东西。吗? - sloth
9个回答

4

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

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

maxLength = 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.

这段代码的作用是查找字符序列(目前使用\w;应该改为更合理的内容),它们遵循模式(某些内容)(重复超过两次的字符)(其他内容)。替换时,它使用一个函数来检查是否超出了所需的最大长度,然后计算出重复部分实际可以有多长才能适合总长度,最后只裁剪重复部分到该长度。

这个代码比较混乱。它将无法截断本来很长的单词(例如第二个测试句子中的“something”),并且构成单词的字符集也需要改变。如果您想采用这种方法,请将其视为起点而不是最终解决方案。

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},具体取决于你的需求。


使用您更新后的正则表达式和 maxLenght = 5,第一句变成了 *Here's a text with toooo... long words...*,我认为应该改为 Here's a text with toooo... long words - sloth

4
我建议使用循环与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...

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

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


+1:这比我的快得多,但是 if (isLetter && currentWordLength <= maxWordLength) 应该改为 if (isLetter && currentWordLength < maxWordLength) - dav_i
对于输入 "This is sweeeeeeeeeeeeeeeet! Hellllllllllllooo wooooooooooooorld and something mooore.",结果是:"This i... .........................................................! ................................................... ................................................... ......... ........................... ..................." 这个更合适吗:This is sweeeeeet! Hellllllooo woooooorld and something mooore.?https://dev59.com/YnTYa4cB1Zd3GeqPtlcC#17592587 - Tim Schmelter
@Nolonar:好的,现在结果是_"This i ...! ... ... ... ... ...."_。我不确定这是否是期望的结果。我认为 OP 只是想要一个“修复”函数来处理过长的单词,所以将重复字符截断到最大长度。 - Tim Schmelter

2

试试这个:

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."


为什么要踩?这个按照需求工作。附言:谢谢,点赞者。 - dav_i

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

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

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


2

试试这个:

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) + "...";
        });
    }
}

2
使用一个带有零宽度正回顾断言的简单正则表达式(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个单词字符后的任何非空格字符,并用...替换匹配项。

结果:

这是一个有太多长单词的文本
这很甜...还有一些...
现在世界末日!
世界末日...
!例子...


2
更加实用的方法可能是根据@Curt在评论中提出的建议。
我立刻想不到任何英语单词中包含三个相同字母,而不是仅仅在六个字符后截断一个单词,你可以尝试这种方法:每当遇到连续两次出现相同的字符时,请删除它的其他连续出现。因此,“sweeeeeet”变成“sweet”,“tooooooo”变成“too”。
这将有一个额外的副作用,限制相同标点符号或空格的数量为2,以防止某些人过度热衷于这些!!!!!!!!。
如果您想考虑省略号(...),那么只需将“最大连续字符”计数设置为3即可。

1
虽然这在英语中可能有效(除了拟声词如“zzz”或“brrr”,以及一些罕见的单词),但在其他语言中却不行(例如德语,由于将复合词写成单个单词,如“Flussschifffahrt”)。 - Sebastian Negraszus

1
以下将限制重复字符的数量为6个。因此,对于您的输入“这是sweeeeeeeeeeeeeeeet!还有更多东西。”,输出将为:“This is sweeeeeet! And something more."
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."

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接