Archive for Python

几个趁手语句

处于局域网中的小型开发团队常常需要互相贴代码, 传文件, 共享资源, 我长期使用过程中总结了几行趁手的语句, 贴出来共享. (本文不适合 windows 用户)

1. Gtalk 传递命令行程序输出信息

常常需要把程序的输出结果或者调试结果通过 IM 发给同事诊断. 而这些结果通常都在字符界面下,拷贝出来很麻烦,于是,我写了一个小程序 gpipe.py,可以把 gtalk 当作一个管道接在程序后面, 比如说, 想把程序编译结果给郝培强(tinyfool),

make 2>&1 | gpipe tinyfool

他的gtalk 客户端就被我用输出给淹没了.
有兴趣的还可以套接 gtalk, 把信息用 base64 编码, 接受方再解码, 如此一来, gtalk 就和Linux 中的管道一样, 将一个机器上的程序的输出套接到另一个机器上另一个程序的输入. 实践证明, 在跨平台的环境下这种做法比使用中间文件分别执行高效很多. 调试时间也大大减少.

2. 传送文件作为邮件附件.

使用matt 客户端,一行即可完成:
echo “Content” | mutt -s “Subject” -a file email@address.demo

这个方法对及时传输一些小文件非常有效, 特别是传送源代码. 还能起到存档备份的效果, 反正Gmail 那么大不用也浪费. 懒人还可以进一步用一个脚本包装, 比如我机器上就包装出了一个 sendboss.sh, 里面是:
echo “Hi, These are the file(s), thanks. Eric” | mutt -s “File” -a $* myboss_email@wustl.edu

这样我每次就只要 “sendboss.sh files” 就可以了. 我老板常常惊讶于我发送文件的反应速度.

3. 一行语句的HTTP文件服务器.

python -m SimpleHTTPServer

即可将当前目录开设为一个8000端口的http 服务器的根目录. 在局域网中,如果需要临时共享当前目录下的一个较大文件,这个方法简便安全,实在是居家旅行必备.

还有, 下载的时候使用 “wget -c” 可以断点续传,很多哥们好像不知道这个小花招.

4. NFS 共享文件夹

SVN 和 CVS 对于代码和文档控制得很好,可是团队中免不了有些 PDF 文档或者色戒电影需要全团队共享,又不需要送到版本控制系统里面。一个共享的文件夹很有必要. 最简单的方法是使用 NFS, 能够跨平台且性能稳定. 具体服务器设置可以参考这里,客户端只要

mount nfs_server:/dir /mnt/share

即可顺利使用此文件夹. 此法对于有电驴 bt 爱好者存在的团队来说,实在是必备良方.

Comments (8)

循环迭代器和闭包

通常, C 程序员使用如下代码对 a 数组的每个元素进行操作:

for (i = 0; i < n; i++) func(a[i]);

这行代码有三个独立的逻辑, for 循环控制了对 a 的访问顺序, a[i] 控制了对 a 数组的元素访问方式, func() 函数控制了对 a 元素的操作. 这个三个逻辑是彼此独立的. 然而, 问题逐渐浮出水面. 首先是 for 循环的次序. 对于随机存取的数组 a, 访问顺序应当是没有关系的. for( i=n-1; i>=0; i–) 也是可以的. 一个简洁的语言应当突出逻辑, 丢掉不必要的代码. 因此, 最好能写成

foreach (k in a)  func(k);

这个解一石二鸟, 第一是把访问顺序逻辑彻底抛弃, 第二是改变了访问方式, 从此没有烦人的 ArrayIndexOutOfBoundException, 不需要担心访问越界. 在函数式编程语言中, 类似的形式是:

map(func, a)

