英文原文链接:https://www.rexegg.com/regex-humor.html#meaning-of-life
作者:Douglas Adams
翻译:无名氏
“哦!沉思计算机,”他说,“我们之所以设计制造你是为了执行这样一个任务,我们希望你告诉我们。。。”,他停顿了一下,“终极答案”。
“终极答案?”沉思计算机说,“关于什么问题的终极答案?”
“生命!”Fook急切地说。
“宇宙!”Lunkwill说。
“世上的一切!”他们异口同声地说。
沉思计算机停顿了一下,思考这个问题。
“有点棘手,”它最后说。
“但是你能回答么?”
“当然,”沉思计算机说,“我能回答。但是,我不得不好好思考这个问题。”
“多久呢?”
“七百五十万年,”沉思计算机答道。
[七百五十万年以后。。。,Fook 和 Lunkwill已经去世了,但是他们的后人继续着他们的事业]
“早上好,”沉思计算机最终说到。
“额。。早上好,沉思计算机”,Loonquawl(Fook and Lunkwill的后人)焦急地说,“你是否有。。。额。。。”
“最终答案?”沉思计算机庄严地打断道,“当然,我有答案了。”
“并且你准备告诉我们答案了?”Loonquawl急切地说。
“当然。”
“现在么?”
"对的,就是现在。"沉思计算机说。
“但是我不认为,”沉思计算机继续说,“你会喜欢这个最终答案。”
“没关系!”Phouchg说道(Fook and Lunkwill的后人),“我们一定要知道这个答案!就是现在!”
“好的,”沉思计算机说,“对于生命、宇宙和世上一切的最终答案是。。。”,沉思计算机停顿了一下。
“是什么!!!?”
“好的,答案就在这里,让我把它打印出来,”沉思计算机带着无上的威严沉静地说。慢慢地,屏幕上输出了一串字符:
^(?=(?!(.)\1)([^\DO:105-93+30])(?-1)(?<!\d(?<=(?![5-90-3])\d))).[^\WHY?]$
“但是。。。这是什么意思呢?”Loonquawl问道。
“我也不知道,”沉思计算机说,“但是我可以设计一台更复杂的计算机来解释这个答案。”
“不过,这需要时间,”沉思计算机说。
你是否好奇结果是什么呢?
1.下面的链接中,有这个正则表达式的结果:https://regex101.com/r/TycZar/1
2.下面这个链接对这个正则表达式作了解释,但是我建议你自己解释这个表达式,这是一个很好的练习;https://www.rexegg.com/regex-what-does-this-mean.html#meaning-of-life
3.原文作者Douglas Adams,本文选自Douglas的书https://www.amazon.com/dp/0345453743?tag=onamazon-20
==========================华丽的分割线==========================
分割线以上部分是我对原文的翻译,分割线以下部分是我对这个正则表达式的解释。
最终答案表达式乍一看很复杂,所以让我们把这个表达式分割成几个子表达式各个击破。
^(?=
[神奇的分隔符] (?!(.)\1)([^\DO:105-93+30])(?-1)(?<!\d(?<=(?![5-90-3])\d))
[神奇的分隔符] ).[^\WHY?]$
首先,让我们分析上面斜体字的正则表达式(就是位于[神奇的分隔符]之间的表达式),权且让我称之为“高亮正则表达式”。该表达式是为了匹配字符串42
。这个正则表达式大量使用了lookaheads,lookbehinds操作,这两者可以合称为lookaround操作。在具体分析之前,让我先介绍下lookaround操作,lookaround操作分为4个操作:(?=)
,(?<=)
,(?!)
和(?<!)
。
1. Lookaheads操作(?=)
该操作断言当前字符的后续字符是和(?=)
包含的pattern匹配。
例如,\d(?=Ton)
。这个表达式会先匹配一个数字字符,然后断言后续字符为Ton
。如果断言成立的话就匹配成功,否则就失败。比如1Ton
就是匹配的字符串。
另一个例子是,(?=\dTon)\d
。这个表达式会先断言当前字符的后续字符为一个数字字符加Ton
。如果断言成立的话就匹配数字字符的部分,否则就匹配失败。这个表达式也能匹配1Ton
,但是效率略低,因为\d
被匹配了两次。
2. Negative lookaheads操作(?!)
该操作断言当前字符的后续字符和(?!)
包含的pattern不匹配。
例如,\d(?!Ton)
。这个表达式会先匹配一个数字字符,然后断言后续字符不是Ton
。如果断言成立的话就匹配成功。比如1Kg
就是匹配的字符串。
另一个例子是,(?!\dTon)\d
。这个表达式会先断言当前字符的后续字符不是一个数字字符加Ton
。如果断言成立的话就匹配数字字符的部分。这个表达式也能匹配1Kg
,但是效率略低,因为\d
被匹配了两次。
3. Lookbehinds操作(?<=)
该操作断言当前字符的前续字符和(?<=)
包含的pattern匹配。
例如,(?<=No )\d
。这个表达式会先断言当前字符的前续字符是“No
”。如果断言成立的话就匹配一个数字。比如“No 1
”就是匹配的字符串。
另一个例子是,\d(?<=No \d)
。这个表达式会先匹配一个数字,然后再断言当前字符的前续字符为“No
”加一个数字字符。如果断言成立的话就匹配成功。这个表达式也能匹配“No 1
”,但是效率略低,因为\d
被匹配了两次。
4. Negative lookbehinds操作(?<!)
该操作断言当前字符的前续字符和(?<!)
包含的pattern不匹配。
例如,(?<!No )\d
。这个表达式会先断言当前字符的前续字符为“No
”。如果断言成立的话就匹配一个数字。比如“Num 1
”就是匹配的字符串。
另一个例子是,\d(?<!No \d)
。这个表达式会先匹配一个数字,然后再断言当前字符的前续字符为“No
”加一个数字字符。如果断言成立的话就匹配成功。这个表达式也能匹配“Num 1
”,但是效率略低,因为\d
被匹配了两次。
介绍完lookaround操作之后,我们可以开始分析上面的“高亮正则表达式”了。这个表达式可以被拆分成四个连续的部分:
a.(?!(.)\1)
\1
用来引用前一个group的结果,而前一个group是(.)
,也就是任意字符。(.)\1
合在一起就是两个连续的相同字符。(?!)
是上文介绍的Negative lookaheads操作,因此这个表达的含义是,后续的两个字符不能是相同的字符。因为接下来的几个表达式限定了匹配字符只能是2
或者4
,所以这个表达式排除了“22
”和“44
”两种情况。
b.([^\DO:105-93+30])
[^\D]
表示只匹配数字字符,所以后面加入的“O:
”以及“+
”实际上是不起任何作用的或者说是为了增加解谜难度而打酱油的存在。“105-93
”和“30
”加入之后,表示从数字字符中去除01356789
,所以这个表达式只匹配2
或者4
。
c.(?-1)
这个表达式的目的是调用前一个子函数。前一个子函数是([^\DO:105-93+30])
,所以(?-1)
在此是指,匹配字符2
或者4
。综合表达式b和表达式c,我们会匹配一个包含两个字符的字符串,并且字符只能是2
或者4
。根据排列组合,有“22
”,“44
”,“24
”和“42
”几种情况。由于表达式a已经排除了“22
”和“44
”两种情况,所以只剩下了“24
”和“42
”两种可能。
d.(?<!\d(?<=(?![5-90-3])\d))
这个表达式有点复杂,我们可以把它拆成几个子表达式。
在表达式(?![5-90-3])\d
中,(?![5-90-3])
是Negative lookaheads,断言后续字符是4
;如果断言成立的话,就匹配之。换句话说,这个表达式就是匹配字符4
。所以,我们可以直接把表达式d中的(?![5-90-3])\d
部分用4
代入,原表达式可以化简为(?<!\d(?<=4))
。
在化简后的表达式中,\d(?<=4)
表示先匹配一个数字,然后再执行lookbehinds操作(?<=)
,断言这个字符是4
。换句话说,这个表达式还在是匹配字符4
。我们把\d(?<=4)
用4
代替,再代入化简后的表达式中,得到(?<!4)
。这是一个Negative lookbehinds操作,也就是断言前一个字符不能为4
。
所以,表达式d确保了字符串的最后一个字符不能是4
。我们在分析表达式c的时候已经知道,“24
”和“42
”是两种仅剩的可能的字符串。既然字符串的最后一个字符不能是4
,那么唯一的可能就是“42
”。这样,正则表达式
(?!(.)\1)([^\DO:105-93+30])(?-1)(?<!\d(?<=(?![5-90-3])\d))
的含义就是匹配字符串42
。我们把它代入最终答案表达式,化简为:
^(?=42).[^\WHY?]$
这个正则表达式的含义就比较明显了,它的目的是匹配一行只有两个字符的文本,这两个字符是“42
”。下面让我们先把这个正则表达式化简为两个子表达式:
e.[^\WHY?]
正则表达式 [^\W]
是什么意思呢?[^]
表示不匹配中括号里的任何字符,\W
等价于[^A-Za-z0-9_]
,也就是说不匹配任何字母数字以及下划线。所以[^\W]
是匹配一个可以是任何字母数字以及下划线的字符。[^\WHY?]
的意思就是,匹配一个可以是任何字母数字以及下划线但是除了HY?
这三个字符以外的字符。
f.(?=42)
(?=)
是一个lookaheads操作,它断言后续字符是“42
”。
所以表达式^(?=42).[^\WHY?]$
是想匹配一个只包含两个字符的字符串。它会先断言后续字符是“42
”,如果断言成立的话,就匹配首位字符,首字符只要是“.
”就行,也就是任意字符;尾字符需要符合[^\WHY?]
。
综上所述,最终答案表达式为了增加解谜难度把一个简单的表达式用各种迂回的方式来表示,但是它的本质就是匹配一行文本,这行文本只包含字符串“42
”。不过,“42
”和生命的意义有啥关系呢?请参考这篇文章:https://coolshell.cn/articles/11170.html
“42
”本就是原作者随机选择的一个数字,这个选择本身就暗示了 生命其实是无意义的,或者说的玄一点:“色即是空,空即是色”。