【发布时间】:2010-11-27 23:38:54
【问题描述】:
我正在循环浏览一个大文本文件,并寻找包含不超过 3 个不同字符的行(但是,这些字符可以无限重复)。我假设最好的方法是使用某种正则表达式。
感谢所有帮助。
(我正在用 PHP 编写脚本,如果有帮助的话)
【问题讨论】:
-
哇,感谢所有建议。我无法告诉你我对帮助的感激之情。
标签: php regex search string find
我正在循环浏览一个大文本文件,并寻找包含不超过 3 个不同字符的行(但是,这些字符可以无限重复)。我假设最好的方法是使用某种正则表达式。
感谢所有帮助。
(我正在用 PHP 编写脚本,如果有帮助的话)
【问题讨论】:
标签: php regex search string find
儿童正则表达式优化有趣的时间练习!以gnarf的正则表达式为起点:
^(.)\1*(.)?(?:\1*\2*)*(.)?(?:\1*\2*\3*)*$
我注意到这里有嵌套和连续的 *s,这会导致很多回溯。例如,在“abcaaax”中,它将尝试将最后一个字符串“a”匹配为长度为 3 的单个 \1*、长度为 2 的 \1* 后跟单个 \1、\1 后跟 2 长度\1*,或三个单匹配 \1。当你有更长的字符串时,这个问题会变得更糟,特别是当由于正则表达式没有什么可以阻止 \1 与 \2 相同的字符时。
^(.)\1*(.)?(?:\1|\2)*(.)?(?:\1|\2|\3)*$
这是在 Python 的 PCRE 匹配器上测试的原始速度的两倍多。 (这比在 PHP 中设置要快,抱歉。)
这仍然有一个问题,(.)? 无法匹配任何内容,然后继续进行其余的匹配。 \1|\2 仍将匹配 \1 即使没有匹配的 \2,这会导致潜在的回溯尝试在无法匹配时更早地引入 \1|\2 和 \1|\2|\3 子句。这可以通过在整个尾随子句周围移动 ? 可选性来解决:
^(.)\1*(?:(.)(?:\1|\2)*(?:(.)(?:\1|\2|\3)*)?)?$
这又是原来的两倍。
还有一个潜在的问题是 \1、\2 和 \3 中的任何一个都可以是同一个字符,当表达式不匹配时可能会导致更多的回溯。这将通过使用否定前瞻来阻止它与前一个字符不匹配:
^(.)\1*(?:(?!\1)(.)(?:\1|\2)*(?:(?!\1|\2)(.)(?:\1|\2|\3)*)?)?$
但是在 Python 中使用我的随机测试数据,我并没有注意到由此带来的显着加速。根据测试数据,您在 PHP 中的表现可能会有所不同,但它可能已经足够好了。如果在此处可用,所有格匹配 (*+) 可能会有所帮助。
没有任何正则表达式比更易于阅读的 Python 替代方法表现更好:
len(set(s))<=3
PHP 中的类似方法可能是count_chars:
strlen(count_chars($s, 3))<=3
我还没有测试过速度,但我非常希望它比正则表达式更快,而且更易读。
所以基本上我只是完全浪费时间摆弄正则表达式。不要浪费你的时间,在使用正则表达式之前先寻找简单的字符串方法!
【讨论】:
冒着被否决的风险,我建议正则表达式不适用于处理这种情况。
您可以匹配一个字符或一组字符,但您不能让它记住已经找到一组字符以排除进一步匹配的字符。
我建议您保留一个字符集,在开始新行之前将其重置,并在越行时添加元素。一旦集合中的元素数超过 3,就删除当前行并继续下一行。
【讨论】:
也许这会起作用:
preg_match("/^(.)\\1*(.)?(?:\\1*\\2*)*(.)?(?:\\1*\\2*\\3*)*$/", $string, $matches);
// aaaaa:Pass
// abababcaaabac:Pass
// aaadsdsdads:Pass
// aasasasassa:Pass
// aasdasdsadfasf:Fail
说明:
/
^ #start of string
(.) #match any character in group 1
\\1* #match whatever group 1 was 0 or more times
(.)? #match any character in group 2 (optional)
(?:\\1*\\2*)* #match group 1 or 2, 0 or more times, 0 or more times
#(non-capture group)
(.)? #match any character in group 3 (optional)
(?:\\1*\\2*\\3*)* #match group 1, 2 or 3, 0 or more times, 0 or more times
#(non-capture group)
$ #end of string
/
另一个好处,$matches[1], [2], [3] 将包含您想要的三个字符。正则表达式查找第一个字符,然后将其存储并匹配,直到找到该字符以外的其他字符,将其作为第二个字符捕获,尽可能多次匹配这些字符中的任何一个,捕获第三个字符,然后匹配所有三个,直到匹配失败或字符串结束并且测试通过。
编辑
由于解析引擎和回溯的工作方式,这个正则表达式会快得多,请阅读 bobince 的答案以获得解释:
/^(.)\\1*(?:(.)(?:\\1|\\2)*(?:(.)(?:\\1|\\2|\\3)*)?)?$/
【讨论】:
'+' 以使它们具有所有格时,它的表现优于其他所有量词。
对我来说 - 作为一名具备足够正则表达式知识的程序员,这听起来不像是一个只能使用 Regexp 解决的问题。
您更有可能需要构建一个 hashMap/array 数据结构 key: character value:count 并迭代大文本文件,为每一行重建映射。在每个新字符处检查已经遇到的字符数是否为 2,如果是,则跳过当前行。
但是如果一个疯狂的正则表达式黑客会想出一个解决方案,我会很惊讶。
【讨论】: