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也算是一对冤家了:一放一取。

(未完待续)

你可能感兴趣的:(C++,c,python,C#,lisp)