最近研究函数式编程,都是haskell和scheme交互着看的,所以笔记中两种语言的内容都有,练习一般也都用两种语言分别实现.

本篇练习一些数组有关的问题,之所以与数组相关是因为在命令式编程中以下问题的核心数据结构主要是数组,而在scheme和haskell中主要是用list来实现.

scheme中没有数组这个数据结构,所以需要用list来实现类似数组的操作,下面首先定义了一些辅组函数用于操作和显示数组,

(define (gen-matrix width hight f)
(define (gen-row x y row matrix)
(if (>= x width) (cons (reverse row) matrix)
(gen-row (+ x 1) y (cons (f x y) row) matrix)))
(define (gen y matrix)
(if (>= y hight) matrix
(gen (+ y 1) (gen-row 0 y '() matrix))))
(reverse (gen 0 '()))) (define (show-matrix matrix)
(define (show-row row)
(if (not (null? row)) (begin (display (car row))(display "\n")(show-row (cdr row)))))
(show-row matrix)) (define (get-matrix-size matrix)
(if (null? matrix) '()
(if (null? (car matrix)) '()
(list (length (car matrix)) (length matrix)))))

gen-matrix用于生成一个width X hight的矩阵,f是一个形如(lambda (x y))的函数,用于输出x y位置的内容,例如:

(gen-matrix 4 4 (lambda (x y) (if (and (= x 2) (= y 2)) 1 0)

将生成一个(2 2)位置为1,其余位置为0的4X4矩阵.

show-matrix用于将列表形式的矩阵以矩形的方式输出到屏幕,例如:

(show-matrix (gen-matrix 4 4 (lambda (x y) (if (and (= x 2) (= y 2)) 1 0))))

将输出

(0 0 0 0)
(0 0 0 0)
(0 0 1 0)
(0 0 0 0)

get-matrix-size用于获得一个矩阵的width和hight其返回值是一个list,(car list) = width (cadr list) = hight

(define (member? xs x)
(cond
[(null? xs) #f]
[else (if (equal? x (car xs)) #t (member? (cdr xs) x))]))

member?函数用于判断一个x在xs中是否存在,此函数在下面的几个示例中用到.

迷宫

给定一个迷宫地图,输入起始点和目标点,输出一条从起始点到目标点的路径,首先来看下scheme的代码

(define maze1  '((1 1 1 1 1 1 1 1 1)
(1 0 1 0 0 0 1 0 1)
(1 0 1 0 1 0 1 0 1)
(1 0 1 0 1 0 1 0 1)
(1 0 0 0 0 0 0 0 1)
(1 1 1 1 1 1 1 1 1))) ;返回一条路径
(define (findpath-one maze from to)(define (findpath-one maze from to)
(letrec* ( [direction '((0 -1) (0 1) (-1 0) (1 0))]
[arrive? (lambda (cur) (and (= (car cur) (car to)) (= (cadr cur) (cadr to))))]
[moveable? (lambda (x y)
(cond
[(> y (length maze)) #f]
[else (let ([line (list-ref maze y)])
(if (> x (length line)) #f (= (list-ref line x) 0)))]))]
[foreach-dir (lambda (dirs pos path close)
(cond
[(null? dirs) '()]
[else (let* ([dir (car dirs)]
[dirx (car dir)]
[diry (cadr dir)]
[nextpos (list (+ (car pos) dirx) (+ (cadr pos) diry))]
[ret (move nextpos path close)])
(if (not (null? ret)) ret (foreach-dir (cdr dirs) pos path close)))]))]
[move (lambda (pos path close)
(if (arrive? pos) (reverse (cons pos path))
(if (or (not (moveable? (car pos) (cadr pos))) (member? close pos)) '()
(foreach-dir direction pos (cons pos path) (cons pos close)))))])
(cond
[(arrive? from) (list from)]
[(or (not (moveable? (car from) (cadr from))) (not (moveable? (car to) (cadr to)))) '()]
[else (foreach-dir direction from (list from) (list from))])))

使用经典的回溯算法,从当前点出发,遍历direction中的四个方向,如果往一个方向前进的时候遇到阻挡,则回溯到上一层去尝试下一个方向。如果方向用完了则表明从当前点无法到达目标,继续回溯到上一层.如果回溯到第一层且方向用完表明从起始点没有到达目标点的路径.这里用了一个辅助的数据结构close表,用于保存已经走过的路径,用于避免路径探测的时候走回头路导致死循环.

要想将结果显示在屏幕上可以定义如下函数:

(define (showmaze maze path)
(let ([matrix-size (get-matrix-size maze)])
(define matrix (gen-matrix (car matrix-size) (cadr matrix-size) (lambda (x y)
(if (member? path (list x y)) '*
(list-ref (list-ref maze y) x)))))
(show-matrix matrix))
)

通过输入一个地图和路径就可以把寻路结果显示到屏幕中,例如:

(showmaze maze1 (findpath-one maze1 '(1 1) '(3 3)))

输出

(1 1 1 1 1 1 1 1 1)
(1 * 1 0 0 0 0 0 1)
(1 * 1 0 1 0 1 0 1)
(1 * 1 * 1 0 1 0 1)
(1 * * * 0 0 1 0 1)
(1 1 1 1 1 1 1 1 1)

接着来看下haskell的版本

import qualified Data.Set as Set
-- 走迷宫
--module Maze
--(
-- FindOne
--) where --返回指定下标的元素
elemat :: [maybe] -> Int -> maybe
elemat xs idx =
if idx >= length xs then error "index out of range"
else fetch xs 0
where fetch (x:xs) acc =
if acc == idx then x
else fetch xs (acc+1) -- 检查输入点是否可移动
movable ::[[Int]] -> (Int,Int) -> Bool
movable maze (x,y) =
if y < length maze then
let line = elemat maze y
in if x < length line then
elemat line x == 0
else False
else False -- 输出一条路径
findonepath :: [[Int]] -> (Int,Int) -> (Int,Int) -> [(Int,Int)]
findonepath maze from to
| not (movable maze from) || not (movable maze to) = []
| otherwise = foreachdir direction from [from] $ Set.fromList []
where
direction = [(0,-1),(0,1),(-1,0),(1,0)] -- 4个移动方向
foreachdir dirs (x,y) path close
| null dirs = []
| otherwise =
let
(dirx,diry) = head dirs
nextpos = (x+dirx,y+diry)
ret = move path close nextpos
in
if null ret then
foreachdir (tail dirs) (x,y) path close
else ret
move path close (x,y)
| (x,y) == to = reverse ((x,y):path) --到达目的地
| otherwise =
if Set.member (x,y) close || not (movable maze (x,y)) then []
else foreachdir direction (x,y) ((x,y):path) $ Set.insert (x,y) close

与scheme版本区别的地方有两点:

  • 没有list-ref方法,所以定义了一个辅组函数elemat用于取给定下标的list元素
  • 使用Data.Set作为close列表的数据结构

八皇后

八皇后问题也是一个经典的回溯算法问题,解题方法与迷宫问题类似:

  • 在当前行的0-N-1的位置中寻找一个合法位置放置皇后,如果找到跳到下面一步,否则说明在当前行的任何位置放置皇后都不能有合法的解,回溯到上一行,

    如果已经回溯到了第一行,切尝试过第一行的所有位置,说明问题没有任何的合法解
  • 进入下一行,如果当前行号大于等于N,输出一个结果,否则执行步骤1

下面是找出一个八皇后解的完整代码:

(define (puzzle size)
(define (vaild? queen pos);判断当前位置是否可以放置皇后
(define (check xs)
(if (null? xs) #t
(let ([x (car (car xs))]
[y (cadr (car xs))])
(cond [(= x (car pos)) #f]
[(= (abs (- x (car pos))) (abs (- y (cadr pos)))) #f]
[else (check (cdr xs))]))))
(check queen))
(define (foreach-row x y queen result)
(cond
[(>= x size) result]
[(>= y size) (cons queen result)]
[else (let ([newresult (if (vaild? queen (list x y))
(foreach-row 0 (+ y 1) (cons (list x y) queen) result)
result)])
(foreach-row (+ x 1) y queen newresult))]))
(let ([result (foreach-row 0 0 '() '())])
(define (show xs)
(if (not (null? xs))
(begin (display "------result-------\n")
(show-matrix (gen-matrix size size (lambda (x y) (if (member? (car xs) (list x y)) '* " "))))
(show (cdr xs)))))
(show result)
(display "total solution:")(display (length result))(display "\n")))

haskell的实现

--判断皇后是否可以合法放置
vaild :: [(Int,Int)] -> (Int,Int) -> Bool
vaild [] _ = True
vaild xs (x,y) = foldr (\q acc -> if (x == (fst q)) || (abs (x - fst q)) == (abs (y - snd q)) then False else acc) True xs foreachrow :: (Int,Int) -> Int -> [(Int,Int)] -> [[(Int,Int)]] -> [[(Int,Int)]]
foreachrow (x,y) size queen result
| x >= size = result
| y >= size = (queen:result)
| otherwise = let newresult = if vaild queen (x,y) then foreachrow (0,y+1) size ((x,y):queen) result
else result
in foreachrow (x+1,y) size queen newresult puzzle :: Int -> Int
puzzle 0 = 0
puzzle size = length $ foreachrow (0,0) size [] []

蛇形矩阵

输入2,输出:

1 2
4 3

输入3,输出:

1 2 3
8 9 4
7 6 5

依此类推.

先简单描述下算法,初始时矩阵全为0,向左移动并将计数值1写到起始位置(0 0),一直向当前方向移动,直到遇到碰撞,切换移动方向.碰撞的条件是x y坐标超出矩阵范围或x y位置的值不为0.

为了处理二维数组添加以下的辅助函数:

;1维,2维数组
;数组起始下标为0
(define (make-array n init) (rep init n))
(define (array-at array n) (element-at array (+ n 1)))
(define (array-replace-at array n new) (replace array new (+ n 1))) (define (make-array2d width hight init) (make-array hight (make-array width init))) (define (array2d-at array2d c r)
(let ([row (if (> (length array2d) r) (array-at array2d r) '())])
(if (null? row) "idx out of range"
(if (> c (length row)) "idx out of range"
(array-at row c))))) (define (array2d-replace-at array2d c r new)
(let ([row (if (> (length array2d) r) (array-at array2d r) '())])
(if (null? row) "idx out of range"
(if (> c (length row)) "idx out of range"
(array-replace-at array2d r (array-replace-at row c new))))))

下面是主函数

(define (snake size)
(define maxc (* size size))
(define (snake-imp c matrix cur direction)
(if (> c maxc) matrix
(let* ([curx (car cur)]
[cury (cadr cur)]
[tmpx (+ curx (caar direction))]
[tmpy (+ cury (cadar direction))]
[newmatrix (array2d-replace-at matrix curx cury c)]
[newdirection (if (or ;检测是否需要调整方向
(> 0 tmpx)
(>= tmpx size)
(> 0 tmpy)
(>= tmpy size)
(not (= 0 (array2d-at newmatrix tmpx tmpy)))) (lshift direction 1)
direction)]
[newx (+ curx (caar newdirection))]
[newy (+ cury (cadar newdirection))])
(snake-imp (+ c 1) newmatrix (list newx newy) newdirection))))
(snake-imp 1 (make-array2d size size 0) '(0 0) '((1 0) (0 1) (-1 0) (0 -1))))

TSPL学习笔记(4):数组相关练习的更多相关文章

  1. DSP EPWM学习笔记2 - EPWM相关寄存器设置问题解析

    DSP EPWM学习笔记2 - EPWM相关寄存器设置问题解析 彭会锋 本篇主要针对不太熟悉的TZ 故障捕获 和 DB 死区产生两个子模块进行学习研究 感觉TI的寄存器命名还是有一定规律可循的 SEL ...

  2. PHP学习笔记之数组篇

    摘要:其实PHP中的数组和JavaScript中的数组很相似,就是一系列键值对的集合.... 转载请注明来源:PHP学习笔记之数组篇   一.如何定义数组:在PHP中创建数组主要有两种方式,下面就让我 ...

  3. JavaScript学习笔记之数组(二)

    JavaScript学习笔记之数组(二) 1.['1','2','3'].map(parseInt) 输出什么,为什么? ['1','2','3'].map(parseInt)//[1,NaN,NaN ...

  4. [学习笔记]JS 数组Array push相关问题

    前言: 今天用写了一个二维数组,都赋值为零,然后更新其中一个值,结果和预期是不一样,会整列的相同位置都是同一个值. 1.用Chrome的控制台样例如下: arrs[2][2] =1的赋值,竟然是三个数 ...

  5. [Golang学习笔记] 07 数组和切片

    01-06回顾: Go语言开发环境配置, 常用源码文件写法, 程序实体(尤其是变量)及其相关各种概念和编程技巧: 类型推断,变量重声明,可重名变量,类型推断,类型转换,别名类型和潜在类型 数组: 数组 ...

  6. go 学习笔记之数组还是切片都没什么不一样

    上篇文章中详细介绍了 Go 的基础语言,指出了 Go 和其他主流的编程语言的差异性,比较侧重于语法细节,相信只要稍加记忆就能轻松从已有的编程语言切换到 Go 语言的编程习惯中,尽管这种切换可能并不是特 ...

  7. Scala入门学习笔记三--数组使用

    前言 本篇主要讲Scala的Array.BufferArray.List,更多教程请参考:Scala教程 本篇知识点概括 若长度固定则使用Array,若长度可能有 变化则使用ArrayBuffer 提 ...

  8. JavaScript新手学习笔记1——数组

    今天,我复习了一下JavaScript的数组相关的知识,总结一下数组的API: 总共有11个API:按照学习的先后顺序来吧,分别是: ① toString()  语法:arr.toString(); ...

  9. JavaScript学习笔记:数组reduce()和reduceRight()方法

    很多时候需要累加数组项的得到一个值(比如说求和).如果你碰到一个类似的问题,你想到的方法是什么呢?会不会和我一样,想到的就是使用for或while循环,对数组进行迭代,依次将他们的值加起来.比如: v ...

随机推荐

  1. RaisingStudio.PackageManager 发布 1.0版

    从此免去命今行打包的痛苦,安装本Module后,可以在Dashbaord的Modules里看到一个Download页,进入可看到全部已安装模块列表(与Installed页内容一致),可搜索找到要打包的 ...

  2. ServiceStack.Redis之IRedisClient<第三篇>

    事实上,IRedisClient里面的很多方法,其实就是Redis的命令名.只要对Redis的命令熟悉一点就能够非常快速地理解和掌握这些方法,趁着现在对Redis不是特别了解,我也对着命令来了解一下这 ...

  3. CentOS7 安装 mongodb

    https://docs.mongodb.com/manual/tutorial/install-mongodb-enterprise-on-red-hat/

  4. 我的fckeditor实践

    一开始我不懂这个ConnectorServlet是何用处,后来发现是专门用于文件上传的,因为fckeditor默认是不支持这个功能的. ConnectorServlet: /* * FCKeditor ...

  5. atitit.编程语言 程序语言 的 工具性 和 材料性 双重性 and 语言无关性 本质

    atitit.编程语言 程序语言 的 工具性 和 材料性 双重性 and 语言无关性 本质 #---语言的 工具和材料双重性 有的人说语言是个工具,有的人说语言是个材料..实际上语言同时属于两个属性. ...

  6. JavaScript-分支语句与函数

    一.分支语句-if语句 四种if语句: 1.if(判断条件) { 满足条件时需执行的语句 } 2.if(判断条件) { 满足条件时需执行的语句 } else { 不满足条件时需执行的语句 } 3.if ...

  7. 使用bee自动生成api文档

    beego中的bee工具可以方便的自动生成api文档,基于数据库字段,自动生成golang版基于beego的crud代码,方法如下: 1.进入到gopath目录的src下执行命令: bee api a ...

  8. maven源码分析- mvn.bat分析

    第一次知道MAVEN是在2008年,当时想分析geoserver这个开源项目,发现该项目采用了maven进行项目管理,当时粗略的学习了一下.真正在工作中使用是在09年下半年,个人感觉使用起来还是非常好 ...

  9. Linux中如何产生core文件?

      在程序不寻常退出时,内核会在当前工作目录下生成一个core文件(是一个内存映像,同时加上调试信息).使用gdb来查看core文件,可以指示出导致程序出错的代码所在文件和行数.   1.core文件 ...

  10. Inno Setup 卸载前关闭进程或服务 x86 x64

    1.32位程序的PSVince.dll插件方法. [Setup] AppName=PSVince AppVerName=PSVince 1.0 DisableProgramGroupPage=true ...