发布于 

O, draconian devil

电影或者小说《达·芬奇密码》中,馆长生前留下了一串数字和两行诗句作为给兰登等人的线索。这里的两行诗句以及之后在《蒙娜丽莎》画作中找到的句子实际上是一种字谜。我在之前看电影时并没有特别注意这三句字谜,直到我看了一位 B 站 UP 主的解说 1 之后,才燃起了对这些字谜的兴趣。

Anagram

字谜有很多种,电影或者小说中的字谜应该是属于 Anagram,根据词典上的定义

An anagram is a word or phrase formed by changing the order of the letters in another word or phrase. —— Collins COBUILD Advanced English Dictionary2

Anagram 就是将单词或短语的字母重新组合得到新的单词或短语。例子有很多,比如 "space" 经过重新排列后变为 "scape",后者有花茎的意思。复杂一些,例如 "eleven plus two" 可以变换为 "twelve plus one"。关于 anagram 的历史、应用以及构建方法等可以阅读相关的维基百科词条3。关于这一单词的起源可以查看 Collins 网站的相关页面2。Anagram 源于 ana+gramma,也就是将名字的字母倒过来写的意思。

O, draconian devil

具体到《达·芬奇密码》中,馆长使用一串打乱顺序的斐波那契数列提示兰登等人诗句是 anagram,不过这一串乱序的数列只是起到了提示字谜类型的作用,数字排列的具体方式与解答谜题之间并没有特别的关联(至少在小说和电影中没有提到,我也没有研究出或者查到关联)。但是斐波那契数列这一线索对于之后的故事情节是有帮助的,不过这与本文无关。

这三道字谜是这样的:

O, Draconian devil!

