我正在寻找C#中的快速方法来查找字符串中的所有日期(字符串是一个大文本,我要扫描大约200,000个不同的字符串)。
因为有很多方法可以写日期(例如2012年12月31日或2012年12月31日等等), 我正在使用这个正则表达式(它应该涵盖几乎所有常用的写日期方式):
string findDates =“(?:( \ d {1,4}) - /.- /.)|(?:(\s\d{1,2})\s+(jan(?:uary){0,1}\.{0,1}|feb(?:ruary){0,1 } \ {0,1} | 3月。(?:CH){0,1} \ {0,1} |四月(?:IL){0,1} \ {0,1} | \可能。 {0,1} |君(?:E){0,1} \ {0,1} |七月(?:y){0,1} \ {0,1} |译者:(?:UST) {0,1} \ {0,1} | SEP:{0,1} \ {0,1} | OCT(tember?)。(?:奥伯){0,1} \ {0,1}。 |十一月(?:余烬){0,1} \ {0,1} |分解(?:余烬)。{0,1} \ {0,1})\ S +(\ d {2,4}) )|:{0,1} \ {0,1} | 2月(:(扬(uary?)。(?:ruary){0,1} \ {0,1} | 3月。(?:CH) {0,1} \ {0,1} |四月。(?:IL){0,1} \ {0,1} |可以\ {0,1} |君(?:E){0, 1} \ {0,1} |七月(?:Y){0,1} \ {0,1} |八月(?:UST)。{0,1} \ {0,1} | SEP( ?:tember){0,1} \ {0,1} | OCT(:?奥伯){0,1} \ {0,1} |十一月(:?烬){0,1} \ {。 0,1} |分解(?:余烬){0,1} \ {0,1})\ S +([0-9] {1,2})[\ S,] +(\ d {2, 4}))“;
使用“RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace”标记。 此外,我试图预编译正则表达式,使其更快。
问题在于它非常慢(在某些文本上超过2秒) 有没有更好更有效的方法来做到这一点?
由于
答案 0 :(得分:3)
如果不对其进行测试,很难提出算法。我们可以推荐更慢的东西。所以它确实尝试了不同的选择。
你的表达看起来有点冗长,但我不能说这是问题的原因。大文件的2秒是可以的,但不适用于较小的文件,所以它都与它正在进行的工作的大小相关
我可以推荐的一种方法是采用两阶段流程。
第一个是筛选以捕获最可能匹配的那个,另一个是进一步检查匹配所在文件的那个部分。例如,'\ d {1,2} \ s *,\ s * \ d {4}'可能是日期的一部分,但查找它比查找有关Jan(uary)/ Feb的所有条件更好(ruary)/ MAR(CH)/ ....
还有一小段建议:首先让指标正确,在开始任何更改之前做好建立基本指标的功课。
如果您想提高效果,您必须在尝试改进之前获得一些硬性和快速的指标。
答案 1 :(得分:3)
这个表达总体上看起来很好,正如其他人所提到的那样,所有{0,1}
代替?
和(?:
而不是应用RegexOptions.ExplicitCapture
可能有点冗长。但这些不应该使表达缓慢。它们只会带来更好的可读性。
可能导致缓慢的原因是表达式中有很多回溯选项,通过使扩展的月份和。可选的。我想知道如果你将表达式改为仅应用可选项会发生什么。一次,在月份名称之后,如果您将月份名称设为贪婪组((?>pattern)
Nonbacktracking(或“贪婪”)子表达式,会发生什么。)
那样:
(jan(?:uary){0,1}\.{0,1}|feb(?:ruary){0,1}\.{0,1}|mar(?:ch){0,1}\.{0,1}|apr(?:il){0,1}\.{0,1}|may\.{0,1}|jun(?:e){0,1}\.{0,1}|jul(?:y){0,1}\.{0,1}|aug(?:ust){0,1}\.{0,1}|sep(?:tember){0,1}\.{0,1}|oct(?:ober){0,1}\.{0,1}|nov(?:ember){0,1}\.{0,1}|dec(?:ember){0,1}\.{0,1})\s+(\d{2,4}))
会变成:
(?>jan(uary)?|feb(ruary)?|mar(ch)?|apr(il)?|may|june?|july?|aug(ust)?|sep(tember)?|oct(ober)?|nov(ember)?|dec(ember)?)\.?\s+(\d{2,4}))
它不仅更短,我希望它更快。
然后在开头就有了表达式的一部分,这对我来说真的没有意义(?:(\d{1,4})- /.- /.)
要么在格式化中丢失了一些东西,要么就是没有帮助。
\ d {1,4}对于一年或任何其他日期部分是有意义的,但- /.- /.
之后根本没有意义。我认为你的意思是:
\d{1,4}[- /.]\d{1,2}[- /.]\d{1,2}
或该领域的某些事情。目前它捕获垃圾,可能不会加快匹配过程。
最后我同意Aliostad的观点,你可能最好不要试图找到一个不太精确的模式来找到初始候选者,然后使用DateTime.TryParseExact或另外一组表达式缩小结果。
您可以使用大量精确表达式,而不是创建“全局”表达式来查找候选项。你会发现使用Regex,在一个大输入上运行一些精确的表达式通常比运行一个带有很多|和s的表达式更便宜。
因此,将搜索分解为多个非常精确的表达式可能会带来更高的性能,这些可能是一个开始:
\b\d{1,2}[- .\\/]\d{1,2}[- .\\/](\d{2}|\d{4})\b
\b((jan|feb|mar|apr|jun|jul|aug|sep|oct|nov|dec)(.|[a-z]{0,10})|\d{1,2})[- .\\/,]\d{1,2}[- .\\/,](\d{2}|\d{4})\b
正如您所看到的,所有可选组都已从这些表达式中删除,从而使它们运行起来更快。我也删除了月份名称中的确切拼写,因为你可能想接受'sept'以及'sep'以及'september'
打破模式也提高了可读性:)。
最后一个提示:限制你需要回溯的可能字符数量,通过限制像\ s +之类的东西,你很少想要20,000个空格来匹配,但如果它们在你的源文档中,它会尝试匹配他们。 \ s {1,20}通常就足够了,并限制了引擎在没有真正没有匹配的情况下尝试匹配的能力。