我们希望 foreach 能够遍历一个数据结构的每个元素一次. 可是这个终究是一个语法糖, 因为它只能胜任特定的数据结构. 对于用户自己设计的一些数据结构, 比如树, 想同时拥有先序和后序遍历, foreach 就无能为力了. 与此同时, 我们也不想用 for 那样简单粗暴的方式去控制访问顺序 (因为很容易导致内存越界, 而且很多数据结构不支持随机存取). 这时候, 就需要迭代器的帮助了. 因为不同的数据结构要求不同的访问顺序, 我们希望数据结构本身能够提供一些访问顺序. 这种对于数据结构上元素访问顺序的抽象, 被称为迭代器. 一般来说, 迭代器通过 getIterator() 被数据结构构造, 具有一个 hasNext() 的判断和一个 next() 的函数. 对一个数据结构的访问可以四海一家的写成:

for (i = a.getIterater(User’s_Order); i.hasNext(); i.next() ) func(i.current());

迭代器把访问逻辑从数据结构上分离出来, 是一个常用的设计方法. Design Pattern 也把它作为23个Pattern之一.在 STL 中也到处都是了它的身影. 但对一个完美主义者来说, 这个代码还是太冗长, 既然对数据结构的访问能够四海一家的写成如此, 为何不直接规定一个简洁的语法访问呢? 是的, 我们希望返朴归真, 回到原来的形式:

for i in a.SomeIterator(): func(i)

