在发现字符串附近或结尾处的一年时,我有一个关于正则表达式步数的问题。
采用如下字符串:
Chocolatechipcookie2017!
找到最后一年并不是真正的问题,有很多选择。有些比其他人快。然而,所有这些都有相当高的步数。
举个例子:
\d{2,}\b.{0,1}$
步数:24
或者:
\d{2,}.{0,1}$
步数:23
或(不是我最喜欢的):
$(?<=\d\d.)
步数:31
或更精确:
[12]{0,1}\d{2,3}.{0,1}$
步数:43
到目前为止,最快的:
^.*\d{2,}.{0,1}$
步数:10
我想要完成的是尽可能减少步数。在简短的字符串中,例如我的例子,问题不是太严重,但我的密码管理员可以并且很乐意生成任意长度的密码,并且有些服务实际上支持最多甚至超过512个字符的密码长度。
另外,我确实对此有一定的学术兴趣。因此,即使我的密码管理器的真实场景无关紧要,我仍然希望听到一些关于此的想法。
提前致谢!
答案 0 :(得分:1)
使用regex101计步器
^.*(\d{4}) 17
^.*\K\d{4} 16
\d{4} 23
注意:上面的第二个选项不适用于C#以及许多其他正则表达式。
注意:基准可能因系统而异。
^.*(\d{4}) ~915,000/s
^.*([0-9]{4}) ~1,030,000/s
\d{4} ~693,000/s
[0-9]{4} ~910,000/s
使用Wiktor
中提到的RegexOptions.RightToLeft
^.*(\d{4}) ~1,233,000/s
^.*([0-9]{4}) ~1,340,000/s
\d{4} ~698,000/s
[0-9]{4} ~909,000/s
显然,使用从右到左选项可以提高我们用例的性能。
下图显示了使用RightToLeft
选项时基准测试的比较。前10个结果用于[0-9]{4}
正则表达式,最后10个用于^.*([0-9]{4})
正则表达式。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
namespace Test
{
class Program
{
static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
List<string> regexes = new List<string> {
@"[0-9]{4}",
@"^.*([0-9]{4})"
};
foreach (string regex in regexes)
{
List<TimeSpan> times = new List<TimeSpan>();
for (int i = 0; i < 100; i++)
{
stopwatch.Start();
Regex myRegex = new Regex(regex, RegexOptions.RightToLeft);
string strTargetString = @"Chocolatechipcookie2017!";
for (int j = 0; j < 100000; j++)
{
foreach (Match myMatch in myRegex.Matches(strTargetString))
{
}
}
stopwatch.Stop();
times.Add(stopwatch.Elapsed);
}
TimeSpan average = TimeSpan.FromMilliseconds(times.Average(t => t.TotalMilliseconds));
Console.WriteLine($"Elapsed={average}");
}
Console.Read();
}
}
}
以上代码启用了RightToLeft
选项:
[0-9]{4}
:3.710秒^.*([0-9]{4})
:10.276秒以上代码没有RightToLeft
:
[0-9]{4}
:3.523秒^.*([0-9]{4})
:10.956秒根据RegexHero获得的步数计数器和基准测试结果,效果最佳的正则表达式为^.*([0-9]{4})
且启用了RightToLeft
选项。
根据使用上述代码的基准测试结果,效果最佳的正则表达式为[0-9]{4}
而未启用RightToLeft
选项。
注意:这些测试只是测试正则表达式执行的速度,而不考虑初始正则表达式执行后的数据操作的性能。