网站: JavaEye 作者: Lich_Ray 发表时间: 2007-07-29 20:33 此文章来自于 http://www.iteye.com
声明:本文系JavaEye网站原创文章,未经JavaEye网站或者作者本人书面许可,任何其他网站严禁擅自发表本文,否则必将追究法律责任!
原文链接: http://www.iteye.com/topic/106747
文章中用纯文本制作的图不可使用等宽字体显示。请进入论坛查看本文,文中错误参考回帖,谢谢。
引用
在 函数式编程语言曲高和寡? 一文中,我们看到 Haskell 能用两行代码
代码
sort [] = [] sort (x:xs) = sort [y | y <- xs>< x] ++ [x] ++ sort [y | y <- xs>= x]
搞定快速排序算法。这是偶然,还是必然?在这篇文章中,lichray 用我们所熟悉的 Python 语言,几行代码搞定很多学编程几年的人都只是一知半解的算法——八皇后问题,展示和上篇文章中的快速排序一样清晰的、令人耳目一新的 函数式算法思想。 预备知识:
代码
# 得到一个 2 到 20 中的偶数组成的列表,只要写 >>> [x for x in range(2, 21) if x % 2 == 0] [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] # 记住,只需给出一个形式,比方说想求两个列表的“笛卡尔乘积” >>> [(x , y) for x in range(4) for y in [3,1,7,8]]
很明显,仅仅使用列表领悟特性就可以表示一些算法了,比如提到过的快速排序:
代码
def sort (ls): return [] if ls == [] \ else sort([y for y in ls[1:] if y < ls[0]]) + \ [ls[0]] + \ sort([y for y in ls[1:] if y >= ls[0]]) # 别忘了 python-2.5 的新特性条件分支表达式哦!可惜太长,不得不强制换行。
但要注意一点:实现拙劣的多未知数列表领悟(比如 Python)可能会崩了你的程序,文中会谈到这一点。 算法描述: 归纳法定义: 形式化的思考:
代码
def queens (row, col): return [[]] if col == 0 \ else [[ran] + rst \ # 在 Python 中,g(x,y)=[x]+y for ran in range(row) \ for rst in queens(row, col - 1) \ if safe(ran, rst)]
不难看出,有了列表领悟这个强大的武器,我们就可以放心大胆地利用描述一个元素形式的思路来解决这类列表输出的问题。这就是列表领悟最根本的思想:“形式化的思维方式”。 现在只剩一个问题了:safe() 函数。先应用一次我们的“图形化的思考”。对一个格局(解,rst)来说,新加入的棋子的 col 值 ran 必须对这个格局中所有已存在的位置满足一个测试 check(),这个测试对于一个位置 (x,y),要求(col 值不等已自动满足) ran ≠ x and |ran - x| ≠ y + 1。 ran ≠ x 很好理解,就是不为同一列;|ran - x| ≠ y + 1 则意味着左右不在同一斜线上。 ┌──┬──┬──┬──┐ │ │ran │ │ │ 0 ├──┼──┼──┼──┤ │x¹y │ │ │ Q │ 1 ├──┼──┼──┼──┤ │ Q │ │ │x²y │ 2 ├──┼──┼──┼──┤ │ │ │ Q │ │ 3 └──┴──┴──┴──┘ 0 1 2 3 如图,算一算图中的两个点 (x¹,y) 和 (x²,y),是不是满足了上面的式子? 由于这里要同时用到 rst 中点的 col 值和 row 值,这样解决:在前文的算法描述中已经指出,因为 row 值被认为是有序的,事实上是一个解的下标,我们 check 一下这个下标,在 check() 的过程中去获取 col 值不就行了?
代码
def check (pos): # 表达式变个形式,少打点字 return not (ran == rst[pos] or abs(ran - rst[pos]) == pos + 1)
值得注意的是,由于这里 check() 用到了逃逸变量 ran 和 rst,check 函数体就必须写在 safe() 函数体内部以使这它们在其闭包环境中出现。 safe() 函数也就很明了了:先生成一个由全部 check(pos), pos ∈ [0, #rst] 结果组成的列表,然后判断一下这个列表中每一项是否都为真。假设我们已得到这样的一种测试一个列表中元素是否全为 True 的函数叫 ands()。
代码
def safe (ran, rst): def check (pos): return not (ran == rst[pos] or abs(ran - rst[pos]) == pos + 1) return ands([check(pos) for pos in range(len(rst))])
前文已经说明,ands() 函数接受一个列表为参数,如果列表中每一项都为 True 则返回 True,否则返回 False。还是直接把数学定义抄一遍:
代码
def ands (ls): return True if ls == [] \ else (False if not ls[0] \ else ands(ls[1:]))
于是,我们的程序就写完啦!试着跑一下 queens(4,4),
代码
>>> queens(4,4) [[1, 3, 0, 2], [2, 0, 3, 1]]
没问题!再跑一下 queens(8,8)!奇怪,为什么跑了 10 分钟还没出结果? 问题在哪儿: 通用列表操作:
代码
[[ran] + rst \ for ran in range(row) \ for rst in queens(row, col - 1) \ if safe(ran, rst)]
首先对最耗时的列表 queens(row, col - 1) 应用群体操作 flatmap(newcol, queens(row, col - 1))。这里需要注意的是,光用 map() 是不够的,因为下面会根据另一个列表 rang(row) 中的每一项产生一个新列表,导致多出一个列表层。所以我们需要用 flatmap() 函数,它在普通的 map() 操作之后会用 append() 把产生的列表联结起来(所以叫“展平”的 map),把多出的一层列表消去:
代码
def flatmap (opt, ls): # 小技巧:累积器在连接列表时可以不用 lambda x,y: x+y,直接用 list.__add__, # 重点是类型出错时有报警 return reduce(list.__add__, map(opt, ls))
接下来,根据列表和列表生成列表(在通用列表操作的世界中,就是 list, list, and list),注意原来的形式 [ran] + rst 是怎么被函数封装掉的:
代码
def newcol (rst): # 顺手解决 filter 只能传递一个参数的限制,用 lambda 代掉 return filter(lambda ls: safe(ls[0], rst), map(lambda x: [x] + rst, range(row)))
其它什么内容都不用改了(当然了,如果你有心情,还可以把另一个列表领悟替换掉,
代码
def safe (ran, rst): return every(check, range(len(rst))) # every() 函数相当于打过兴奋剂的 ands def every (test, ls): return True if ls == [] \ else (False if not test(ls[0]) \ else every(test, ls[1:]))
不过单层的列表领悟没有性能问题)。现在可以完美地输出八皇后问题的 92 个解了(这里就不列了,太长了)。 反思:
最后留个小问题:我们在算法描述一节中提到了一种解的表示方法,把一个格局中所有棋子的坐标表示为序对,然后返回解的列表。请给出一个小函数,把我们现在的实现版本输出的解转换为这种形式。数据的本质是信息。信息的完全是首要的,数据的表示只是个次要问题。 |
《 用 Python 秒掉八皇后问题! 》 的评论也很精彩,欢迎您也添加评论。查看详细 >>
推荐相关文章:
测试驱动的一点疑问
HTTP状态码
JavaEye推荐
上海乐福狗信息技术有限公司:诚聘技术经理和开发工程师
免费下载IBM社区版软件--它基于开放的标准,支持广泛的开发类型,让您的开发高效自主!
京沪穗蓉四地免费注册,SOA技术高手汇聚交锋.
上海:优秀公司德比:高薪诚聘 资深Java工程师
广州:优易公司:诚聘Java工程师,开发经理
上海:尤恩斯国际集团:诚聘开发工程师
北京:优秀公司NHNChina招聘:WEB开发,系统管理,JAVA开发, DBA