好消息是现代语言都支持这样的方法. [注: 在C/C++ 语言中, 还不能支持上面的写法. C#有类似的写法. 在 Ruby 和 Python 中很常见. ] 可是能不能再简化呢, 能不能连 i 这个中间变量都不要呢? 我们简直希望写成:

a.SomeIterator(func)

是呀, 这样的世界多美好啊. 可是, 别忘了在面向对象的语言中间, 一切都是对象. 在 C 的时代, 我们可以通过函数指针把 func 参数传递进去. 可是这里是对象, 怎么办呢. 很简单, 只要构造一个只有一个方法的对象, 传进去就行了. 这样的构造方法, 居然也是一个设计模式, 叫做函数对象(Function Object), 或者叫函数子 (Functor). 被 Java 的匿名内部类这个概念折腾到崩溃的读到这里, 应该知道所谓的匿名内部类, 就是为了在面向对象的环境中做函数传递这个事情而想出来的小花招. Java 的写法就是

a.SomeIterator( new FunctionObject()
{ public func(a)
{return something;}
});

[为了引用这样一个函数, 使用了天书一般的不必要代码, 可想有多少人真的喜欢这个方法]

逃离了Java 的世界, 我们来到 Python 或者 Ruby 或者其他函数式语言的世界, 激动的发现原来函数也是一种对象, 和其他对象一样在 Python 中坐上头等舱. 如此好事怎么能错过呢. 于是, 大摇大摆写上:

a.SomeIterator(func)

没有类型检查, 也没有复杂代码, 连一个辅助变量都不多谢, 真舒服. 在 Python 中间, 这样的模式比比皆是, 比如 os.path.walk(path, visit, arg) 可以对path目录下所有的文件遍历, 并且调用 visit 函数处理, 还可以夹带一个私货 arg 进去, 非常方便. [注: os.walk() 则反其道行之, 提供一个 generator, 外部用 for 访问]

从外面夹带私货不算特别困难, 毕竟可以通过修改函数的定义方式来实现. 可是假如想从迭代器里面夹带一些私货出来给函数用, 就有点困难了. 比如说, func 函数需要访问迭代器的一个私有的变量. 显然把迭代器作为参数传给函数是不行的. 只能反其道行之, 让迭代器把函数当成自己人, 以迭代器为主, 把函数包含到迭代器的作用域内才能玩转. 这个就是所谓的闭包. 也就是说, 一个函数被包入另一个更大的作用域, 并且可以访问大的作用域里面的变量. C/C++ 是不允许在函数里面定义函数的, 所以只好望着闭包干羡慕. Java 算不错了, 用内部类解决了这个问题.

我们说个具体的例子: 函数 func(x) 需要另一个值 y, 返回 x*y, 另一个值 y 在外部作用域定义了. 写成 Python 代码

a= [1, 2, 3]
def func(x):
return x*y

for y in a:
print func(2)

会出现 2,4,6

我们可以看到, 函数func 的定义和使用是独立的. 而静态语言是不能随便在什么位置都能定义函数闭包的, 原因不难解释: 编译器会跳出来告诉你 y 这个变量没有定义. 因为动态语言在运行时才能得到 y 的值, 从而使用 func, 所以不存在这个问题. 动态语言的灵活性在此充分展现. 最后, 既然我们知道迭代器后面必然要加一个闭包, 还要括号干啥? 不如直接写成 (实际上, Groovy 语言就是这样的):

a.each func

从简单的循环, 到迭代器, 到生成器, 到内部类, 到函数作为一级对象, 再到闭包, 过程式编程语言, 对象编程语言和函数编程语言越来越呈现融合的趋势.

Comments (5)

说 Python 的几句坏话

我一直很推崇使用 Python, 但是推崇不是迷信, 今天就说说几个 Python 相对于 Java 和其他语言的缺点, 以及初学者要注意什么.

1. 文档不完善, 最少惊奇原则不适用

很多 C++ 和 Java 开发者都知道, MSDN Java Doc 堪称技术文档的典范. Java 更是充分体现 Knuth 提出的 Literate Programming 的精华, 用一个专门的叫 javadoc 的工具自动提取程序元信息, 生成交叉引用的 JavaDoc 文档. 任何复杂的类, 框架, 在javadoc 的统一管理下, 可以生成非常漂亮的文档. 如果再配合设计模式中的 facade 模式, 一个复杂的框架很简单就可以上手. 这方面, Hibernate, Lucene 都是优秀的典范. 当然, 交叉引用的文档也有明显的缺陷, 就是极其庞大冗长, 因此 JavaDoc 和 JDK 是独立的两个部分. MSDN 更加绝了, 直接出几张光盘, 你爱装不装. 虽然冗长, 好处就是在面向对象的汪洋大海中顺着链接, 永远不会丢失. 而 Python 的文档还远远不够细致. 所有的对象方法, 只是简单介绍. 如果一个对象方法返回另外一个不熟悉的对象, 则必须手工定位新对象的位置, 而不能顺着文档继续前进. 为了搞清楚一个包, 完成一个小任务, 可能要多次搜索文档. 对于黑客级别的用户来说, Python 文档言简意赅, 喜闻乐见, 只要记住了每个对象的每个函数, 写程序如行云流水. 对于初学者和用 Python 写稍大项目的人来说, 这就是最恐怖的噩梦. 因为以介绍单个函数功能为组织方式的文档, 对于刚上手的用户来说, 很难顺着对象调用关系,通过文档指引完成任务. 即使著名的框架比如 django, 也缺少交叉引用的细致的文档. Python Library Reference 只能说是一个完成了 50% 的README. 在这种情况下, 除非对一个框架很熟悉, 否则这个框架对于初学者来说简直是”惊奇的噩梦” — 一般用户只能看例子然后顿悟到: 哦, 原来是这样用的.

除此以外, 因为语言的动态性, 使得 IDE 能给的帮助特别少. 对于 Java 程序员来说, 很多时候都是靠着 Eclipse 的提示去选择正确的方法调用. 而因为 Python 的动态性, 变量的类型到运行时才能确定, 因此能够给的提示相对变少. Python 既是动态的, 又是强类型的, 所以代码的阅读者必须要一步一步手工追踪中间对象的类型, 并且查阅对象方法文档, 才能搞清楚到底一段代码做得什么. 而这一切, 偏偏是没有 IDE 帮助的. 对于开发者来说, 如果不熟悉对象的可用方法, 用 Python 开发也会事倍功半, 因为要常常停下来去 Google.

当然, 文档不够完美, IDE 不支持的现状, 根本是因为 Python 的动态性: 谁让 Python 返回值不规定类型呢? 我的建议是, A. 对于现成已知的一些对象方法上, Python 社区应该注意文档的维护. B. 对于不熟悉对象的可用方法这个问题, 要买一本 Pocket Reference. 或者下载一些 cheet sheet. 在刚接触 Python 或者 刚接触 Python 框架的时候, 一本薄薄的可供快速查阅所有对象的所有方法的小书, 无比重要. 这也间接的说明, 和 shell 脚本, 正则表达式语法等一样, 脚本语言必须要很熟悉才能发挥魔力.

(– 附实例,对Java 不熟悉的可直接跳过: 我曾经做过一个游戏, 这个游戏类似于推箱子, 其中的一个核心是要把多幅带alpha 通道的图像拼成一个图像, 然后显示在窗口中. 打开 API Doc, 简单的搜索一下, 发现有 java.awt.Image 类. 不过这个是抽象类, 没关系, 文档下面就明明白白写着两个可以直接用的类, 一个是 BufferedImage, 另一个是 VolatileImage. 看上去 BufferedImage 更像, 于是点进去, 正是想要的. 看看构造函数, 一个空白的图像就构造好了. 顺着方法向下看, 看到 createGraphics() 方法, 返回 Graphics2D 方法, 说明中写着这个可用做在 Image 上绘图, 正是想要的, 点返回值, 是 Graphics2D, 有方法叫 drawImage. 任务完成, 轻松自然.

回到 Python. 我遇到过一个需求, 是用 Python 提取所有一个 Zip 文件的注释. Google Zip+Python 定位到 zipfile 模块. 找到 ZipFile 对象. 读文档没发现可以往外读注释, 只能碰运气, 开一个解释器尝试. 看上去 getinfo 可以. 因为没有交叉引用, 只能搜索ZipInfo 这个对象. 终于看到一个方法是 comment. 不知道类型, 仍然需要自己在解释器下先 type 测试一下, 才能在程序中写下一行完整的语句.–)


2. 文档上有, 但是你不能用

我用 Ubuntu/MacOSX系统, Python 是自带的. 以为万事大吉, 实际上, 因为 Python 所有的库都是运行时装载的, 所以有些库在有些系统上是缺失的, 而文档则是所有标准安装的超集. 因此即使文档上有, 你也没法用.

这个问题对于 Java 程序员和 .Net 程序员来说是不能接受的. 对于任何一个良好设计的 SDK, 文档和实际可用的应该具有同等的覆盖面. 在 Java 中, 有一个包就是有, 没有就是没有, 绝对不会出现标准文档中提到了, 实际中却不可用的情况. 对于可选包, 用户也能理解需要下载模块. 而对于 Python, 开发人员很难理解为什么标准文档中提到了, 实际中却缺这个组件缺那个组件. 一个潜在的伤害就是, Python 的跨平台能力没那么强. 试想, 开发的机器上有一个模块, 而用户没有. 而标准文档偏偏又认为这个是一个标准组件, 那么, 在装载的时候,找不到这个组件了, 让用户根本就无从下手. 我的建议是 A. Python 应当有更好的 import 报错机制, 对于不是每台机器都安装的模块, 应当给出平台中立的更加友好的帮助信息, 而不是简单的 ImportError. B. 应当在 Python 语言中支持更加强大的内省机制, 对于系统当前可用的包, 应当有一个全局的缓存. 这样, 用户才能知道哪些可用哪些不可用, 不可用的时候怎么办. C. 对于用户来说, 只能求助于Google 或新闻组, 或者从 Python 源代码中找线索. 一个较好的办法是在 Python 源代码上 执行 make test, Python 会汇报哪些模块已经编译, 哪些模块被跳过了.

(–实例: 前几天想写个程序抓Google Reader. 认证的时候要用到 SSL. 因此找到 Python 文档, 照着例子 import urllib2, 然后直接连 Https 服务器. 出错了, 原因是没有 ssl 支持, 不能访问 https. 所有的 Python 在线文档都说, 这个是标准模块. 对呀, Python Library Reference 里面都有的模块, 怎么能有问题呢? Google 搜索也帮不了什么忙, 因为 Python 是跨平台的, 不同的平台有不同的解决方法. 所有的在线帮助只能提示到 _ssl.o 这一层. 于是, 只能翻看 Python 源代码, 在 Modules/_ssl.c 中看了一圈, 发现需要 openssl/ssl.h 等文件, 这才意识到, 需要安装 openssl 和 libssl-dev. 在 Ubuntu 上, 同样的问题还存在于 sqlite3 模块, tk 模块 和 bz2 模块. 分别需要安装 libsqlite3-dev, tk-dev 和 libbz2-dev, 再重新编译 Python 系统. 使用 make; python setup.py install 来安装 –)

3. Python 是胶水语言, 用是简单的, 模块开发是痛苦的

Python 用到一定程度以后, 一个自然的需求就是把其他语言, 特别是C语言写的库包装成 Python 库. 这个问题说复杂也不复杂, 在 Python/C Reference Manual 中也提到了所有必要的信息. 只是层次太细. Boost.Python 有更加好的实现, 不过需要对 C++ 模板很熟悉才行. 当然, Python 的模块开发的痛苦比起 JNI 复杂的 JNIEnv 对象, Lua 的 基于栈的 Push Pop 已经是大大轻松了. 相比较于 Ruby 的自动生成 makefile, 也算各有千秋. 只是模块开发设计到的技术细节太多, 要对 Python/C API 了然于胸才行. 因此我的建议是: 不要从轮子造起. 如果有这样的模块, 就用. 如果没有, 而又非要用特定功能的模块不可, 那就暂时先不要考虑用 Python. 相比较于其他更加成熟的语言, 比如 Java, Python 的可用模块还是显得少了一点. 因为 Jython 的出现, 这个情况已经大大缓解. 不过工业界历来喜欢用最稳定的东西, 所以可以想象, 多少 Web2.0 公司是为了用 Lucene 才用 Java 服务器的, 而每天又有多少新的公司投入 Java 的怀抱 :)

