编程与软件工程rexegg.com
史上最伟大的正则技巧(2014)
The Greatest Regex Trick Ever (2014)
2015 — 2021,它一次次回到首页
黑点是上首页(≥ 50 分)的那次投稿,空心点是没冲上去的投稿,点越大分数越高。
导读
这篇短文介绍一个正则技巧:把不想匹配的模式放在前面的分支里,把真正想保留的目标放进捕获组,例如 `"Tarzan"|(Tarzan)`,整体能“吃掉”排除项,但最终只读取捕获组里的目标。文章用排除引号中的 Tarzan、过滤 HTML 标签内容等例子演示这种“匹配但不捕获”的思路。
为什么经久不衰:它把“排除某物”转化为“匹配它但不捕获它”,思路简单却极实用,也能帮助理解捕获组和分支选择,因此多年后仍值得读。
HN 评论精选
给出技巧的 TL;DR 和多个可直接套用的排除式捕获组示例。
TL;DR:这个正则匹配 Tarzan,但不匹配 "Tarzan":`"Tarzan"|(Tarzan)`。你也可以把多个不想匹配的情形放进前面的分支;例如 `Tarzania|--Tarzan--|"Tarzan"|(Tarzan)` 只保留不匹配前三个模式的 Tarzan。更复杂的正则也可以,比如 `<img[^>]+>|(\w+)` 匹配不在 img 标签里的词,`<b>[^<]*</b>|([\w\s]+)` 匹配不被 `<b>` 标签包围的内容。
JoeCoder_查看英文原文
Here is the TL;DR. This regex matches Tarzan but not "Tarzan": "Tarzan"|(Tarzan) You can also include more than one case of what you don't want to match. This one also finds only the cases of Tarzan that don't match the first three patterns: Tarzania|--Tarzan--|"Tarzan"|(Tarzan) You can even use more complex regexes. This matches all words not in an image tag: <img[^>]+>|(\w+) And likewise this matches anything not surrounded by <b> tags: <b>[^<]*</b>|([\w\s]+)
指出该技巧依赖引擎对分支选择的顺序保证,跨引擎并不可靠。
这个技巧在实践中并不可靠,作者的讨论也有点混乱。作者似乎假设匹配引擎会以某种确定顺序检查不同分支,但一般正则语义并未规定 `|` 的优先级;POSIX 只要求返回最左最长匹配,而多数引擎为了效率并不严格遵循 POSIX,因此“先匹配排除项、再捕获目标”的保证并不普遍。
bza查看英文原文
This technique is unreliable in practice, and the author's discussion is confused. First, their explanation doesn't make sense. They're supposing that there's some determinacy in the order in which a matcher can be expected to examine the different possible matches. But that's provably not the case: if it were, then deterministic and non-determinsitic finite automata would be inequivalent. But the technique in question does seem to require some determinacy as to which of several alternatives will match against a string. Where does that determinacy come from? The semantics of the alternation operator (the '|') as usually formulated don't specify any preference among alternations. For that reason, POSIX additionally requires that a matcher return the longest possible match (and if there are several such, the leftmost is what must be returned). Where you do find an explicit guarantee concerning which of several different possible ways of matching will be preferred, it's almost certainly because the engine is aiming at POSIX compliance. Such compliance has a significant cost, though, as it requires the matcher to consider all possible matches (in order to find the largest). For that reason, most regex engines forego strict POSIX compliance and only guarantee that some match will be returned if one exists, not that that match will be the leftmost longest. Some engines offer the option of requesting strict POSIX behavior, but the default will always be to eagerly return the first match encountered (and recall the point above that there provably can't be a guarantee about the order in which matches are encountered, in general). You should never do this in production code unless you're sure that your matcher is POSIX-compliant.
建议拆分步骤而非写一个复杂正则。
更普遍的提示是:不必把所有事情塞进一个正则里,也不必一步得到最终结果。几乎所有“灾难级”正则都来自有人想一次做太多事。另一种做法是跑两遍:第一遍取出所有 Tarzan,第二遍从结果里过滤掉 "Tarzan"。
jandrese查看英文原文
The more general tip is that a single regex isn't the only tool you have. You don't have to get your final product one one step. Almost every "disaster" regex comes from someone trying to do too much at once. One other solution would have been to run the regex twice, once to pick up all instances of Tarzan, and a second on the results of the first to filter out all instances of "Tarzan".
用 lexer-parser 分工来理解该技巧的结构。
这个技巧可以看作解析中“词法分析器/语法分析器”分工的简化版:`|` 之间的子正则定义各类 token(词法部分),分组机制则选出唯一想保留的 token(一个极简的语法分析器)。
praptak查看英文原文
This trick may be thought of as a simplification of the systematic approach to parsing stuff, that is the lexer-parser division of responsibilities. The lexer uses regexes but only for splitting the input stream of characters into tokens. Identifiers, integers, operators, strings, keywords, opening brackets and whatnot - each type of token is defined by a regex. This part is hopefully deterministic and simple, although the lexer matches regexes for all kinds of tokens at once, which is why lexer generators are often used to generate lexers. The heavy lifting is done by the actual parser which tries to combine the tokens into something that makes sense from the point of the grammar. So in this trick the sub-regexes between |'s define the tokens (the lexer part) while the group mechanism selects the single token that we want to keep (a very very simple parser).
历次讨论
同类还值得读
开源应用架构
The Architecture of Open Source Applications
《开源软件架构》(AOSA)是一本在线免费书,邀请知名开源项目的作者亲自剖析其内部设计,涵盖 SQLAlchemy、LLVM、Audacity、Apache、Bash 等系统。
编程与软件工程上首页 9 次 · 2011–2024aosabook.org编辑精选编程语言简史:简短、不完整且多半错误
A Brief, Incomplete, and Mostly Wrong History of Programming Languages
一篇以戏谑、故意不准确的方式回顾编程语言史的博客文章,用夸张轶事和讽刺定义串起 Lisp、C、C++、Java、JavaScript、PHP 等语言。
编程与软件工程上首页 8 次 · 2009–2021james-iry.blogspot.comSQLite 是如何测试的
How SQLite is tested
SQLite 官方文档页,系统介绍 SQLite 的测试体系:多种测试方法(TCL 测试、模糊测试、回归测试、静态分析等)和海量测试用例,展示了这个小型开源项目如何靠极致测试获得…
编程与软件工程上首页 8 次 · 2009–2025sqlite.org编辑精选高级编程语言(2009)
Advanced programming languages (2009)
Matt Might 的经典文章,按用途推荐“高级编程语言”:如果追求就业就选主流语言,如果追求表达力、学术研究或创业,则推荐 Haskell、OCaml、Scala 等函数式语…
编程与软件工程上首页 8 次 · 2009–2022matt.might.net我的 Forth 与栈机器经历
Yosefk - My history with Forth & stack machines
作者以亲身经历回顾了使用 Forth 语言和栈式机器的历程:既赞叹其简洁优雅、代码量小、可自举的优点,也坦率指出栈操作在实际编程中的痛苦,以及 Forth 社区文化中的问题。
编程与软件工程上首页 8 次 · 2010–2024yosefk.com编辑精选