为了更好的帮助新人入门与老手深入了解nutrimatic——这一在PH中至关重要的技能,语谜Puzzling邀请Brightly撰写了一篇它的中文介绍。本文条理清晰,内容翔实,实为必读好文。另外,我们整理了一份“Nutrimatic简明语法与实用模版”的表格,向公众号回复“Nutrimatic”可取得链接。祝你学的愉快!

曾用名:【Nutrimatic】救赎之道 就在其中
特邀作者:Brightly
编辑:鱼左
这道题,你会怎么去做?

CCBC12 A区首题 "奇怪的瓶子"
也许你会想:嗯……有这么多字母,可能要重新排列?那又要用什么排列顺序呢?如果我告诉你,我即使不理解答案的原理,也能在10秒钟做出来,你相信吗?只要运用一个“妙妙小工具”,我们就能对题目字母进行重新排列,并找出其中最合理的那个——

你好.jpg
这样的案例绝非少数...
Puzzle Hunt中,你也许会看到"暴力破解"或"爆破"等等术语。这与网络安全中的*暴力攻击*(Brute-force attack)类似,解题者借助程序穷举所有的可能性,从而省去复杂的逻辑推理,避开重复的解题操作,直接得到答案。
"道高一尺,魔高一丈"——这或许很适用于展现解题者与出题者的“友好关系”:
前者想方设法的逃避"苦力"的解题,试图凭借仅有的已知字母窥见答案全貌。后者绞尽脑汁的追求"防爆"的答案,希望运用多样的答案形式放大解题需求。
在这种"内卷"的氛围下,矛与盾各显神通。所以对于当今的PH,熟练使用工具进行词汇搜索几乎成为了必修课。今天,我们就来讲讲广受PHer青睐的爆破词汇搜索工具:Nutrimatic


以免看不懂这个笑话。
【免责声明】任何人因阅读本文章而可能遭致的意外,其造成的损失(包括但不限于因了解Nutrimatic而失去解题乐趣,因了解Nutrimatic而出题太难答案太烂遭人吐槽的心理损失,或是在不恰当的场合使用Nutrimatic达到一定的“作弊”效果而被谴责处罚等),我们对此概不负责,亦不承担任何法律责任。

别乱用啊😠
那么,Nutrimatic到底是何方神圣?
Nutrimatic(简称nutri),出自《银河系漫游指南》(Nutrimatic Drinks Despenser, 一个貌似可以"量身定制"的饮品制作机)。正如配制机根据条件配茶一样,Nutrimatic以维基百科为词库,通过正则表达式的语法进行单词与词汇的模式匹配。"
那正则表达式又是什么呢?"
正则表达式 (Regular Expression,简称regex/regexp)是一套描述字符串匹配模式的逻辑公式。它的作用,简单来说就是,可以在复杂的字符串当中,把需要的字符筛选出来,或者把不需要的字符过滤掉。是一个强大且实用的工具!
正则表达式有多强大?不妨看看这则漫画——

*正则高尔夫(Regex Golf)是一种谜题。玩家会得到两组数据,游戏目标是编写尽可能短的正则表达式来匹配数据,保证能匹配上其中一组的全部元素,且无法匹配上另一组中的任意一个元素。
由此可见,正则表达式十分万能,你甚至可以用正则表达式写一个搜索正则表达式的表达式!
此外,nutri拥有着庞大的词库,这也是它受人们追捧的一大原因。有多庞大呢?作者是这么介绍的:
Nutrimatic的词库由所有在维基百科中出现超过五次的单词和短语组成。也就是说,你不仅可以搜索到英文词汇,还能搜索到人名,地名,台词,名言,习语等等..."

意满离
初入nutri首页,你会发现寥寥几句的语法*教学*(姑且认为它是吧)

相信有不少人看的都是一知半解... 但是莫慌!这篇文章将对Nutrimatic的语法及使用进行更加详细的讲解,相信看完的你能够摆脱对nutri的种种困惑!
本篇文章信息量较大!请耐心食用!
目录