Comments (7)

语言模型与语言识别程序 -1

[先说一下, 上次的语言识别程序的思路来自 Python Cookbook, 我只是略作修改, 并没有什么太大的贡献, 这个系列可能包含三到四篇文章. 底下我可能会用朴素贝页斯分类器再写一个然后比较两者效果]

以前我在翻译怎样写一个拼写检查器中, 就提到了(概率)语言模型这个东西. 什么是语言模型呢, 简单的说, 就是这个语言当中, 每个词出现的概率分布是有一定规律的, 与其他因素无关的. 如果我们要写一个汉语到英文的翻译器, 那么, 理论上说, 我们翻译器输出来的结果当中每个单词出现的概率, 应该和英文当中每个词出现的概率一样. 试想一下, the 这个单词在英文中常常出现, 而如果我们的翻译器对于任意汉语的翻译结果中, 故意调低或者错误设置 the 出现的概率, 造成翻译出来的结果几乎不出现 the, 或者出现的概率出奇的奇怪, 那么, 这段翻译出来的话, 它怎么看也不会像英语. 同样的道理, 一个拼写检查器可以把任意拼错的单词纠正过来, 在纠正过程中必须照顾到语言模型. 如果 teh 可以被改正为 the 和 tek, 那么我们应该优先考虑 the 才对, 因为 the 被使用的概率比 tek 大. (统计语言模型整体是很大的一块, 大家可以去读 Google 黑板报数学之美系列得到一个大致的感觉. 我这里只用到语言模型, 用不到条件概率)

