我想获取一个包含给定字符串中前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#正则表达式是否等同于上述代码?
感谢。
答案 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毫秒
正如预期的那样,编译的正则表达式比未编译的正则表达式更快。但是,经典版本要快得多。