基本元素:小写字母,数字与空格
在Nutrimatic中,小写字母,数字与空格就是组成搜索结果的三大元素。你可以将它们作为已知信息放入搜索表达式中,而nutrimatic会根据你提供的信息严格的在词库中进行搜索:

注意事项
Nutrimatic的查询结果只包含小写字母,数字和空格。其余特殊符号如冒号 (2001: A Space Odyssey),撇号 (I'm),变音符号 (naïve) 等都会被省略或以空格替换。同样的,请不要将这些符号作为已知信息进行搜索!
任意未知单字母字符:A, C&V
在Puzzle Hunt解决谜题中,我们经常遇到"答案的部分字母已知"的情况。而我们可以用A, C与V来表示未知字母,它们是nutrimatic中必不可少的运算符之一。
A: 匹配任意单个字母表(Alphabetic)中的字母字符
C: 匹配任意单个辅音(Consonant)字母字符
V: 匹配任意单个元音(Vowel)字母字符
不难发现:A=C+V
具体使用时,你就可以在未知字母的位置用以上字符代替(A是最常用的)。
例如:pu?z?i?g (?代表未知字母)

puAzAiAg注意事项
Nutrimatic区分大小写,请区分好小写字母与大写字母的使用
A, C, V只能替换单个字母字符
Nutrimatic规定y不是元音
小练习请写出以下搜索表达式
一个前三个字母是ans的六字母单词/词组
一个辅音和元音交替出现的八字母单词/词组
答案: 1.ansAAA 2.CVCVCVCV 或 VCVCVCVC
单字母,数字与空格的替换:#,_,. 与 -
在前文我们提到小写字母,数字与空格是组成搜索结果的三大元素。相应的,我们也有替代数字与空格的运算符(但相对没有字母常用):
#: 匹配任意单个数字
_: 匹配任意单个字母或数字
. : 匹配任意单个字母,数字或空格
- : 匹配任意单个可移除空格

不同符号搜索范围韦恩图所谓"可移除",就是一个可选的状态。这个地方可以有空格,也可以没有:

comeAA-AA
但是话说回来,Nutri搜索时通常会自动给每个字母间加可移除空格,所以-在替代字符上并不常用,但它会有其他更常见的功能!(埋个伏笔)
量化运算:?, *与+
何为"量化"?以上文中提到的 - 运算为例,空格的出现是可移除的。量化就是将其以数量形式表现,也就是零个空格或一个空格。而?, *与+就是对作用对象的出现次数进行的限制:
?: 匹配作用对象零次或一次
*: 匹配作用对象零次或多次
+: 匹配作用对象一次或多次

我们可以用量化运算符配合未知字母进行搜索,比如A*可以用来表示任意长度的字母串,在词组爆破中能发挥很强的作用!

每日正则笑话 (2/1)
不妨看几个例子:

endeavou?r 这里,endeavou?r中"?"的作用对象是字母u,也就是endeavou?r,在搜索结果中,u就会被匹配零次或一次。
再来看一个。你知道了某个单词的部分字母(按顺序的),但不知道单词长度或它们在单词中的具体位置,如"这个单词中的某四个字母依次是a,t,t和p"。这时候就可以使用未知字母A与量化符号 * (匹配零次或多次)来进行搜索:

A*aA*tA*tA*pA*
不仅是字母,空格也可以进行量化运算:

comeAA ?AA
发现了吗?表达式comeAA ?AA与上文提到的comeAA-AA有着相同的搜索结果!这时候,我们也称这两个表达式是等价的。
我们不妨刨析一下两个表达式的不同处: ? 与 - (注意?前有一个空格)
?: 匹配空格零次或一次
-: 匹配任意单个可移除空格,也就是零个空格或一个空格
由此可见," ?"与"-"也是等价的!
小练习
请写出以下搜索表达式 1.一个第一位是a,最后一位是r的单词/词组
2.提取在“sueihappyaiu”字符串中隐藏的单词/词组(例:happy)
3.请写出一个搜索结果是"apple",且使用+*?任一符号的表达式
答案(仅供参考): 1.aA+r 2.s?u?e?i?h?a?p?p?y?a?i?u? 3.ap+le
初见重排 (Anagram):< >
论PH中最常招人"唾弃"的步骤,排序一定能占一席之地。排序的本质就是字母的重新排列,而nutrimatic也有为重排而生的操作符:< >
还记得那个被秒杀的"奇怪的瓶子"吗?

又见面了.jpg这就是重排操作的运用,< >的语法理解起来十分简单(至少你先这么认为吧),你将需要被重排的字符放进< >中就可以啦! 当然,不要妄想着nutri帮你重排五十几个字母的anagram...会爆的...
喜欢我extreme anagrams吗?anagram solver同样不是万能的!
括号的艺术:[ ], ( )与{ }除了< >外,nutrimatic同样为另外三种括号赋予不同的功能: [ ]: 匹配括号内任意一个字母/数字(可与 - 连用)
( ): 将括号内的元素视作不可拆分的整体,也可以辅助定义运算符的作用对象(优先级)
{ }: 对匹配作用对象次数进行数量限制
[ ]还记得前文提到的A, C, V吗?它们本质上就是匹配某几个特殊的字母,如V就可以等价的写成[aeiou],即匹配aeiou中的任意一个字母。(V与[aeiou]是等价的!)
那么使用[]符有什么好处呢?显然,它能让我们的搜词有着更精准的范围限定——在PH中,我们时常能写出某个未知位置的可能字母,运用这点,我们就可以直接进行爆破:
st[acist]t[acist][acist][acist][acist][acist]
那来试着写写A的等价表述吧——
[abcdefghijklmnopqrstuvwxyz]
正确的,但是肉眼可见的冗长。事实上,我们可以使用 - 符(没错,与可移除空格相同符号)对连续的字母进行简写。例如我们可以用[a-z]来简写上式,意义就是"从a到z的所有字母" 更一般的,[m-n]表示匹配在字母表/数字序中以m开始,n结束的区间的任意单个字母/数字:
[a-c]
[1-3]
你也可以将不同的区间写到一起:[a-ce-f]
我们来一点没什么用的小拓展...但如果我们非要不干人事输入[y-a]会匹配出什么结果呢?
如果我们再过分点,输入[r-1]会匹配出什么结果呢?
由此,我们可以推测——[m-n]中如果m和n不满足字母表中的升序关系,若m为字母,nutri会将[m-n]直接认作m(现实中没人会这么搜词吧)当然还有同时放数字和空格的情况,留给读者自行探索~
( )正如数学中的括号运算一样,nutri中的小括号可以明确运算符的作用对象。
何为作用对象?不妨先以数学视角比较一下,2+3×5 与 (2+3)×5中乘号的作用对象……那再来看看nutrimatic——
cA*nut 与 (cA)*nut前者,*的作用对象是A,也就是:c+0个或多个未知字母+nut
后者,*的作用对象是cA,也就是:0个或多个c+未知字母+nut
介绍完优先级,我们再看看整体性。在anagram操作中,我们有时不想破坏某几个字母的连续性,这时候我们就可以将其用括号保护起来,使其不受重排操作的影响:
<cotenmAA> 与 <(co)(te)nmAA>结合? (匹配零次或一次),我们可以实现从若干碎片中选取其中几个组合成词的操作:<(rile)?(affy)?(untr)?(acet)?(onit)?(axev)?>
{ }在前文的介绍中,你或许已经注意到了:
在该表达式结尾有连续五个的[acist],使得整个式子变得十分冗长...{ }就能帮你解决这个问题!我们只需要在{ }中填入想要重复的次数就行啦!
st[acist]t[acist]{5}结合刚介绍的( ),我们也可以对{ }的作用范围进行限制:

(cA){2}nut
那如果我们想对匹配次数进行范围限制呢?
{m,}: 对匹配前面的子表达式的次数进行无上界的数量限制——至少m次,无上限
{,n}: 对匹配前面的子表达式的次数进行无下界的数量限制——无下限,至多n次
{m,n}: 对匹配前面的子表达式的次数进行有界的数量限制——至少m次,至多n次
例如,我们想搜索所有以dis-三字母开头,11字母以上的单词,我们就可以这么写:

disA{8,}
注意事项
[ ], ( ) 和 { }不可以互相嵌套使用 2.{ } 具有不可拆分性,什么是不可拆分性?
小练习
1.请用{}实现?+*的功能 2.一个只由abcde中的字母组成的5字母单词/词组
答案:1.?:{0,1} +:{1,} *:{0,}2.[a-e]{5}
重排 & 括号:<>
从前文中的例子就可以发现,我们不仅可以将字母放进<>中,也同样可以将一些带括号的表达式放入其中!



( ), [ ] 与 { }在< >中的使用 非常方便对吧?确实,但不妨再看看下面这两个例子——


<[dgr]{3}eee> 与 <[dgr][dgr][dgr]eee>
诶?

致敬COL
在我们的认知中,[dgr][dgr][dgr]与[dgr]{3}应该是等价的,它们同样表示了"3个d,g或r的字母",但为何却在重排中产生了不同的差异?
这就要提到不可拆分性了……
不可拆分性
在nutrimatic中,不可拆分性也许不会那么常见,但这确确实实的会影响我们搜索的结果——不可拆分性:某一串字符不可被其他字母或数字所拆分(允许空格)的性质换而言之,具有不可拆分性的表达式在搜索结果中是连续的(忽略空格)最显然的,( )具有不可拆分性:

<d(ace)>
纵观所有的搜索结果,可以发现a,c,e三个字母一定是连续的 (忽略空格)再来看看{ }:

<d[cis]{3}>
可以发现,非d的三个字符都是连续的 那回归最初的例子,[dgr]{3}相当于要求这三个字母是连续的,而[dgr][dgr][dgr]就没有该性质,也就能搜出degree了!
注意事项事实上,除{ }外,*,+有时在匹配时同样具有不可拆分性


为什么说有时?比如在匹配单字母时就不会出现这一状况——

哈哈 挺幽默的
哒咩!:^
非,作为三大逻辑运算之一,nutrimatic肯定少不了它的一席之地。
^: 与[ ]配套使用,匹配未在[ ]中的字符
先前,我们使用[ ]得到了V的等价表述:[aeiou] 然后,我们引入 - ,得到了A的(简化)等价表述:[a-z]那C呢?如果模仿A,我们确实可以用区间叠加的形式实现等价描述:[b-df-hj-np-tv-z]这么写颇有一种orme shoe的美感,就是一坨史可读性不强
但我们知道A=C+V,也就是所有非元音的字母一定是辅音(一句废话),那我们就可以这么写啦——[^aeiou]有了^,你就可以在搜索中丢掉自己不想要的字母啦!比如,我们想要搜索所有不带a,c,e的7字母单词/词组——

[^ace]{7}
注意事项
在[ ]中,你要么写出所有允许出现的字母;要么配合^写出所有不能出现的字母,两者不能共存
使用[^xxxxx]后匹配的是不在方框内的字符而非字母,也就是说数字和空格是包括在内的!(也就是说[^ ]是合法的,即不匹配空格)所以上方的例子会出现"list of"
应该不会有人不知道^怎么打出来吧
子串
为了方便后续理解,我们先来讲讲子串的概念。在nutri中,我们有时候会遇到词组的搜索表达式/搜索结果。我们将组成词组的每一个字符串(单词,数字...)称为子串;每个子串的字符数量称为子串长度;子串的先后顺序称为子串顺序;每个搜索结果都具有它自己的子串数量,顺序及长度,我们将其统称为子串排布。
单词?词组?:" "
正如标题所言,由于nutrimatic会自动在搜索中加入可选空格,搜索结果也许会同时存在单词或词组。有时候我们只想将结果以单词形式展现(不包含任何空格),这时候我们只需将表达式用一对双引号" "包裹起来——

duA*
"duA*"不仅如此," "还可以限定输出的子串长度。比如,我们想要限制输出内容为所有4个字母 + 5个字母的双子串词组,就可以——

"AAAA AAAAA"
我们来结合其他的运算符来试试:

为什么出现了(4,2,2)的子串排布?(提示:想想^的匹配范围)由此,我们可以发现:使用" "可以有效地让输出字符串的空格不会额外增加/减少
注意事项
" "是英语的双引号,不是中文的啊喂!

烫烫烫烫烫烫烫烫烫
与&或:& & |应该能读懂这个标题吧在实际场景中,我们也许会对搜词结果有着多种限制。例如:
1.单词长度为7字母 2.包含至少3个元音字母 3.包含连续的por三个字母如果仅用一个表达式写出来或许会比较麻烦...但我们可以用&符对不同的表达式进行并列!

"A{7}"&<(por)AAAA>&<VVVAAAA>
还记得先前提过的"从若干碎片中选取其中几个组合成词"的例子吗?

就是这个!如果我们想去除如on it这样的单碎片结果产生,就可以使用&并列一个对长度进行限制的表达式:

ace taffy
那|的作用也就呼之欲出了——
&: 同时匹配前后两个表达式("与")
|: 选择匹配前后两个表达式之一("或")
不仅是表达式,在字母的选择上,|符可以发挥出别样的功效——例:匹配某个单词,使其包含两个重复的元音字母

"A*(aa|ee|ii|oo|uu)A*"
注意事项请不要使用&同时并列过多项的表达式!这会导致搜索时间开销的指数级增长!
综合练习

请根据目前的wordle盘面(黄色代表答案单词中包含该字母,绿色代表字母位置正确,灰色表示无此字母),写出一个可行的表达式,使得全部的搜索结果都是"有效的"(即不违反灰,黄和绿任一颜色的限制)答案(供参考): "<AApo>s"&[^taxnlug]{4}s&[^p]AA[^o]s*
此处wordle只是nutri能力练习的环境设定,大家一起玩的时候请不要作弊!
终于介绍完啦! 如果你看到这里并理解前面的内容了,那么恭喜你!你已经拥有能够与出题者混战友好交流的能力啦! 真的要在这里结束了——吗? 在前文的介绍中,我们并没有像其他运算符一样明确的给出<>与""的功能,一部分原因是其本质功能稍显复杂,我们作为使用者并不需要过多的考虑;另一部分原因是nutrimatic本身的代码存在部分缺陷,使得它们在嵌套使用时,运作机理并非严谨明确。正如作者所说:"重排的算法并非完美——当你试图对复杂的表达式进行重排时。你所得到的结果可能并不能符合你的初始输入。所以当你这么做时,请务必检查一下输出结果"在接下来的部分,我们会以更深层次的角度去刨析< >与" "的功能和运用——
(这一部分理解上较为复杂,且在实际使用时不会太过于影响搜索体验和结果,所以读者可以选择性的跳过)
溯本求源:<" "> 与 "< >"
我们暂且先将它们嵌套使用的情况放在一边,从单独的开始——" "我们知道,双引号可以让输出结果的空格不会额外增加/减少。换句话说,双引号内的子串排布:子串数量,各子串长度及子串顺序不会更改至此,我们可以推断出——" "具有不可拆分性

子串数量:3,各子串长度及顺序:(4,5,3)
那如果我们插入了一个可选空格(-)呢?

"AAAA-AAAAA"
也就是说,输入的子串排布可能为:
(9)- 单子串9字母长度 (4,5) - 双子串,4字母长度 + 5字母长度
这时候如果观察输出结果,可以发现,nutrimatic将所有符合(9)和(4,5)的单词/词组结果输出了(碍于常用度排序这边不作截图,读者可以自己进行试验)
于是我们可以有如下结论——
" ": 将输出结果的子串排布按输入表达式的子串排布进行固定/限制,具有不可拆分性
< >
在前面两次的介绍中,我们可以发现:( ), { }等带有不可拆分性的子表达式是不会被重排影响的。可以推测,< >是以括号内的不可拆分元素为单位进行重排。
但输出结果的子串排布是怎么样的呢?不妨来做个实验,我们以AAAAAAAAA(长度9)为例:
1.输入子串数 = 1

2.输入子串数 = 2

读者可以自行尝试输入子串数=3的情况,在本篇进行省略
可以发现,输出子串数量≥输入子串数量
< >: 以括号内不可拆分元素为单位进行重排,导出所有不小于输入子串数量的结果
" "和< >在嵌套使用时," "在子串数量上具有更高的优先级
AAAA & A{4}
为方便后续理解,我们先对AAAA和A{4}进行区别——
AAAA: 具有可拆分性,不可拆分单位为A
A{4}: 具有不可拆分性,在nutri操作中以整体的形式参与搜索
"< >" & <" ">
现在,让我们来融合一下吧...接下来的讨论都以目标词组:XXXX XXXXX ,即(4,5)的子串排布为例
"< >"
i. "<AAAA AAAAA>"我们分析一下两组符号的作用情况:
< >: 不可拆分元素——9个A (对9个未知字母进行重排)
" ": 子串数量——2
各子串长度——所有< >的可能结果,即(1,8)(2,7)(3,6)...(8,1)

导出结果:(1,8)(2,7)(3,6)(4,5)(5,4)(6,3)(7,2)(8,1)
相信有读者会有疑问:< >明明可以导出多个子串数量,为什么套上" "只剩一种呢?这便是优先级的作用结果,在" "下,表达式AAAA AAAAA只有2个子串,那" "与< >共同作用下的输出结果就限定为2个子串。(优先级的说法为作者的推测,仅供参考,若有其他更合理的解释还请指正!)
ii. "<A{4} A{5}>" 同样的,我们先对两组符号的作用情况进行分析:
< >: 不可拆分元素——A{4}, A{5},即这两个整体的重排
" ": 子串数量——2
各子串长度——所有< >的可能结果,即(4,5)或(5,4)

导出结果:(5,4)与(4,5)
<" ">那当我们将两个符号对换呢?显然,由于" "具有不可拆分性,无论如何< >都只对1个整体(即"XXXX")进行重排,这跟没重排一个样子...也就是说:<" ">等价于" "进而推测:"A{4} A{5}"等价于"AAAA AAAAA"这里没有重排操作," "限定了子串排布。所以子串数量,各子串长度及子串顺序都是固定的。

<"AAAA AAAAA">

<"A{4} A{5}">
这也侧面展现出:不等价的子表达式≠不等价的整体表达式
也许有些读者看完这部分会觉得"呀,搞得我都不敢正常用nutri了好麻烦啊"并不,这一部分更算是以一个探求本质的角度对< >的" "功能进行探索。再三强调,这一部分对日常的使用并没什么影响!
这下真的结束啦!本来这篇教程在半个月前就要发的,但因学业咕咕咕咕了很久。在此先对鱼左老师和读者们道个歉 :(鱼左:本来这篇教程在上一周就要发的,但因生病咕咕咕咕了很久。在此先对读者们道个歉 :( 最后再分享几个小tips:
不要过度依赖nutri,遇到特殊的答案形式也许还不如用眼睛看...
在具体题目中搜索未知词时,可以尝试加上已知的相关词语加强常用度
如果在比赛中快速的使用nutri将某一题爆掉了,赛后有时间可以自己重新按正常思路尝试做一遍哦。就像沃尔沃说的“别赶路,去感受路”,这不仅是对自己的锻炼也是对出题者的尊重
请在恰当的时候恰当地使用nutri
完结!祝nutri愉快~