基于这个假设, 我们自然的得到一个主意: 好呀, 假如两个文本的语言模型类似, 我们就认为他们是同种语言写成的. 对的, 怎么衡量这个类似呢, 我们可以用数学的方法: 统计任何两个语言模型之间的”距离”, 然后选取最小的. 这个地方距离随便怎么定义, 可以用向量夹角, 可以用欧式距离. 但是, 现实的问题不是这么简单. 首先, 如果每个词都对应一个词频的话, 得到的向量是高维的. 除非有一个包含各种语言单词的全集, 否则很难说这个向量一共要有多少个维度. 而且高维的随机向量还往往以很大的概率正交. 为了控制维度灾难, 一个简单的方法是使用每一个字节作为一个词统计. 这个模型虽然看上去很没道理, 而且还很”面向机器”, 但是考虑到计算机处理语言的时候本来也就是根据字节来处理, 这样做一下也未尝不可.

我并没有亲自实践比较字节频率然后计算向量距离的方法, 不过我可以想像, 这样的方法在UTF-8字符集上是有很好表现的. 有人可能会担心, 加入某个汉字编码成了两个字节, 这两个字节正好分别等于西文字母的编码的话, 汉字就可以在计算机中等价的写成一串字母了, 这样, 就很难保证一段汉字是不是和一段希腊文正好有同样的字节分布了, 因为我们可以作弊一下, 搞出一段汉字, 正好和希腊文对应分布一样. 但是, 巧妙之处在于UTF-8 字符集本身通过若干前缀区分了不同的变长编码, 不同的编码区段没有任何交集. 反过来说就是, 从UTF-8 编码的字节序列中任意取一个字节出来, 一定能够区分这个字节是某个变长编码的一部分. 任何一个多字节符号编出来的编码的一部分, 绝对不可能正好是其他单字节符号的编码. 这样, 单字节多字节变成字节编码后各自为政, 映射到的字节码的概率分布不会出现“作弊”现象. 用严格的数学语言来说就是, 一个字符对应的字节在叠加到向量中的时候, 绝对不会叠加到属于另一个字符的所有字节上, 也就是说, 他们总有独立分布的部分存在. 这个是字节模型能正确反映语言模型的一个必要要求.

