使用Regex获取包含C#.NET中前N个单词的子字符串

时间:2017-01-02 08:55:23

标签: c# regex string

我想获取一个包含给定字符串中前N个字的字符串。 E.g。

获取第一个 5个字快速布朗,fox1跳过懒狗 应该返回 quick_brown,fox1跳过

请注意,单词包括字母,数字和_(基本上是\ W +匹配),并保留所有原始分隔符(例如)。

我设法使用经典的C#代码这样做:

public static bool IsWordChar(this char c)
{
    return char.IsLetterOrDigit(c) || c == '_';
}

public static string GetFirstWords(string s, int wordCount, string truncateSuffix = " [...]")
{
    var sb = new StringBuilder();
    int currWordCount = 0;
    char prevC = '\0';
    foreach (var c in s)
    {
        sb.Append(c);
        if (!c.IsWordChar() && prevC.IsWordChar())
            currWordCount++;

        if (currWordCount >= wordCount)
        {
            if (sb.Length < s.Length)
                sb.Append(truncateSuffix);

            return sb.ToString();
        }

        prevC = c;
    }

    // adding last word, if necessary
    if (prevC.IsWordChar())
        sb.Append(prevC);

    return sb.ToString();
}

它的工作速度足以满足我的需求(O(n)),但我想知道是否可以使用正则表达式来实现。

我尝试使用\W+并进行前N次匹配,但我从原始文本中删除了实际的非单词分隔符。

问题:C#正则表达式是否等同于上述代码?

感谢。

3 个答案:

答案 0 :(得分:1)

我会使用字边界(\b)来搜索字词,而不仅仅是\w\W

如果我稍微修改你的问题,搜索前N个单词和N-1之间的单词&#39;你或许可以使用

Regex.Match("The quick_brown, fox1 jumps over the lazy dog", @"^(\b.+?\b){9}")

获得N = 5的预期结果。

请注意,这假设输入以单词开头。

答案 1 :(得分:1)

提取包含较长字符串中前五个单词的siubstring的正则表达式是

@"^\W*\w+(?:\W+\w+){4}"

请参阅regex demo

<强>详情:

  • ^ - 字符串的开头
  • \W* - 零个或多个非单词符号
  • \w+ - 1 +字符号
  • (?:\W+\w+){4} - 4个序列(如果输入字符串中的字数可能少于5,并且预期输出是整个字符串,则替换为{0,4}):
    • \W+ - 1 +非单词字符
    • \w+ - 1 + word chars。

正则表达式是否更高效,您需要在C#中测试解决方案。要有效地使用正则表达式,请使用readonly声明为RegexOptions.Compiled字段,然后使用Regex.Match进行调用。请参阅C# demo

private static readonly Regex rxFirst5Words = new Regex(@"^\W*\w+(?:\W+\w+){4}", RegexOptions.Compiled);
// ...
var s = "The quick_brown, fox1 jumps over the lazy dog";
var result = rxFirst5Words.Match(s);
if (result.Success)
    Console.WriteLine(result.Value);

答案 2 :(得分:0)

对于那些想要快速编写代码的人,我根据接受的答案发布我正在使用的代码:

public static string GetFirstWordsRegEx(string s, int wordCount, string truncateSuffix = " [...]")
{
    // replace with string.Format for C# less than 6.0
    string pattern = $@"^\W*\w+(?:\W+\w+){{{wordCount - 1}}}";
    var regex = new Regex(pattern);
    var match = regex.Match(s);
    if (!match.Success)
        return s;

    var ret = match.Value;
    return ret.Length < s.Length ? ret + truncateSuffix : ret;
}

如果小于wordCount,它会获得第一个wordCount字及其分隔符或所有文字。此外,允许在发生截断时附加后缀。

<强> [编辑]

Wiktor Stribiżew对哪个解决方案具有更高性能进行测试存有疑问。我将称之为第一个解决方案&#34; classic&#34;,第二个&#34; Regex normal&#34;第三个&#34;正则表达式编译&#34;。

第一个在问题范围内,第二个在答案的开头,第三个在下面:

    // generated all possible text truncation patterns
    private static readonly List<Regex> FirstWordRegexes = new List<Regex>
    {
        new Regex(@"^\W*\w+(?:\W+\w+){0}", RegexOptions.Compiled),
        new Regex(@"^\W*\w+(?:\W+\w+){1}", RegexOptions.Compiled),
        new Regex(@"^\W*\w+(?:\W+\w+){2}", RegexOptions.Compiled),
        new Regex(@"^\W*\w+(?:\W+\w+){3}", RegexOptions.Compiled),
        new Regex(@"^\W*\w+(?:\W+\w+){4}", RegexOptions.Compiled),
        new Regex(@"^\W*\w+(?:\W+\w+){5}", RegexOptions.Compiled),
        new Regex(@"^\W*\w+(?:\W+\w+){6}", RegexOptions.Compiled),
        // ...
        // removed for brevity
        // ...
        new Regex(@"^\W*\w+(?:\W+\w+){147}", RegexOptions.Compiled),
        new Regex(@"^\W*\w+(?:\W+\w+){148}", RegexOptions.Compiled),
        new Regex(@"^\W*\w+(?:\W+\w+){149}", RegexOptions.Compiled),
    };

    public static string GetFirstWordsRegExOptimized(string s, int wordCount, string truncateSuffix = " [...]")
    {
        var regex = FirstWordRegexes[wordCount-1];
        var match = regex.Match(s);
        if (!match.Success)
            return s;

        var ret = match.Value;
        return ret.Length < s.Length ? ret + truncateSuffix : ret;
    }

测试代码

var sw = new Stopwatch();
var rand = new Random();
const int minValue = 20;
const int maxValue = 150;

#region warm up
sw.Start();
Console.WriteLine($"Warming up...");

// _testStrings contains 100K random real texts which may be longer or not than first words truncation value
foreach (var str in _testStrings)
{
    var dummy = str;
}
Console.WriteLine($"Warm up took {sw.ElapsedMilliseconds} ms");
#endregion

#region Classic C# approach
foreach (var str in _testStrings)
{
    int wordCount = rand.Next(minValue, maxValue);
    var firstWords = Utils.GetFirstWords(str, wordCount);
}
Console.WriteLine($"Classic code took {sw.ElapsedMilliseconds} ms");
sw.Restart();
#endregion

#region Uncompiled regex
sw.Start();
foreach (var str in _testStrings)
{
    int wordCount = rand.Next(minValue, maxValue);
    var firstWords = Utils.GetFirstWordsRegEx(str, wordCount);
}
Console.WriteLine($"Uncompiled regex code took {sw.ElapsedMilliseconds} ms");
sw.Restart();
#endregion

#region Compiled regex
sw.Start();
foreach (var str in _testStrings)
{

    int wordCount = rand.Next(minValue, maxValue);
    var firstWords = Utils.GetFirstWordsRegExOptimized(str, wordCount);
}
Console.WriteLine($"Compiled regex code took {sw.ElapsedMilliseconds} ms");
sw.Restart();
#endregion

<强>结果

  

经典代码耗时953毫秒

     

未编译的正则表达式代码耗时5559毫秒

     

编译的正则表达式代码耗时4194毫秒

正如预期的那样,编译的正则表达式比未编译的正则表达式更快。但是,经典版本要快得多。