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

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

这是sweeeeeeeeeeeeeeeeet!还有更多。

最终看起来像这样:

这是 sweeeeeeee 还有更多。

更新:

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

现代启示录!-> 启示录……现在!

天启!-> 启示录...

!例子!-> !考试...

这是sweeeeeeeeeeeeeeeeet!还有更多。- >这是甜蜜的......还有一些......更多。

4

9 回答 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)(repeated character more than two times)(something else)。对于替换,它使用一个函数来检查它的长度是否超过了所需的最大长度,然后它计算重复部分真正适合总长度的长度,然后仅将重复部分减少到该长度。

很乱。它将无法截断非常长的单词(例如第二个测试句中的»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}根据您的需要,字符类的更好选择可能是类似的。

于 2013-07-11T11:43:08.430 回答
4

我建议使用StringBuilderwith 循环:

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

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

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

于 2013-07-11T11:47:03.243 回答
2

使用一个简单的正则表达式和一个零宽度正后向断言(LinqPad -ready示例代码):

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 个单词字符之后查找任何非空格字符,并将匹配替换为....

结果:

这是一段文字太长...长字
这是亲爱的...还有一些...更多。
天启……现在!
启示录...
!Examp...

于 2013-07-11T12:07:32.207 回答
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) + "...";
        });
    }
}
于 2013-07-11T12:01:37.503 回答
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."

于 2013-07-11T11:41:49.060 回答
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();
}
于 2013-07-11T11:47:55.013 回答
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

于 2013-07-11T11:56:29.090 回答
2

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

我无法立即想到任何连续包含 3 个相同字母的英文单词。您可以尝试这种方法,而不是简单地在 6 个字符后删除一个单词:每当您连续两次遇到相同的字符时,删除它的任何其他连续出现。因此“sweeeeeet”变成“sweet”,“tooooooo”变成“too”。

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

如果您想考虑省略号(...),那么只需将“最大连续字符”计数 == 3,而不是 2。

于 2013-07-11T12:43:45.247 回答
1

下面将重复字符的数量限制为 6。因此,对于您的输入“这是 sweeeeeeeeeeeeeeeeet!还有更多。” 输出将是:

“这太棒了!还有更多。”

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."
于 2013-07-11T12:10:26.970 回答