[To be continued…]

Comments (1)

A Practical Guide to the AIMA Python Code 2

Continued with the last post

Some readers reported that if using graph_search to solve the NQueensProblem provided in search.py, python will complain that:

TypeError: list objects are unhashable

Yes, you are doing perfectly right and the code itself is imperfect. As this point, you might want to make the List objective hashable. Inspired by the idea of Set and FrozenSet in Python, you would expect a frozen(x) function to make any mutable object immutable (and therefore hashable) [Or you can try to use tuple from beginning, but in the NQueensProblem code, it uses .index method, which is only suitable for List]. Unfortunately, this proposal (PEP-351) was rejected by Guido. Hence, we don’t have a unified way to convert any “state” to an immutable object and insert it in to “closed table”. One possible way to do this is to write your own State class and implement the __hashcode__ function. The other handy but awkward way is to convert the list to a tuple when insterting to the “closed table, i.e. edit Line 144 of search.py as:

if tuple(node.state) not in closed:
closed[tuple(node.state)] = True

Here is a toy forzen function.

“”"Toy function frozen which can convert mutable objects to immutable
	Author: Eric You XU.
	GPLv2
“”"

def frozen(x):
	“”"Return the immutable version of an object.
	“”"
	if type(x) == type([]):
		return tuple(x)
	elif type(x) == type([]):
		raise “I can’t froze dictionary object as they are very hot.”
	# fill your type here
	else:
		return x

You can put it in to search.py and edit the Line 144 as:

if frozen(node.state) not in closed:
colsed[
frozen(node.state)] = True

Now the code looks much better.

Comments

A practical guide to the AIMA Python code -1

Part -1 Search

Introduction