啊,严酷的恶魔!(这句和之后两句都是来自人民文学出版社朱振武等人的译本,关于翻译有一些很有意思的争议,可以参考这两篇在豆瓣上转载的文章及相应评论45

Oh, lame saint!

噢,瘸腿的圣徒!

So dark the con of man

男人的骗局是多么黑暗

如果没有那串乱序的数列,这三句画很容易让人联想到单词或语句背后的宗教暗喻。不过在知道谜题类型后,答案就出来了一半。按照 anagram 的规则,只需重排字母,使得原句变成另外有意义的句子就可以。这里需要注意的是,重排过程中单词数量是可以变化的。不过直接重排还是有一定难度的,需要很好的对于单词的直觉。在原本的故事情节中,兰登等人能够解开谜底,除了女主对于这类字谜的了解外,也离不开馆长留下的其他线索以及兰登的知识储备,当然也和故事本身涉及的宗教与艺术的背景有关。

对于没有相关知识背景的人来说,这类问题如何解决呢?我搜索到一篇使用自然语言处理(NLP)来解决这一类谜题的文章,并且是专门针对小说中的谜题而设计的。不过在分享之前,先介绍一些其他方法。

Anagram Solver

在搜索引擎上搜索“anagram solver”可以找到很多网站提供在线解谜功能。不过这些网站大多只能只是生成了字母的其他排序结果,往往只能对单词进行处理,并且输入的单词数量或短语长度存在限制,一般为 14 个字母左右。我尝试用这些网站解决“So dark the con of man”,然而完整输入都是无法实现的。虽然如此,我也找到了一个依靠字谜库进行查找的网站6,它包含了 18371878 个可能的答案,能成功解决上面的三个问题。

Cracking the Da Vinci Code

显然这些 Anagram Solvers 并不能解决一些复杂的字谜。Lucas Ou-Yang 在文章《Cracking the Da Vinci Code》中给出了自己的解法7,使用了 NLP。大致流程如下。

首先作者设立了三个前提条件:

  1. 空格也属于字谜中的字符

  2. 不考虑标点符号

  3. 字谜都是英语单词

这里需要说明一下第一条。作者把空格也算作字谜的一部分,在接下来的操作中规定了空格的数量,也就是答案中单词的数量是和谜题一致的。这一假设对于前两个两个谜题是合适的,对于第三个是不成立的。谜面与答案的字母数量不一致是可以出现的正常现象,但作者简化了这些复杂的情况。

接下来,作者考虑对字母进行重排。对于“O, Draconian devil”,算上空格一共有 17 个字符,也就是存在 17!17! 种可能性。考虑到会出现同种字母交换顺序仍为同一种排列的情况,实际数量会稍微少一些。这显然是巨大的,会产生很多无意义的单词。于是作者使用了 Google’s Trillion Word Corpus 中最常见的 10000 个单词 8 用来确定重排的单词是否有实际含义。具体实现可以查看 Github 中的代码9。代码中用来去除标点符号的函数在新版本的 Python 中会报错,可以从这里查看解决方案10

当然有意义的单词并不一定能组成有意义的短语。所以作者使用 N-grams 对重排的结果进行筛选。文章中,作者使用了含有 1000000 个高频搭配的 3-grams 表11,也就是三个单词的常见搭配组合,正好可以解决前两个问题,不过这份文件貌似是付费使用的。具体实现同样可以查看 Github 中的代码9

使用这些手段,作者很快找出了前两个谜题对应的答案。

O, Draconian devil ←→ Leonardo da Vinci

Oh, lame saint ←→ The Mona Lisa

Issue

Lucas 在自己的文章中提到,使用 bigrams 也就是两个单词的组合同样可以实现很好的效果。另外在作者 Github 相应仓库的 Issue12中,有人给出了改进的建议:直接在 n-grams 表中查找谜面。具体代码是这样的:

1
2
3
for trigram in common_ngrams_dict:
if sorted(trigram) == sorted(input_string):
filtered_solution.append(trigram)

这样避免了消耗大量的时间重排字母找到单词正确的组合。因为 anagram 本质上是字母的重排,谜底和谜面的共同特征是使用了相同的字母类型与数量,所以如果两串字符排序后一致,说明它们可以构成一道 anagram 题目,这样也就找到了对应的答案。

对于这种方案,Lucas 提出了两点缺陷:

  1. 常见搭配表中没有谜底

  2. 谜底单词数和谜面不一致

事实上,这两点缺陷在 Lucas 自己原本的方法中也是存在的,只不过增加了字母重排之后保留了人工筛选的可能。

对于第三个谜题,答案是 Madonna of the Rocks,和谜面的单词数是不一致的,这就增加了解谜的难度。而对于更多的字母,如 American novelist Dan Brown ←→ An albino convert? Man’s weird!13,不仅需要有意义,还要符合语法。

Leonard vs Leonardo

重新考虑前两个问题,也就是 Lucas 解决的两个 anagram,解决起来真的有这么顺利吗?

显然不是的,Lucas 很可能在这里玩了一些小把戏。对于前半部分的字母重排,如果你自己运行一遍代码会发现结果中并 没有"Leonardo da Vinci"。而后半部分由于我没有那份词表所以无法验证。为什么会这样呢?

代码是没有问题的,问题在于那份 10000 个常见单词表。

我们先来看两个外国名字:Leonard 和 Leonardo,虽然两者都可以在外国名字中见到,但显然后者有着更加浓重的拉美或地中海语言色彩,前者是更典型的美国名字。

在 Lucas 写的代码中,可以发现它使用的单词文件为google-10000-english-usa.txt,这份文件可以在 Lucas 自己的 Github 仓库中下载到,我在之前的章节中提供了这份文件的原始出处8

我们使用如下代码来对比这两份不同来源的文件:

1
2
3
4
5
6
7
8
with open('./google-10000-english-usa-lucas.txt','r') as f1:
data1 = [word.strip() for word in f1.readlines() if word.strip()]
with open('./google-10000-english-usa.txt','r') as f2:
data2 = [word.strip() for word in f2.readlines() if word.strip()]

for word in data1:
if word not in data2:
print(word)

data1是 Lucas 仓库中的文件内容,data2是原始的版本,运行得到:

1
2
3
mona
vinci
leonardo

也就是说 Lucas 的版本中多出了三个单词,而这三个单词恰好是答案中的。前面提到,Leonard 比 Leonardo 更像一个美国名字。这份 10000 词的文件中收录的单词本来就不多(考虑到词的屈折变化),收录的名字也更多是 "Jack" “David” "Leonard" 这种常见的名字,找不到达·芬奇的名字也很正常。既然如此,一份更大的单词表似乎是一种解决方法。按照这份单词表提供者的说法,这是一份 333333 个高频单词表的缩减版。按照这一份更加完整的单词表14,可以找到 "vinci" 位于第 14298 位,"leonardo" 位于第 15462 位,"mona" 位于第 18319 位,也就是说要想找到谜底,至少需要一份包含 18319 个单词的文件,这些单词对于第三道谜题也是足够的,甚至由于第三题的谜底比较特殊,只需要 6144 个单词就够了。对于之前提到的 "American novelist Dan Brown”的例子,为了找到“albino”这个词,需要找到第 41210 位,而且 "man’s" 的存在又进一步增大了难度。