[关闭]
@yiltoncent 2015-04-03T14:59:12.000000Z 字数 3673 阅读 3295

《ANSI COMMON LISP》第三章 习题

LISP


1. 用箱子表示法表示以下列表:
最后一个(a (b . c) d)

答案
这个答案有问题,最后的d前面的指针应该指向一个cons对象,cons对象的car指向dcdr指向NIL

2. 写一个保留原本列表中元素顺序的union版本:

  1. > (new-union '(a b c) '(d a b))
  2. (A B C D)
  1. (defun new_union (x y)
  2. (let ((rx (reverse x)))
  3. (dolist (mb y)
  4. (if (not (member mb x))
  5. (push mb rx)))
  6. (reverse rx)))

3. 定义一个函数,接受一个列表并返回一个列表,指出相等元素出现的次数,并由由最常见至最少见的排序:

  1. > (occurrences '(a b a d a c d c a))
  2. ((A . 4) (C . 2) (D . 2) (B . 1))
  1. (defun occurrences (x)
  2. (if (not (consp x))
  3. nil
  4. (let ((out nil))
  5. (dolist (obj x) 轮询列表的每个元素
  6. (let ((alist (assoc obj out))) 从输出关联列表中寻找是否有相应的key
  7. (if alist 如果存在则自增相应关联列表的value
  8. (incf (cdr alist))
  9. (push (cons obj 1) out)))) 否则创建新的关联列表并压入到输出列表中
  10. (sort out #'> :key #'cdr)))) 使用sort排序

回顾

多数CL函数接受一个或多个 关键字参数。 这些关键字参数不是把对应的参数放在特定的位置作匹配,而是在函数调用中用特殊标签,称为关键字,来匹配。

4. 为什么(member '(a) '((a) (b)))返回nil
因为member默认采用eql来比较对象,很明显'(a)((a) (b))里面的不是一个对象。如果(member '(a) '((a) (b)) :test #'equal)则返回((A) (B))

5. 假设函数pos+接受一个列表并返回把每个元素加上自己的位置的列表:

  1. > (pos+ '(7 5 1 4))
  2. (7 6 3 7)
  1. (defun pos+ (list) 命令式编程 我自己做的
  2. (let ((len (length list)))
  3. (if (zerop len)
  4. nil
  5. (let ((output nil) (idx 0))
  6. (dolist (obj list)
  7. (progn
  8. (push (+ obj idx) output)
  9. (setf idx (+ idx 1))))
  10. (reverse output)))))
  11. (defun pos+ (list) 函数式编程,答案里面代码最短的
  12. (let ((i -1))
  13. (mapcar #'(lambda (x) (+ x (incf i))) list)))

差距还是挺大的,这里面最优雅的方式就是用lambda函数,用mapcar对队列中每个元素单独处理。优美之极。

  1. 经过好几年的审议,政府委员会决定列表应该由cdr指向第一个元素,而car指向剩下的列表。定义符合政府版本的以下函数:
  1. (a) cons
  2. (b) list
  3. (c) lengt (for lists)
  4. (d) member (for lists; no keywords)
  1. (a)
  2. (defun cons (x y)
  3. (let ((ls '(nil . nil)))
  4. (setf (cdr ls) x
  5. (car ls) y)
  6. ls))
  7. (b) 要解决 3.6 (b),你需要使用到 6.3 节的参数 &rest
  8. (c)
  9. (defun length (ls)
  10. (if ls
  11. (+ 1 (length (car ls)))
  12. 0)) 递归方法用的真多
  13. (d)
  14. (defun member (obj ls)
  15. (if (consp ls)
  16. nil
  17. (if (eql (cdr ls) obj)
  18. ls
  19. (member obj (car ls)))))
  1. 定义一个函数,接受一个列表并用点状表示法打印出来:
  1. > (showdots '(a b c)
  2. (A . (B . (C . NIL)))
  1. 我写的,不能处理嵌套的情况 还是过程式思维
  2. (defun showdots (x)
  3. (let ((len (length x)))
  4. (mapcar #'(lambda (x) (format t "(~A . " x)) x)
  5. (format t "NIL ")
  6. (do ((i 1 (+ i 1)))
  7. ((> i len) 'nil)
  8. (format t ")"))))
  9. (defun showdots (ls)
  10. (format t "~A" (showdots-rec ls)))
  11. (defun showdots-rec (ls)
  12. (if ls
  13. (if (atom ls)
  14. ls
  15. (format nil "(~A . ~A)" (showdots-rec (car ls)) (showdots-rec (cdr ls))))))
  16. 递归的使用,函数式编程思维。代码简单。

9. 写一个程序来找到3.15节里表示的网络,最长有限的路径(不重复)。网络可能包含循环。

  1. (defun longest-path (start end net)
  2. (bfs-l end (list (list start)) net nil))
  3. (defun bfs-l (end queue net sol)
  4. (if queue
  5. (let ((path (car queue)))
  6. (let ((node (car path)))
  7. (bfs-l end
  8. (append (cdr queue) (new-paths path node net))
  9. net
  10. (if (eql node end) path sol))))
  11. (reverse sol)))
  12. (defun new-paths (path node net)
  13. (let ((acc nil))
  14. (dolist (x (cdr (assoc node net)))
  15. (or (member x path)
  16. (push (cons x path) acc)))
  17. acc))
  18. (longest-path 'a 'd *net*)
  19. 0: (LONGEST-PATH A D ((A B C) (B A C) (C A B D) (D C)))
  20. 1: (BFS-L D ((A)) ((A B C) (B A C) (C A B D) (D C)) NIL)
  21. 2: (NEW-PATHS (A) A ((A B C) (B A C) (C A B D) (D C)))
  22. 2: NEW-PATHS returned ((C A) (B A))
  23. 2: (BFS-L D ((C A) (B A)) ((A B C) (B A C) (C A B D) (D C)) NIL)
  24. 3: (NEW-PATHS (C A) C ((A B C) (B A C) (C A B D) (D C)))
  25. 3: NEW-PATHS returned ((D C A) (B C A))
  26. 3: (BFS-L D ((B A) (D C A) (B C A)) ((A B C) (B A C) (C A B D) (D C))
  27. NIL)
  28. 4: (NEW-PATHS (B A) B ((A B C) (B A C) (C A B D) (D C)))
  29. 4: NEW-PATHS returned ((C B A))
  30. 4: (BFS-L D ((D C A) (B C A) (C B A))
  31. ((A B C) (B A C) (C A B D) (D C)) NIL)
  32. 5: (NEW-PATHS (D C A) D ((A B C) (B A C) (C A B D) (D C)))
  33. 5: NEW-PATHS returned NIL
  34. 5: (BFS-L D ((B C A) (C B A)) ((A B C) (B A C) (C A B D) (D C))
  35. (D C A))
  36. 6: (NEW-PATHS (B C A) B ((A B C) (B A C) (C A B D) (D C)))
  37. 6: NEW-PATHS returned NIL
  38. 6: (BFS-L D ((C B A)) ((A B C) (B A C) (C A B D) (D C)) (D C A))
  39. 7: (NEW-PATHS (C B A) C ((A B C) (B A C) (C A B D) (D C)))
  40. 7: NEW-PATHS returned ((D C B A))
  41. 7: (BFS-L D ((D C B A)) ((A B C) (B A C) (C A B D) (D C))
  42. (D C A))
  43. 8: (NEW-PATHS (D C B A) D ((A B C) (B A C) (C A B D) (D C)))
  44. 8: NEW-PATHS returned NIL
  45. 8: (BFS-L D NIL ((A B C) (B A C) (C A B D) (D C)) (D C B A))
  46. 8: BFS-L returned (A B C D)
  47. 7: BFS-L returned (A B C D)
  48. 6: BFS-L returned (A B C D)
  49. 5: BFS-L returned (A B C D)
  50. 4: BFS-L returned (A B C D)
  51. 3: BFS-L returned (A B C D)
  52. 2: BFS-L returned (A B C D)
  53. 1: BFS-L returned (A B C D)
  54. 0: LONGEST-PATH returned (A B C D)
  55. (A B C D)

(or (member x path) 这句比较关键,new-paths只取那些路径长的,因此如果节点已经是path的成员了,表示有循环产生,此时就不记录这条路径。
bfs-l的基本思路:对于维护了未搜索路径的序列queue,取其第一个未搜索路径及其第一个节点(节点是倒序放置的),由该未搜索路径及第一个节点,利用new-paths来找到该节点的下方节点,并生成新的未搜索路径并加入到queue的后端。BFS算法对于每层都搜索完毕才会进入下一层,因此叫做广度优先搜索。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注