用Ruby 写Turing 机

来源:互联网 发布:刺客信条mac版下载 编辑:程序博客网 时间:2024/04/29 07:48

最近在看John E.Hopcroft,Rajeev Motwani,Jeffrey D.Ullman 三巨头写的Introduction to Automata Theory,Language,and Computation,想写一个Turing 机验证一下自己写的状态转移函数对不对。懒得很,网上搜了几个不错的。但Ruby Quiz 上的这个最简单。

 

 

 

162 Turing

 

问题描述

Quizdescription by James Edward Gray II

Turing 机是十九世纪三十年代提出的一种结构很简单的计算模型。虽然它比如今的任何计算机都要简单,但大量研究表明其计算能力丝毫不弱于任何能想到的机器(当然,用起来不太方便)。

这周的任务是造台Turing 机来玩玩。

一台Turing 机包括三个简单部件:

* 一个状态寄存器
* 一条无限长的纸带,纸带被分为无数小格,每个格子能容纳一个字符。还有一个读写头,在任何时刻都会指向一个确定的小格。纸带上默认是空字符。
* 一组有限指令集。程序就是一个状态迁移的大表格。Turing 机根据状态寄存器的当前值和读写头所指的字符查得一条指令。这条指令包括寄存器的新状态,填入读写头指向单元的字符和读写头下一步移动的方向。

为了让我们的Turing 机足够简单,我们规定状态寄存器容纳的字应能被正则表达式 //w+/ 匹配上,而纸带上的字符要能被 //w/ 匹配上。另外,我们规定空字符为下划线(_)

Turing 机的程序格式如下:

 

上述格式意思是:如果当前状态是CurrentState 而读写头所指的字符是空字符的话,把状态置为NewState ,并用字符C 替换空字符,然后读写头右移一格。这五个部分写在一行里面,用空格隔开。允许程序中出现单行注释,该行中井号(#)所起的部分为注释。注释、空白行将被忽略。

你的Turing 机应被初始为程序第一行中的CurrentState。当然随你,你也可在程序载入时预置好纸带的内容,但缺省为空字符。当程序找不到一条与当前状态和读写头所指字符对应的指令时,打印出纸带上从第一个非空字符到最后一个非空字符之间的内容,然后退出。

下面是一个示例,你可以看到我的Turing 机是怎么运行的:

 

总结

仅仅过了48小时,就收到好几份代码,除了一份超过1500行的文件(代码生成的),其他大多都很简单,手写的规则集和纸带,能处理有限的二进制数输入。这保证了代码的简短易懂。我脑中蹦出一个想法,我们可以做一个扩展的Turing 机,这样我们就可以去掉重复的状态信息。举个例子:

 

把第二个参数改写成正则表达式,程序还可以更简单:

 

我不怀疑有更有效的方法利用这种简易Turing 机创造出更好的语言,更好的Turing 机。这只是想初略考察一下Turing 机是什么,而不是怎么把它变成一门高级语言。如果我们想那样,试试Ruby啰(生成器能帮我们写出更长的Turing 机程序,就像那个1500行的程序,也很有趣哦)。

这周,我看了Alpha Chen 的方案,很短很直白,而且有意让我大吃一惊。我们从最后一行,这个方案的入口函数,开始:

 

Chen 先生成一个Turing 机,仅当__FILE__ (一个包含文件名的常量)等于$0 时把命令行的前两个参数传进去。只是一种很常见的检查,防止其它脚本通过require 机制运行该脚本。

现在来如何初始化Turing 类。

 

@tape 被初始为一个散列。用一个散列来表示纸带看起来有点奇怪,数组似乎更适合。但散列在这用得很巧妙,有利于简化后面的代码。这不是一种普适的技巧。我们要实现的Turing 机毕竟不是一个严谨的应用,所以无须考虑太多。

因为这个原因@tape 的初始化有点复杂。传进来的tape,如果不为nil,会被split(//) 断成一个字符数组,然后一个一个地插进散列中。

读程序相当简单。每读入一行,先移除注释,并通过下面这句跳过空白行:

 

正则表达式中的^表示这一行开始,$表示这一行到尾了,/s*表示任意的空白。如果发现一行空白,跳入循环的下一次迭代。

当这一行被拆成字,程序被散列进@program 中:

 

Chen program 散列的键值有两部分:当前机器状态和读写头所指的符合。这两部分构成一个更完整的机器状态。把规则的其余部分插入对应散列项。

下面是Chen 的代码中运行的部分:

 

上面提到过,查找下一状态需要两部分信息:当前状态和纸带头所指的符合。如果没有找到这一项,next_state nil,循环结束。

否则,next_state 被拆成三部分,分别赋值给Turing 机的不同部分。方向用来更新@head,向前或向后移一格。从这可以看出,@tape 为什么是散列而不是数组,为了简化代码。这样Chen 就不用检查数组边界,看tape 是否需要向左或是向右扩展。相当于为@tape 引入了一个新的“索引”来分配散列项。

在退出循环之后,需要输出纸带的内容。既然@tape 是一个散列,马上会想到调用Hash#values 来取得内容,但这样不妥,因为Ruby 不保证值的顺序。为了得到纸带的顺序内容,Chen Hash#sort 先按键的大小对键值对排序,因为键的值就是纸带的下标,所以值肯定能被正确排序。

map 用来移除键,只留下值,然后合并到一个字符串中。最后一步是去掉多余的空字符,这样只显示出纸带上“有用的”内容。Chen 的正则表达式会移除紧接在一行开始之后和一行结束之前的所有空字符。

 

P.S.: 本想贴在译言上的,居然技术故障!希望不要想饭否一样倒掉。

原创粉丝点击