images.jpg AIMA (Artificial Intelligence: A Modern Approach) might be the most successful textbook about AI. I take this course with Prof. Yixin Chen this semester. I am going to go through all the source code (Python) provided on the website and use them to finish my homework. Therefore, both for my personal referral purpose and for someone who might be interested in using that code, I write a very simple “practical guide to AIMA python source code” series here. Enjoy the magic of AI and Python!
Step 1, build the environment.

In this section, you will need three python files:

http://aima-python.googlecode.com/svn/trunk/agents.py
http://aima-python.googlecode.com/svn/trunk/search.py
http://aima-python.googlecode.com/svn/trunk/utils.py

Type:
python agents.py
and
python search.py

to make sure that you have all the packages like Image and Tk with your python. If you don’t have them, just download them. The general guideline is to use apt-get if possible. If you are not using Debian family OS, download the Python package and then use

python setup.py

at the python package folder.

Step 2, study the code — tree search

In order to use the code, we have to get familiar with the interface. Let’s get start from search.py and start from depth_first_tree_search (Line 130). The line reads:

def depth_first_tree_search(problem):
return tree_search(problem, Stack())

We all know that depth-first search uses Stack. Now let’s omit the technical detail about the implementation of Stack in util.py and check what the “problem” is.

Let’s move to Line 16 of search.py. Here is the definition of Problem class. Here __init__ just assigns the initial state. All functions like successor and goal_test are abstract. So, let’s take a look a concrete example. Let’s jump to Line 458, NQueenProblem. It’s a subclass of Problem. It defines the initial state, successor function, and goal_test function. Node that the function conflicted and conflict are for internal use only, there are not the essential part of a problem.

Here, by this case study, we at least know that we have to define a problem with

1. self.initial field which is the starting point for a problem.
2. A successor function which generate the successor state for a given state. We will study it carefully later.
3. A goal_test function which returns either False or True.

The initial is really trivial, we can just assign a initial state to it. Note that Python is a dynamic type language, so we don’t actually care about what type of state is. We will exam the type of state later, but now let’s assume that we know the type of state.

