产生的文字将会是部分可信的(locally plausible),因为任两个出现的单词也是输入文件里,两个同时出现的单词。令人惊讶的是,获得看起来是 ── 有意义的整句 ── 甚至整个段落是的频率相当高。
图 8.2 包含了程序的上半部,用来读取示例文件的代码。
图 8.2 读取示例文件
从图 8.2 所导出的数据,会被存在哈希表 *words*
里。这个哈希表的键是代表单词的符号,而值会像是下列的关联列表(assoc-lists):
使用作为示例文件时,这是与键 |discover|
有关的值。它指出了 “discover” 这个单词,在诗里面用了四次,与 “wide” 用了两次,而 “sin” 与 ”sights” 各一次。(译注: 诗可以在这里找到 http://www.paradiselost.org/ )
函数 read-text
累积了这个信息。这个函数接受一个路径名(pathname),然后替每一个出现在文件中的单词,生成一个上面所展示的关联列表。它的工作方式是,逐字读取文件的每个字符,将累积的单词存在字符串 buffer
。 maxword
设成 ,程序可以读取至多 100 个单词,对英语来说足够了。
只要下个字符是一个字(由 alpha-char-p
决定)或是一撇 (apostrophe) ,就持续累积字符。任何使单词停止累积的字符会送给 see
。数种标点符号(punctuation)也被视为是单词;函数 punc
返回标点字符的伪单词(pseudo-word)。
函数 see
注册每一个我们看过的单词。它需要知道前一个单词,以及我们刚确认过的单词 ── 这也是为什么要有变量 prev
存在。起初这个变量设为伪单词里的句点;在 see
函数被调用后, prev
变量包含了我们最后见过的单词。
现在来到了有趣的部份。图 8.3 包含了从图 8.2 所累积的数据来产生文字的代码。 generate-text
函数导出整个过程。它接受一个要产生几个单词的数字,以及选择性传入前一个单词。使用缺省值,会让产生出来的文件从句子的开头开始。
图 8.3 产生文字
要取得一个新的单词, generate-text
使用前一个单词,接着调用 random-next
。 random-next
函数根据每个单词出现的机率加上权重,随机选择伴随输入文本中 prev
之后的单词。
现在会是测试运行下程序的好时机。但其实你早看过一个它所产生的示例: 就是本书开头的那首诗,是使用弥尔顿的失乐园作为输入文件所产生的。
(译注: 诗可在这里看,或是浏览书的第 vi 页)
Half lost on my firmness gains more glad heart,
Or violent and from forage drives
A glimmering of all sun new begun
Forth my early, is not without delay;
For their soft with whirlwind; and balm.
Undoubtedly he scornful turn’d round ninefold,
Though doubled now what redounds,
And chains these a lower world devote, yet inflicted?
Till body or rare, and best things else enjoy’d in heav’n
To stand divided light at ev’n and poise their eyes,
Or nourish, lik’ning spiritual, I have thou appear.