lisp初体验-Practical Common Lisp笔记-3.迷你数据库-上

好家伙,这还是本人第一次碰到入门书籍刚hello完world,就讲到数据库这么复杂的东东,而且注意,这里不是怎么连数据库,而是怎样来制作一个数据库,OMG~
或许作者也自知有些夸张(真的么?),本篇开始就申明:此章代码并不求甚解,很多东东自有后面的章节给出详解,这里只是通过具体的应用来加深对lisp的理解和感觉,当然如果学有余力深解下也无不可。

好吧,话不多说,本章的主体案例为:完成一个具有收录、评分功能的cd/music工具(另一种常见的经典案例是“通讯录”,话说在python的简明教程中,能做到这一步就恭喜成为一个pythoner了...)

根据功能,最基本的实现是要有个列表:
*(list 1 2 3)
(1 2 3)

嘿嘿,天然支持啊,那么如果要带上查找功能至少得有key吧:
*(list :a 1 :b 2 :c 3)
(:A 1 :B 2 :C 3)
好了,以上便是lisp式字典了,简单k-v下:
*(getf (list :a 1 :b 2 :c 3) :a)
1

基于这种字典,第一个版本基本就成形了:
*(defun make-cd (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped ripped))
试试:
* (make-cd "Roses" "Kathy Mattea" 7 t)
(:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)

数据格式已经确定了下来,下面就是要存储数据了:
*(defvar *db* nil) #defvar为Lisp的内置宏(暂时理解成默认方法好了)
*DB* #输出结果。db是定义的变量,加*号包夹则为全局变量

添加cd工具:
*(defun add-record (cd) (push cd *db*)) #这里的push也是默认宏,用作添加
试试:
*(add-record (make-cd "Roses" "Kathy Mattea" 7 t))
((:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T))
*(add-record (make-cd "Fly" "Dixie Chicks" 8 t))
((:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T)
(:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T))
*(add-record (make-cd "Home" "Dixie Chicks" 9 t))
((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T)
(:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T)
(:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T))
看起来还不错,添加功能算是实现了,看看我们的DB数据:
**db*
((:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED T)
(:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T)
(:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T))
似乎有点别扭,如果可以变成这样,或许会好些:
TITLE: Home
ARTIST: Dixie Chicks
RATING: 9
RIPPED: T

TITLE: Fly
ARTIST: Dixie Chicks
RATING: 8
RIPPED: T

TITLE: Roses
ARTIST: Kathy Mattea
RATING: 7
RIPPED: T

那么,咱标准化下输出:
*(defun dump-db ()
(dolist (cd *db*)
(format t "~{~a:~10t~a~%~}~%" cd)))
是不是有点晕了,这里的"~"据说很神奇,变化万千(与悟空有的一拼),这里简单解释下此处的几种变化:
~a: 根据数据格式美化输出,对普通字符串无效,对key做大写转换
~t: 制表符,~10t表示隔10位
~{、~}:成对出现,常用于循环体中,用来包夹循环体,可以嵌套
~%: 换行符
这里的dolist也是默认宏,用来循环、遍历list类数据,简化输出用,如果不用的话,代码会变成这个样子:
*(defun dump-db ()
(format t "~{~{~a:~10t~a~%~}~%~}" *db*))
如何取舍,自个儿决定~

输出基本ok了,那么输入呢,加点交互是个不错的主意:
*(defun prompt-read (prompt)
(format *query-io* "~a: " prompt) #query-io被申明为全局变量了
(force-output *query-io*) #force-output是宏,等待输入
(read-line *query-io*)) #read-line是宏,读取输入
组合下:
*(defun prompt-for-cd ()
(make-cd
(prompt-read "Title")
(prompt-read "Artist")
(prompt-read "Rating")
(prompt-read "Ripped [y/n]")))
理论上,有些字段是有必要验证下的:
*(parse-integer (prompt-read "Rating")) #验证是否整形
当然,不是就抛错也不太合理,那么放松下,不是就给个默认值好了:
*(parse-integer (prompt-read "Rating") :junk-allowed t)
虽然不报错,不过NIL似乎也不太好。再精确些:
*(or (parse-integer (prompt-read "Rating") :junk-allowed t) 0) #出错就给0

在lisp中也有专门的"yes/no"宏:
*(y-or-n-p "Ripped [y/n]: ")
于是,上面的代码变成了这个样子:
*(defun prompt-for-cd ()
(make-cd
(prompt-read "Title")
(prompt-read "Artist")
(or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)
(y-or-n-p "Ripped [y/n]: ")))

如过要添加一堆cd,再包装一层循环就更靠谱了:
*(defun add-cds ()
(loop (add-record (prompt-for-cd))
(if (not (y-or-n-p "Another? [y/n]: ")) (return))))
试试看:
*(add-cds)
Title: Rockin' the Suburbs
Artist: Ben Folds
Rating: 6
Ripped [y/n]: y
Another? [y/n]: y
Title: Give Us a Break
Artist: Limpopo
Rating: 10
Ripped [y/n]: y
Another? [y/n]: y
Title: Lyle Lovett
Artist: Lyle Lovett
Rating: 9
Ripped [y/n]: y
Another? [y/n]: n
NIL
恩,不错哦~

作为一个靠谱的工具,至少的,数据得要持久存储吧:
*(defun save-db (filename)
(with-open-file (out filename
:direction :output
:if-exists :supersede)
(with-standard-io-syntax
(print *db* out))))
with-open-file宏很好理解了,打开文件写入,若存在就替换。with-standard-io-syntax宏主要目的是保持写入和即将的读出格式一致,也可以简单理解成序列化(简单理解不算误人子弟吧~)
有存就有取:
*(defun load-db (filename)
(with-open-file (in filename)
(with-standard-io-syntax
(setf *db* (read in)))))
这里with-open-file参数好像少了些,默认就是读么,也不存在覆盖的问题。
还有这里的SETF和上面的GETF也算是一对冤家了:一放一取。

(未完待续)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值