The successor function is really tricky. A straight-forward way to design a successor function will return all possible states in a list. But if you do that, you will get an “Too many values to unpack” error. The design here is actually very neat: instead of put a state in to the list, you can put a (action, state) tuple in to a list. [Line 88 of search.py, in

function expand(problem)
...
for (act, next) in problem.successor(self.state)

read this line and you will figure out the date structure to store the successors. The reason is also simple: we can trace the path easily if you record the action down at every expanding step.

Since we moved our eyes to the Node class, let’s exam it carefully.

The constructor of Node is fairly simple. The path function will backtrack all the path to the root. We can modify the code of __repr__ here to print the action out if needed. If any search finally return a result in the form of Node, we can actually get everything we need from this Node, e.g., the path to the node, the action series to the node.

Based on the knowledge above, you can write your own problem model based on tree search. Now let’s move on to graph search.

Step 3. Graph search.

The only difference between tree search and graph search is that in graph search, we have a fringe that stores the closed notes. As you might know, the fringe is implemented in hash table. In Python, there is a tiny technical point: List is not hash-able as it’s mutable, Tuple and String are hash-able. Therefore, if you implement your state as a list, make sure to convert it to a tuple or string or a constant. Otherwise graph search will not be happy with this and complain about it.

If you want to use A* search, another point arises: heuristic function. Note that Line 211 reads:

def f(n):
return max(getattr(n, 'f', -infinity), n.path_cost + h(n))

here n is a Node instead of a State. Hence, if you want to get the state information and implement h, you have to use n.state to get the state information.

Step 4. build your application

Perhaps the best part to study the code is to write your own code to test it. Here are my implementations on eightpuzzle and three-missionaries three cannibals problem. The code is ugly and only for study purpose only. Please don’t directly copy it to your homework.

Eightpuzzle.py

#!/usr/bin/python

from utils import *;
from search import *;

class EightPuzzle(Problem):
	“”"The problem of solving a eight puzzle problem “”"
	def __init__(self, state):
		self.initial = tuple(state)

	def successor(self, state):
		“”"In the 8 puzzle problem, move the blank to other position.”"”
		result = []
		position = list(state).index(0)
		for action, newposition in self.possible_position(position).iteritems():
			new = list(state[:])
			new[newposition], new[position] = new[position], new[newposition]
			result.append((action, tuple(new)))
		return result

	def possible_position(self, position):
		“”"return the possible position”"”
		result = {};
		col = position % 3;
		row = position // 3;
		if row != 2:
			result[‘D’] = position +3
		if col != 0:
			result[‘L’] = position -1
		if row != 0:
			result[‘U’] = position -3
		if col != 2:
			result[‘R’] = position +1

		return result
	

	def goal_test(self, state):
		if state[0] != 0:
			return False
		elif state[1] !=1:
			return False
		elif state[2] !=2:
			return False
		elif state[3] !=3:
			return False
		elif state[4] !=4:
			return False
		elif state[5] !=5:
			return False
		elif state[6] !=6:
			return False
		elif state[7] !=7:
			return False
		elif state[8] !=8:
			return False
		else:
			return True 	

	def h(self, node):
		“”"Heuristic function”"”
		temp=list(node.state)
		sum=0
		for x in range(8):	# mahatten distance
			sum = sum + abs(temp.index(x)-x)
		return sum

	def h2(self, node):
		“”"bad h”"”
		sum = 0
		pos = 0
		for x in node.state:
			sum = sum + abs(x - pos)
			pos = pos + 1
		return sum
if (__name__==“__main__”):

#	state = [1, 2, 3, 4, 5, 6, 7, 8, 0]
	state = [7, 2, 4, 5, 0, 6, 8, 3, 1]
#	state = (2, 6, 4, 7, 0, 3, 5, 8, 1)
	instance = EightPuzzle(state)
#	breadth_first_tree_search(instance)
#	depth_first_tree_search(instance)
#	breadth_first_graph_search(instance)
#	print depth_first_graph_search(instance).path()
	print astar_search(instance, instance.h).path() 

Boat.py

#!/usr/bin/python

from utils import *;
from search import *;

“”"state is a tuple with (#m1, #m2, #s1, #s2, where_is_the_boat)”"”

class Boat(Problem):
	def __init__(self, state):
		self.initial = tuple(state)
		self.N = state[0]
		self.nodenum = 1

	def successor(self, state):
		result = []
		symbol = state[4]*2 -1; # 0 => -1
		action = 0
		for x in (0, 1, 2):
			for y in (0, 1, 2):
				if x + y >= 1 and x + y <= 2:
					new = (state[0] + symbol*x, state[1]-symbol*x, state[2] + symbol*y, state[3] - symbol*y, 1 - state[4])
					if self.check(new):
						result.append((action, new))
						action = action + 1
		self.nodenum = self.nodenum + action
		print result
		return result

	def check(self, state):
		for x in state[:-1]:
			if x<0:
				return False;

		if state[0]!=0 and state[0] < state[2]: return False;
		if state[1]!=0 and state[1] < state[3]: return False;

		return True

	def goal_test(self, state):
		if state[0] == 0 and state[1] == self.N and state[2] == 0 and state[3] == self.N and state[4] ==1:
			print state
			return True
		return False;

	def h(self, node):
		return 0
		state=node.state
		return state[0]

if (__name__==“__main__”):

	state=[3, 0, 3, 0, 0]

	instance = Boat(state)
#	breadth_first_tree_search(instance)
#	depth_first_tree_search(instance)
#	breadth_first_graph_search(instance)
#	depth_first_graph_search(instance)
	astar_search(instance, instance.h)
	print instance.nodenum

BTW, if you need to highlight your source code and convert it to HTML for example, one of the handy program is GNU source-highlight. I really love this.

Comments (1)