2013年3月11日星期一

笔记:Land of Lisp

chap 2 - chap 4

(defparameter *foo* 6)  全局变量,可修改
(defvar *foo* 5) 全局变量,只在定义时赋值一次
(let (a 5)) 词法变量

defun 定义全局函数
flet 定义局部函数,函数间不可相互引用
labels 定义局部函数,函数间可相互引用

'(expt 2 3) 数据模式
(expt 2 3) 代码模式

列表连接
> (cons 'pork (cons 'beef (cons 'chicken ())))
(PORK BEEF CHICKEN)

首个元素
> (car '(pork beef chicken))
PORK

除掉首个元素之后的列表
> (cdr '(pork beef chicken))
(BEEF CHICKEN)

除掉首个元素之后的列表,之后再取首个元素 =》 第二个元素嘛。。。
> (cadr '(pork beef chicken))
BEEF

还有 cddr, caddr, cddar, cadadr 等等

按顺序执行语句,最后一条语句的运行结果作为返回值
> (progn (setf *number-was-odd* t)
'odd-number)

when / unless 与progn类似,不过多一个判断条件

cond 条件可以写的比较复杂,case一般用于字符串比对

if 的结构相对简单,一个判定条件,2个分支
> (if (member 1 '(3 4 1 5))
'one-is-in-the-list
'one-is-not-in-the-list)

检查1个元素是否在列表里,如果存在,则返回以该元素为起始的剩余元素列表
> (member 1 '(3 4 1 5))
(1 5)

指定函数,对列表做grep
> (find-if #'oddp '(2 4 5 6))

eq 做符号比较
equal 做多种类型数据比较
eql 做数值和符号比较
equalp 比较复杂的比较

chap 5 - chap 6

mapcar相当于列表做函数映射(perl中的map)

apply的第一个参数指定调用函数,后面是调用该函数时传入的参数列表
>(defun describe-paths (location edges)
(apply #'append (mapcar #'describe-path (cdr (assoc location edges)))))


根据指定的key找出符合条件的记录
> (find 'y '((5 x) (3 y) (7 z)) :key #'cadr)
(3 Y)

print 每个元素单独打一行
prin1 不加换行符打印
princ 打印字符
read 输入
#\newline #\tab #\space 换行符、tab符、空格符


转换字符串列表
>(defun tweak-text (lst caps lit)
  (when lst
    (let ((item (car lst))
          (rest (cdr lst)))
      (cond ((eql item #\space) (cons item (tweak-text rest caps lit)))
            ((member item '(#\! #\? #\.)) (cons item (tweak-text rest t lit)))
            ((eql item #\") (cons item (tweak-text rest caps (not lit))))
            (lit (cons item (tweak-text rest nil lit)))
            (caps (cons (char-upcase item) (tweak-text rest nil lit)))
            (t (cons (char-downcase item) (tweak-text rest nil nil)))))))

> (tweak-text  '(#\a #\Space #\b) 't 'nil)
(tweak-text  '(#\a #\Space #\b) 't 'nil)

总共6个可能:
  1. 如果是空格,原样输出
  2. 如果是!/?/. 等结束符,下个字符就转成大写(tweak-text rest t lit)
  3. 如果是"子字符串,则转换之前的lit模式
  4. 如果正处于lit模式,则不强制转换成大写 (tweak-text rest nil lit)
  5. 如果caps为真,则强制转换为大写,后面默认不转换(cons (char-upcase item) (tweak-text rest nil lit))
  6. 否则,转换为小写,后面默认不转换 (cons (char-downcase item) (tweak-text rest nil nil))
> (defun game-print (lst)
 (princ (coerce (tweak-text (coerce (string-trim "() "
 (prin1-to-string lst))
'list)
t
 nil)
'string))
(fresh-line))

> (game-print  '(i am a. test "c aNnd d" b))
I am a. Test "c aNnd d" b


注意game-print的处理过程:




lambda函数

> (mapcar (lambda (n) (/ n 2)) '(2 4 6))
(1 2 3)

chap 7

循环列表,将foo尾部指针指向foo首部
> (defparameter foo '(1 2 3))
FOO
> (setf (cdddr foo) foo)
#1=(1 2 3 . #1#)
把exp中非字母数字类的字符都转换成_
(defun dot-name (exp)
(substitute-if #\_ (complement #'alphanumericp) (prin1-to-string exp)))

截取字符串s的前 *max-label-length* -3 个字符,后面加3个点
(concatenate 'string (subseq s 0 (- *max-label-length* 3)) "...")

一张edges图中有多个node,每个node有多个edge,按结构嵌套处理
(defun edges->dot (edges)
  (mapc (lambda (node)
    (mapc (lambda (edge)
      (fresh-line)
      (princ (dot-name (car node)))
      (princ "->")
      (princ (dot-name (car edge)))
      (princ "[label=\"")
      (princ (dot-label (cdr edge)))
      (princ "\"];"))
  (cdr node)))
edges))
调用thunk函数,输出的结果写入fname文件,再执行指定shell命令画图
(defun dot->png (fname thunk)
 (with-open-file (*standard-output*
fname
:direction :output :if-exists :supersede)
 (funcall thunk))
(ext:shell (concatenate 'string "dot -Tpng -O " fname)))

maplist对列表的cdr依次调用指定的函数

(defparameter *wizard-edges* '((living-room (garden west door)
(attic upstairs ladder))
(garden (living-room east door))
(attic (living-room downstairs ladder))))

(defun uedges->dot (edges)
 (maplist (lambda (lst)
    (mapc (lambda (edge)
     (unless (assoc (car edge) (cdr lst))
       (fresh-line)
       (princ (dot-name (caar lst)))
       (princ "--")
       (princ (dot-name (car edge)))
       (princ "[label=\"")
       (princ (dot-label (cdr edge)))
       (princ "\"];")))
      (cdar lst)))
edges))

  用maplist遍历edges子集合,注意每次遍历都是取cdr

  每次遍历,先用(cdar lst)取出此次首个对象对应的边的集合

  以首次遍历的节点living-room为例,就有两条边(garden west door)、(attic upstairs ladder)
 
  对于每一条边,检查这条边的节点(car edge) = garden、attic,是否会在后面(cdr lst) =(garden, attic)遇到

  如果后面还会遇到,就先不打印,死撑到最后。。。

  通过maplist、assoc的判断,实现去重,很漂亮!

chap 8

循环

(loop repeat 10
collect 1)

(loop for n from 1 to 10
collect n)


取出所有从node能够到达的节点集合visited

取出node起始的边:(direct-edges node edge-list)

从node指向的节点开始递归遍历:(traverse (cdr edge))
(defun get-connected (node edge-list)
 (let ((visited nil))
(labels ((traverse (node)
(unless (member node visited)
 (push node visited)
 (mapc (lambda (edge)
(traverse (cdr edge)))
(direct-edges node edge-list)))))
(traverse node))
visited))

分割出不相邻的节点集:
从nodes取出一个节点node,然后调用get-connected遍历,得到该节点node能够到达的节点集合connected
对剩余unconnected的节点集合重复上述处理过程 

(defun find-islands (nodes edge-list)
(let ((islands nil))
 (labels ((find-island (nodes)
(let* ((connected (get-connected (car nodes) edge-list))
(unconnected (set-difference nodes connected)))
(push connected islands)
 (when unconnected
(find-island unconnected)))))
(find-island nodes))
islands))

按顶点置换边集合:
把(1 2) (1 3) 换成(1 (2) (3))
(remove-duplicates (mapcar #'car edge-list)) 取出不重复的顶点集
对每个顶点node1,取出对应的边集合(remove-duplicates (direct-edges node1 edge-list)
把每个边集合的顶点换成list形式 (list (cdr edge))

(defun edges-to-alist (edge-list)
   (mapcar (lambda (node1)
            (cons node1
                   (mapcar (lambda (edge)
                            (list (cdr edge)))
                            (remove-duplicates (direct-edges node1 edge-list)
                                              :test #'equal))))
          (remove-duplicates (mapcar #'car edge-list))))

添加cops标记
对于每个顶点的alist集合做处理:(1 (2) (3))
例如node1 = 1 , node1-edges = (2) (3)
如果nod1-edges里的顶点node2,与node1连接的边正好在edges-with-cops边集合里
则把(node2) 转换成(node2 'cops),否则,原样输出edge
(defun add-cops (edge-alist edges-with-cops)
   (mapcar (lambda (x)
            (let ((node1 (car x))
                  (node1-edges (cdr x)))
              (cons node1
`                     (mapcar (lambda (edge)
                              (let ((node2 (car edge)))
                              (if (intersection (edge-pair node1 node2)
                                                  edges-with-cops
                                                  :test #'equal)
                                    (list node2 'cops)
                                  edge)))
                            node1-edges))))
          edge-alist))

检查是否至少有一个cops
(some #'cdr (cdr (assoc n edge-alist)))
mapcan 将map之后的结果都合并成一个列表
> (defun ingredients (order)
     (mapcan (lambda (burger)
                (case burger
                   (single '(patty))
                   (double '(patty patty))
                   (double-cheese '(patty patty cheese))))
             order))
INGREDIENTS
> (ingredients '(single double-cheese double))
'(PATTY PATTY PATTY CHEESE PATTY PATTY)

chap 9

这章草草看过,回头有空重读一下。。。说实话,这个作者真话痨。。。

数组赋值
> (defparameter x (make-array 3))
#(NIL NIL NIL)
> (setf (aref x 1) 'foo)
FOO
> x
#(NIL FOO NIL)
> (aref x 1)
FOO

列表赋值
> (setf foo '(a b c))
(A B C)
> (second foo)
B
> (setf (second foo) 'z)
Z
> foo
(A Z C)

HASH赋值
> foo
#(NIL NIL (X Y Z) NIL)
> (setf (car (aref foo 2)) (make-hash-table))
#S(HASH-TABLE)
> (setf (gethash 'zoink (car (aref foo 2))) 5)
5
> foo
#(NIL NIL (#S(HASH-TABLE (ZOINK . 5)) Y Z) NIL)

aref在数据量较大的时候比nth快得多,nth仅适用于小数据量
> (nth 1 '(foo bar baz))
BAR

用values关键字让函数返回多个值
> (defun foo ()
    (values 3 7))
FOO
> (foo)
3 ;
7

默认只取第1个返回值,也可用multiple-value-bind绑定多个返回值
> (+ (foo) 5)
8
> (multiple-value-bind (a b) (foo)
                       (* a b))
21

hash版本的edges
 (defun hash-edges (edge-list)
   (let ((tab (make-hash-table)))
     (mapc (lambda (x)
            (let ((node (car x)))
               (push (cdr x) (gethash node tab))))
          edge-list)
     tab))

定义结构体
> (defstruct person
             name
             age
             waist-size
             favorite-color)
PERSON

新增一个对象
> (defparameter *bob* (make-person :name "Bob"
                                   :age 35
                                   :waist-size 32
                                   :favorite-color "blue"))
*BOB*

对象属性取值、赋值
> (person-age *bob*)
35
> (setf (person-age *bob*) 36)
36

直接批量赋值
 > (defparameter *that-guy* #S(person :name "Bob" :age 35 :waist-size 32 :favorite-color "blue"))

一些序列处理函数,find-if 过滤、count 计数、position 首次出现的位置、some 至少存在一个元素满足断言、every 每个元素都满足断言
> (find-if #'numberp '(a b 5 d))
5
> (count #\s "mississippi")
4
> (position #\4 "2kewl4skewl")
5
> (some #'numberp '(a b 5 d))
T
> (every #'numberp '(a b 5 d))
NIL
求和
> (reduce #'+ '(3 4 6 5 2))
20

取出最大的偶数
 > (reduce (lambda (best item)
             (if (and (evenp item) (> item best))
                 item
               best))
          '(7 4 6 5 2)
           :initial-value 0)
6

初始化数组
(make-array 5 :initial-contents '(1 2 3 4 5))

以list形式返回map结果
> (map 'list
       (lambda (x)
            (if (eq x #\s)
               #\S
               x))
       "this is a string")
 (#\t #\h #\i #\S #\  #\i #\S #\  #\a #\  #\S #\t #\r #\i #\n #\g)

子字符串
> (subseq "america" 2 6)
"eric" 
排序
> (sort '(5 8 2 4 9 3 6) #'<)
(2 3 4 5 6 8 9)

接口固定,根据参数类型处理(适用于简单判断)
> (defun add (a b)
     (cond ((and (numberp a) (numberp b)) (+ a b))
           ((and (listp a) (listp b)) (append a b))))
ADD
> (add 3 4)
7
> (add '(a b) '(c d))
(A B C D)

接口固定,支持多种参数类型处理(扩展性好)
> (defmethod add ((a number) (b number))
     (+ a b))
ADD
> (defmethod add ((a list) (b list))
     (append a b))
ADD

chap 10

循环例子


chap 11

输出内容到 t 终端 / nil字符串 / stream
> (format t "Add onion rings for only ~$ dollars more!" 1.5)
Add onion rings for only 1.50 dollars more!
NIL
~s会保留",而~a会去掉分隔符
> (format t "I am printing ~s in the middle of this sentence." "foo")
I am printing "foo" in the middle of this sentence.
> (format t "I am printing ~a in the middle of this sentence." "foo")
I am printing foo in the middle of this sentence.
> (format t "I am printing ~10a within ten spaces of room." "foo")
I am printing foo        within ten spaces of room.
> (format t "I am printing ~10@a within ten spaces of room." "foo")
I am printing        foo within ten spaces of room.

打印数组
> (format t "~{I see a ~a... or was it a ~a?~%~}" *animals*)

两种换行
> (progn (format t "this is on one line ~%")
(format t "~%this is on another line"))
this is on one line

this is on another line
> (progn (format t "this is on one line ~&")
(format t "~&this is on another line"))
this is on one line
this is on another line
此外还有数值处理,居中、两端对齐等等,用的时候再翻书

chap 12

写文件
> (with-open-file (my-stream "data.txt" :direction :output)
 (print "my data" my-stream))

读文件
> (with-open-file (my-stream "data.txt" :direction :input)
(read my-stream))
"my data"

写字符串
> (defparameter foo (make-string-output-stream))
> (princ "This will go into foo. " foo)
> (princ "This will also go into foo. " foo)
> (get-output-stream-string foo)
"This will go into foo. This will also go into foo. "

 socket的简单例子
> (defparameter my-socket (socket-server 4321)) ;ON THE SERVER
MY-SOCKET
> (defparameter my-stream (socket-accept my-socket)) ;ON THE SERVER

> (defparameter my-stream (socket-connect 4321 "127.0.0.1")) ;ON THE CLIENT
MY-STREAM
> (print "Yo Server!" my-stream) ;ON THE CLIENT
"Yo Server!"

> (read my-stream) ;ON THE SERVER
"Yo Server!"
> (close my-stream)
T

chap 13

写了一个简单的web server

包括解析url,解析参数,解析http header,根据请求参数进行应答等等


chap 14

介绍函数式编程


chap 15

又是一个游戏


chap 16  宏

;This function is finally safe to use
(defmacro split (val yes no)
 (let1 g (gensym)
 `(let1 ,g ,val
(if ,g
(let ((head (car ,g))
(tail (cdr ,g)))
,yes)
,no))))
其中,gensym是为了避免调用的时候也有变量名为g,导致冲突出错


chap 17

DSL介绍,举了svg的例子,大概就是新建一些函数or宏,生成特定格式的文件or转换为特定格式的内容

比较灵活,容易自行扩展,但只适用于特定领域

chap 18

延迟求值

chap 19

游戏例子,没看

chap 20

还是游戏例子,没看

没有评论:

发表评论