newLISP你也行 --- 列表

  #############################################################################
  # Name:newLISP你也行 --- 列表
  # Author:黄登(winger)
  # Project:http://code.google.com/p/newlisp-you-can-do
  # Gtalk:free.winger@gmail.com
  # Gtalk-Group:zen0code@appspot.com
  # Blog:http://my.opera.com/freewinger/blog/
  # QQ-Group:31138659
  # 大道至简 -- newLISP
  #
  # Copyright 2012 黄登(winger) All rights reserved.
  # Permission is granted to copy, distribute and/or
  # modify this document under the terms of the GNU Free Documentation License,
  # Version 1.2 or any later version published by the Free Software Foundation;
  # with no Invariant Sections, no Front-Cover Texts,and no Back-Cover Texts.
  #############################################################################
 
 
                      这节将带你们进入元初 LIST.
                    一切由LIST开始,也将由LIST结束.
 
 
  一.  创建列表
      Building lists
 
      下面的几个函数可以显式的创建函数.
 
       list   用表达式创建列表
       set    将列表数据赋值给symbol
       append 将若干列表组合成一个新列表
       cons   将一个元素添加到已存在列表的顶端,或者创建一个新列表.(无破坏性)
       push   将一个元素压入列表.(此函数带有破坏性)
       dup    复制表达式
 
      我们可以简单的创建一个列表数据,然后将他赋值给一个symbol,列表前得用引号,防
  止计算:
 
  ;(set 'vowels (quote("a" "e" "i" "o" "u")))
  >(set 'vowels '("a" "e" "i" "o" "u"))
  ("a" "e" "i" "o" "u") ; an unevaluated list
 
 
  1. list , append 和 cons
 
      使用list 可以更自由的创建列表.list 的参数可以是表达式,而这些表达式都会被计
  算.
 
 
  (set 'rhyme (list 1 2 "buckle my shoe" '(3 4) "knock" "on" "the" "door"))
  ; rhyme 现在拥有八个元素
  (1 2 "buckle my shoe" '(3 4) "knock" "on" "the" "door")
 
 
      '(3 4) 使用了单引号,阻止了计算.
 
 
      如果使用过传统LISP的同学肯定对cons非常熟悉,几乎可以算标志性的函数了.他就像
  一条线,把一个个贝壳穿起来.
      在nl里,根据第二个参数的不同,cons会做出不同的操作.
 
 
  >(help cons)
  syntax: (cons <exp-1> <exp-2>)
 
 
      如果第二个参数是一个列表.他会把第一个参数作为一个元素,插入到第二个列表的开
  头,最后这个新的列表被返回,而原来的 2 个参数都不会改变.
 
 
  >(set mo '(a b))
  >(cons 1 mo)
  (1 a b)
  >mo
  (a b)
 
  >(cons 2012 '(12 23))
  (2012 12 23)
 
  >(cons '(a b c) '(静 心 渡))
  ((a b c) 静 心 渡)
  ;整个 '(a b c ) 作为一个元素被插入到 '(静 心 度) 的前面
 
 
      如果第二个参数不是列表,而是元素,则 cons 会创建一个新的列表.
 
 
  >(cons 4 6) ; 创建了一个新列表
  (4 6)
 
 
      append 可以组合多个列表:
 
  >(set 'odd '(1 3 5 7) 'even '(2 4 6 8))
  >(append odd even)
  (1 3 5 7 2 4 6 8)
  ;append 操作的必须是列表,否则会报错
 
 
      下面是list 和append 的差别:
 
 
  (set 'a '(a b c) 'b '(1 2 3))
  (list a b)
  ;-> ((a b c) (1 2 3)) ; list 创建了一个多层列表
  (append a b)
  ;-> (a b c 1 2 3) ; append 创建了一个非单层列表
 
 
      list 把所有的参数都作为互相平等元素,构建一个新的列表.
      append 把所有参数列表中的元素,提取出来,组成一个新的列表.
 
 
  2.  push : 将元素插入列表
      push: pushing items onto lists
 
 
      push 非常强大,也充分体现了nl的灵活性.
      使用 push 可以创建一个全新列表,也可以将一个元素插入到以存在列表的任何地方.
 
 
  >(help push)
  syntax: (push <exp> <list> [<int-index-1> [<int-index-2> ... ]])
  syntax: (push <exp> <list> [<list-indexes>])
  syntax: (push <str-1> <str-2> [<int-index>])
 
 
      先来看第二个语法.
 
 
  >(set 'vowels '("e" "i" "o" "u"))
  ("e" "i" "o" "u")
  >(push (char 97) vowels)
  ("a" "e" "i" "o" "u")
  >vowels
  ("a" "e" "i" "o" "u")
  ;vowels 已经被改变 ("a" "e" "i" "o" "u")
 
 
      第三个可选参数 [<list-indexes>] 就是元素插入的位置. 在lisp中,列表的索引是
  从0 开始的 , 也可以用复数索引,复数代表了从右边开始查找. 注意:在效率上,将数据插
  入到开始和插入到别的地方没什么差别.
 
 
  列表      ( 1   2  "buckle my shoe" (3  4) )
  索引        0   1          2           3
  复索引     -4  -3         -2           -1
 
 
  >(set 'vowels '("a" "e" "i" "o"))
  >(push "u" vowels -1)  ;送负索引 将 "u" 插入到列表的尾部,
  ("a" "e" "i" "o" "u")
  ; vowels 现在的值是 ("a" "e" "i" "o" "u")
  >(set 'evens '(2 6 10))
  (2 6 10)
  >(push 8 evens -2) ; 将 8 插入到 倒数第2个位置
  (2 6 8 10)
  >(push 4 evens 1)  ; 将 4 插入到 第1个位置
  (2 4 6 8 10)
  >evens
  (2 4 6 8 10)
 
 
      如果你提供的列表名是不存在的, push 会自动为你创建一个.
      这种自动创建的特性在nl里还非常多,极大程度的提高了编码的灵活性.
 
- (for (c 1 10)
      (push c number-list -1) ; 不会提示找不到 number-list
      (println number-list))
 
 
  ;输出结果:
 
 
  (1)
  (1 2)
  (1 2 3)
  (1 2 3 4)
  (1 2 3 4 5)
  (1 2 3 4 5 6)
  (1 2 3 4 5 6 7)
  (1 2 3 4 5 6 7 8)
  (1 2 3 4 5 6 7 8 9)
  (1 2 3 4 5 6 7 8 9 10)
 
 
      有很多方法可以产生未排序的数字序列.
 
 
  >(rand 999 10) ;随机生成10 个 0-998 的数
  (612 648 789 110 935 555 524 283 78 774)
  ;这是我此刻的输出结果,你的应该不一样.如果一样那我们太有缘了
 
  > (randomize (sequence 90 99)) ;randomize 用来随机打乱列表中元素的排列顺序
  (99 90 95 92 96 97 93 98 91 94)
 
 
      你可以使用一打的时间,不断的re-wirte and  re-wirte.而这个过程将会越来越有趣
  .
      另一个 和 push 相对应的函数是 pop , 他可以将 列表中的任意一个元素弹出列表.
 
  >(setf mo '(a b c e f))
  '(a b c e f)
 
  >(pop mo)
  a ;pop 的返回值是他弹出去的元素,这点和push不一样
  >mo
  (b c e f)
 
  >(pop mo -2)
  e
  >mo
  (b c f)
 
  3.  dup : 创建重复元素列表
      dup: building lists of duplicate elements
 
 
      dup 可以按我们的需求复制一个元素 n 遍, 然后将他们以列表的形式返回.
 
 
  >(dup 1 6) ; duplicate 1 six times
  (1 1 1 1 1 1)
  >(dup '(1 2 3) 6)
  ((1 2 3) (1 2 3) (1 2 3) (1 2 3) (1 2 3) (1 2 3))
  >(dup x 6)
  (x x x x x x)
 
 
      dup 也可以操作字符串,返回值就是一个更长的列表:
 
  >(dup "x" 6) ; 字符串 "x" 被赋值了6份 然后组合成一个字符串
  "xxxxxx"
 
      如果我们想要他返回列表,而不是字符串可以设置第三个参数为 true .
 
  >(dup "x" 6 true) ;
  ("x" "x" "x" "x" "x" "x")
 
 
 
  二.  列表的整体操作
      Working with whole lists
 
 
      接下来我们介绍如何对 list 就行一些常见的操作.
 
  1.  使用和处理列表
      Using and processing lists
 
 
  (set 'vowels '("a" "e" "i" "o" "u"))
 
- (dolist (v vowels)
      (println (apply upper-case (list v))))
      ;(println (upper-case v))
  A
  E
  I
  O
  U
 
 
      这个例子中用到了 2 个 列表处理函数,一个 dolist 一个 apply .第二个用的有点
  鸡肋. upper-case 会将字符串参数转换成大小.
 
 
  2.  使用 apply 和 map
      Using apply and map
 
      三个例子中 apply 的第一个参数是函数,第二个参数必须是列表.这就是为什么 有个
  (list v)的原因. 之前我们简单的介绍过 apply 和 map 的差别.上面的例子同样可以用
  map 完成. 现在如果对他们的区别感到迷惑,只要记住, 在没有第三个参数的情况下,
  apply 会将列表中所有的元素抽取出来一起传递给, 第二个参数指定的函数.而 map 则是
  将函数逐一应用到每个元素 .
      以后会输入讲解所以不用太担心.
 
 
  >(map upper-case '("a" "e" "i" "o" "u"))
  ("A" "E" "I" "O" "U")
 
 
      map 穿过列表的每一个元素,逐一使用 upper-case 转换他们,然后把结果组合成列表
  返回
 
 
  3.  反转列表
      reverse
 
  ;请使用utf8版本试验下面的语句.
  >(set '数字 '("一" "二" "三" "四" "五"))
  ("一" "二" "三" "四" "五")
 
  >(reverse 数字)
  ("五" "四" "三" "二" "一")
 
  >数字
  ("五" "四" "三" "二" "一")
 
 
      reverse 函数永久的颠倒列表内元素的排列顺序,他具有破坏性
 
 
  4.  排序和随机化
      sort and randomize
 
      randomize 和 sort 看起来互相颠倒. 前者将一个列表,随机混乱排序,不过他是非破
  坏性,他操作和返回的都是原列表的拷贝. 而后者则会将列表内的元素,按从小到大的顺序
  (默认情况) 重新排序,他是破坏性的.
      下面我们创建一个字母表和数字表混合的新列表.然后用 randomize 打乱,  接着用
  sort 重新排序.
 
 
- (for (c (char "a") (char "z"))
      (push (char c) alphabet -1))
- (for (i 1 26)
      (push i numbers -1))
  >(set 'data (append alphabet numbers))
- ("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p"
  "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" 1 2 3 4 5 6 7 8 9 10 11 12
  13 14 15 16 17 18 19 20 21 22 23 24 25 26)
 
  >(randomize data)
- ("a" 4 "l" 17 7 2 "w" 22 19 16 "r" 21 15 8 "z" "p" "t" "b" "x" "y" 26 9 "o"
  "s" "q" 10 14 "j" "d" 3 "n" "i" "f" 1 "g" 20 23 24 "v" "k" 25 11 "u" 5 "e"
  13 6 "c" 18 12 "h" "m")
 
  >(sort data)
- (1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 "a" "b" "c"
   "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w"
   "x" "y" "z")
 
 
      对于不同的数据类型之间的比较,遵循下面的顺序,比如数字小于字符串,字符串小于
  list .
 
      Atoms: nil, true, integer or float, string, symbol, primitive
      Lists: quoted expression, list, lambda, lambda-macro
 
      之前的 sort 排序都是默认从小到大的.我们也可以提供而外的函数,完成特定的顺序
  排序.
 
 
  >(help sort)
  syntax: (sort <list> [<func-compare>])
 
 
      默认情况下, sort 使用 < 做为比较函数.
 
  (sort data <)
 
 
      我们使用 > 看看:
 
 
- (for (c (char "a") (char "z"))
      (push (char c) alphabet -1))
 
  >alphabet
- ("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n"
  "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z")
 
  >(sort alphabet >)
- ("z" "y" "x" "w" "v" "u" "t" "s" "r" "q" "p" "o" "n"
  "m" "l" "k" "j" "i" "h" "g" "f" "e" "d" "c" "b" "a")
 
 
      当然自定义一个比较函数也是可以, 记住在newLISP里大部分的列表操作函数,都可以
  使用自定义函数.
 
 
- (define (shorter? a b) ; 接受2个受比较的参数
      (< (length a) (length b))) ;比较2个参数的长度,如果第一个小于第二个则true
 
  (sort (directory) shorter?)
 
  ;输出排序后的文件名列表,短文件名在前,长文件名在后
- ("." "tk" "me" ".." "tip" "sen" "api" "util" "temp" "code" "tools" "3.txt"
   "1.txt" "nl.dll" "gs.bat" "cd.dll" "windows" "tst.txt" "modules" "iup.txt"
   "iup.dll" "COPYING" "link.lsp"  "init.lsp" "hack.lsp" "examples" "SciTE.exe"
   "jres.bat1" "iupim.dll"  "iupcd.dll" "guiserver" "winger.lsp" "unlink.lsp"
   "readme.txt" "newlispdoc" "iupole.dll" "iupgtk.dll" "index.html" ...)
 
 
      使用匿名函数,可以写的更短:
 
      (sort (directory) (fn (a b) (< (length a) (length b))))
 
      fn 是 lambda 的缩写 , 都能用来申明匿名函数.
 
 
  5.  列表去重
      unique
 
  >(set 'data '( 1 1 2 2 2 "nice" 2 2 2 3 2 "girl" 4 4 4))
  ( 1 1 2 2 2 "nice" 2 2 2 3 2 "girl" 4 4 4)
 
  >(unique d)
  (1 2 "nice" 3 "girl" 4)
 
 
  6.  踏平列表
      flat
 
      flat 可以把将多层列表,变成成一层列表.
 
  (set 'data '(0 (0 1 2) 1 (0 1) 0 1 (0 1 2) ((0 1) 0)))
  >(length data)
  8
 
  >(length (flat data))
  15
 
  >(flat data)
  (0 0 1 2 1 0 1 0 1 0 1 2 0 1 0)
 
  >data
  (0 (0 1 2) 1 (0 1) 0 1 (0 1 2) ((0 1) 0))
  ;仍旧是嵌套列表,所以sort是非破坏性的
 
 
  7.  矩阵换位
      transpose
 
      矩阵的内容后面还会介绍,这里我们先让我们看看 transpose .
 
 
  (set 'a-list '(("a" 1) ("b" 2) ("c" 3)))
 
  >(transpose a-list)
  (("a" "b" "c") ( 1 2 3))
 
- >(set 'table
- '((A1 B1 C1 D1 E1 F1 G1 H1)
  (A2 B2 C2 D2 E2 F2 G2 H2)
  (A3 B3 C3 D3 E3 F3 G3 H3)))
 
  >(transpose table)
- ((A1 A2 A3)
  (B1 B2 B3)
  (C1 C2 C3)
  (D1 D2 D3)
  (E1 E2 E3)
  (F1 F2 F3)
  (G1 G2 G3)
  (H1 H2 H3))
 
 
      transpose 将 x y 坐标上的数据对换了位置.记住他以第一个子列表的长度为,变换
  后子列表的数量.
 
 
  >(transpose '(("a" "b" "c") (1 2)) )
  (("a" 1) ("b" 2) ("c" nil))
  ;就算第二个列表差了一个 ,他也会用nil 填补上
 
  > (transpose '(("a" "b" ) (1 2 3)) )
  (("a" 1) ("b" 2))
  ;但是第一个列表的数据操作完了,第二个列表还有多余的,那就只能舍弃了
 
 
 
      下面看个比较讨巧的方法:
 
 
  >(set 'table '((A 1) (B 2) (C 3) (D 4) (E 5)))
  ((A 1) (B 2) (C 3) (D 4) (E 5))
 
  >(set 'table (transpose (rotate (transpose table))))
  ((1 A) (2 B) (3 C) (4 D) (5 E))
 
 
      使用map 也能达成相同的目的.
 
 
  (set 'table (map (fn (i) (rotate i)) table))
 
 
  8.  分解列表
      explode
 
      这个函数非常好用,特别是在 nl 里到处是 string 的环境下.
 
 
  >(explode (sequence 1 10))
  ((1) (2) (3) (4) (5) (6) (7) (8) (9) (10))
  ;将一个列表中的元素全部分解成一个个子列表
 
 
  >(explode "china top boxer")
  ("c" "h" "i" "n" "a" " " "t" "o" "p" " " "b" "o" "x" "e" "r")
  ;字符串也能操作,以后你会经常使用到.
 
 
      还可以指定分解的片段的大小:
 
 
  >(explode (sequence 1 10) 2)
  ((1 2) (3 4) (5 6) (7 8) (9 10))
 
  >(explode (sequence 1 10) 3)
  ((1 2 3) (4 5 6) (7 8 9) (10))
 
  >(explode (sequence 1 10) 4)
  ((1 2 3 4) (5 6 7 8) (9 10))
 
 
 
 
 
 
 
  三.  分析列表:   测试和搜索
       List analysis: testing and searching
 
      这一节内容比较多,而且必须在不断的实践中才能真正的掌握好. 所以同学们一定要
  在平时多看别人的代码,多看code pattern 和手册,慢慢积累自己的经验.
 
      nl的列表操作函数和字符串操作函数基本上都可以通用,这也是lisp 不同于别的传统
  lisp的一个优点,强大的字符串处理功能.因为我们再实际应用中更多的是用到字符串.
 
      nl的操作和测试函数都是经过作者苦心优化的,在效率上比各位手动遍历,来的效率高
  很多,所以尽量使用系统内置的函数.
 
      先简单的介绍下,本小节要介绍的函数.
 
      find , match , member , ref , filter , index , count .
 
 
      再次提醒,大部分列表操作函数也同样适用于字符串操作.
 
      length 统计列表元素个数.
 
      starts-with 和 ends-with  测试列表是否以指定的元素开始或结束:
 
 
 
- (for (c (char "a") (char "z"))
      (push (char c) alphabet -1))
 
  >alphabet
- ("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l"
  "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z")
 
  >(starts-with alphabet "a") ; 测试列表是否以 "a" 开头
  true
 
  >(starts-with (join alphabet) "abc") ; join 组合列表成字符串,然后测试
  true
 
  >(ends-with alphabet "z") ; 测试列表是否以 "z" 结束
  true
 
 
 
  1.  查找
      find
 
      find 函数比较常用,你可以用他在列表或者字符串中寻找某个元素第一次出现的位置
  . 如果找到他就返回元素第一次出现的位置(索引从 0 开始),然后忽略剩下的部分,不在
  寻找. 如果没找到则返回nil.
 
 
 
- (set 'sign-of-four
  (parse (read-file "/Users/me/Sherlock-Holmes/sign-of-four.txt") {\W} 0))
  ;这里使用到了正则表达式,将读入的字符串,分解成一个个单词组成的列表.
- (if (find "Moriarty" sign-of-four) ;查找 Moriarty anywhere?
      (println "Moriarty is mentioned")
      (println "No mention of Moriarty"))
 
  ;输出
  No mention of Moriarty
 
- (if (find "Lestrade" sign-of-four)
      (println "Lestrade is mentioned")
      (println "No mention of Lestrade"))
 
  ;输出
  Lestrade is mentioned.
 
  >(find "Watson" sign-of-four)
  477
 
  > (find "人" "中国人万岁" )
  4 ;一个utf8字符占2个位
  > (length "中国人万岁" )
  10
 
 
  >(help find)
  syntax: (find <exp-key> <list> [<func-compare> | <int-regex-option>])
  syntax: (find <str-key> <str-data> [<int-regex-option> [<int-offset>]])
 
 
      第一个语法是对列表的操作,第二个是对字符串的操作.find 可以使用正则表达式,不
  过都是用来处理和字符串相关的情况,就算在列表中.
 
 
  > (find  {(3.*)} '(a b c 3 4 "z133a" ) 0 )
  5
 
 
      友情提示:在处理有关正则表达式的时候,最后用 { } 替代 " " . { } 不用我们再转
  义了,否则你会见到无数个 \ .
 
 
  (set 'loc (find "(tea|cocaine|morphine|tobacco)" sign-of-four 0))
- (if loc
      (println "The drug " (sign-of-four loc) " is mentioned.")
      (println "No trace of drugs"))
  ;输出
  The drug cocaine is mentioned.
 
 
      上面使用正则表达式寻找 4 中麻醉品,找到一个就返回 , 这里实现找到的是可卡因.
  (find "(tea|cocaine|morphine|tobacco)" sign-of-four 0) , 最后的 0 是正则表达式
  标志 ,如不使用则不用设置,  0 是 指大小写相关, 1 表示大小写不相关.更多 PCRE标志
  信息,请查手册.
 
 
 
- (set 'word-list '("being" "believe" "ceiling" "conceit" "conceive"
  "deceive"
  "financier" "foreign" "neither" "receive" "science" "sufficient" "their"
  "vein" "weird"))
 
  >(find {(c)(ie)(?# ie 紧跟在 c 后面 ...)} word-list 0)
  6 ; 第一个找到的单词是 "financier"
 
 
      (?# )是正则表达式的内部注释, 在你将所有人难倒之前,用这个可以挽回很多面子.
  find 调用正则表达式匹配,搜索的列表元素.如果元素是个字符串,而且其中包含了"cie"
  这样的字符组合,则返回整个字符串在列表中的位置.
      find 也可以使用比较函数,后面马上就会说到.
      find 的特性就是找到一个就返回, 他很知足, 但是我们常常要得更多, 这时候可以
  循环调用 find , 然后将找到的字符串元素存储起来,最后返回.
 
 
- (set 'word-list '("scientist" "being" "believe" "ceiling" "conceit"
  "conceive"
  "deceive" "financier" "foreign" "neither" "receive" "science"
  "sufficient" "their" "vein" "weird"))
- (while (set 'temp
      (find {(c)(ie)(?# i before e except after c...)} word-list 0))
      (push (word-list temp) results -1)  ;将找到的字符串元素 存储起来
      (set 'word-list ((+ temp 1) word-list))) ;寻找到一次就重新设置列表
  >results
  ("scientist" "financier" "science" "sufficient")
 
 
      使用 filter 可以更简单:
 
 
  >(filter (fn (w) (find {(c)(ie)} w 0)) word-list)
  ("scientist" "financier" "science" "sufficient")
 
 
      条条大路通罗马:
 
  > (ref-all {(c)(ie)(?# 只是注释)} word-list regex 0)
  ("scientist" "financier" "science" "sufficient")
 
  > (find-all {(c)(ie)} word-list $0 regex)
  ("scientist" "financier" "science" "sufficient")
 
  > (find-all {(c)(ie)} word-list $it regex)
  ("scientist" "financier" "science" "sufficient")
 
 
      count 函数接受2个列表,并统计第一个列表中的元素在第二个列表中出现的次数.
 
 
- >(count '("Sherlock" "Holmes" "Watson" "Lestrade" "Moriarty" "Moran")
      sign-of-four)
  (34 135 24 1 0 0)
 
 
      Holmes 福尔摩斯出现了 135 次 ,他可怜的助理 Watson 沃森 才出现了 24 次.
 
      如果要寻找嵌套列表内的元素,就要用ref了,当然你一可以手工遍历 嵌套列表.不过
  效率会差很多.
 
 
- (set 'maze
-     '((1 2)
        (1 2 3)
        (1 2 3 4)))
  >(find 4 maze)
  nil ;  找不到
  >(ref 4 maze)
  (2 3) ; 返回 4 在 maze 内的位置, 第二个元素内的第三个元素
 
 
 
  2.  member
 
      member 有点类似 find , 不过他会将找到的第一个元素连带其后的所有元素,作为列
  表返回.
 
 
  >(set 's (sequence 1 100 7)) ; 从1 开始 间隔 7 的整数 直到 100
  (1 8 15 22 29 36 43 50 57 64 71 78 85 92 99)
  >(member 78 s))
  (78 85 92 99)
 
 
 
  3.  列表匹配
      matching patterns in lists
 
 
      接下来介绍的这个函数和nl的正则表达式可谓是骑虎相当,他就相当于列表的正则表
  达式: match .
 
  > (help match)
  syntax: (match <list-pattern> <list-match> [<bool>])
 
 
      他有 3 个通配符,  *  ?  + .
      这个三个通配符, 匹配的是元素的个数, 而不是元素的内容, 切记切记!
      ? 代表一个元素, * 代表 0 个或者多于 0 元素, + 代表 1 个或者多于 1 个元素.
 
      下面这个例子将在 10000 个随机数中, 找出匹配 ... 1 2 3 ... 的部分.
 
 
  >(dotimes (c 10000) (push (rand 10) data))
  (7 9 3 8 0 2 4 8 3 ...)
 
 
      我们只要找到 1 2 3 三个数紧密的相邻在一起的部分.前后是什么我们不关心.
 
  >(match '(* 1 2 3 *) data)
  ((7 9 3 8 ... 0 4 5) (7 2 4 1 ... 3 5 5 5))
 
      1 2 3 前后可以什么都没有,也可以有任意的元素.
 
      如果第三个参数 [<bool>] 没有设置,默认返回,通配符部分的内容.这里是前后 2 个
  * 号匹配的所有元素.
 
 
  >(match '(* 1 2 3 *) data true) ;设置成任意 true 值,则将非通配符部分一起返回.
  ((7 9 3 8 ... 0 4 5) 1 2 3 (7 2 4 1 ... 3 5 5 5))
 
 
      和之前的 find 一样, 如果我们要找出所有匹配的部分,就得用循环:
 
 
  (set 'number-list '(2 4 3 4 5 4 3 6 2 3 5 2 2 3 5 3 3 4 2 3 4 2))
- (while (set 'temp-list (match '(* 3 4 *) number-list))
      (set 'number-list (apply append temp-list)))
      ;每次将除 3 4 外的元素重新组合成新的列表 ,相当于每次去除一个 3 4
 
 
  ;输出
  (2 4 5 4 3 6 2 3 5 2 2 3 5 3 2 2)
 
 
 
 
  4.  find-all
 
      find-all 比 find 更强大,因为他可以一次性搜索出所有符合条件元素.
 
  > (help find-all)
  syntax: (find-all <str-regex-pattern> <str-text> [<exp> [<int-regex-option>]])
  syntax: (find-all <list-match-pattern> <list-lists> [<exp>])
  syntax: (find-all <exp-key> <list> <exp> <func-compare>)
 
 
      处理列表的时候,我们一般用第三个语法,只有他可以提供比较函数,而且必须提供.
  第二个语法的 <list-match-pattern> 使用 match 通配符. 第一个语法适用于字符串.
 
 
  (set 'food '("bread" "cheese" "onion" "pickle" "lettuce"))
  >(find-all "onion" food (print $0 { }) >)
  bread cheese lettuce
 
 
      上面这个例子,从 food 列表找出所有大于 "onion" 的字符串(大小依照ASCII顺序).
      使用 < 函数做比较,可以找出所有小于 "onion" 的字符串.
 
  >(find-all "onion" food (print $0 { }) <)
  pickle
 
 
 
  5.  ref 和 ref-all
 
      ref 与 find 比起来的一大特色就是能够搜索嵌套列表, 这个功能使得处理繁杂庞大
  的数据变得简单. 而且你不用担心他的效率.
 
 
  (xml-type-tags nil nil nil nil) ; controls XML parsing
- (set 'itunes-data
-     (xml-parse
          (read-file "/Users/me/Music/iTunes/iTunes Music Library.xml")
              (+ 1 2 4 8 16)))
 
 
      xml-parse 将xml文件分割一个个便于处理的子列表. (+ 1 2 4 8 16) 代表了如下参
  数. 具体细节请爬手册.
 
  option  description
  1       suppress whitespace text tags
  2       suppress empty attribute lists
  4       suppress comment tags
  8       translate string tags into symbols
  16      add SXML (S-expression XML) attribute tags
 
 
  >(ref "Brian Eno" itunes-data)
  (0 2 14 528 6 1)
 
 
      我们从itunes-data 中检索到了 "Baian Eno" 的位置. 他位于 itunes-data 列表的
  第一个 0 个索引的子列表的, 第 2 个索引的子列表的, 第 14 个索引的子列表 ...
      很蛋疼的绕口令,其实就是一层层列表找进去.
 
 
  >(ref-all "Brian Eno" itunes-data) ;ref-all 可以找出所有嵌套列表内符合的元素
  ((0 2 14 528 6 1) (0 2 16 3186 6 1) (0 2 16 3226 6 1))
 
 
      找到后还可以替换: set-ref  set-ref-all .具体后面会将.
 
 
 
 
  6.  过滤列表: filter , clean 和 index
      Filtering lists: filter, clean, and index
 
 
      这三个过滤函数要多用. 用多了你才能信手拈来.
      过滤函数就像淘金, 我们自己用指定的判断函数编制好网, 然后将所有的数据,倒进
  这个网, 处理过后, 留在网里的就是我们需要的东西.
 
      filter 和 index 的语法一样, 前者返回的是符合条件的元素, 而后者返回的是元素
  索引.
 
  >(help filter)
  syntax: (filter <exp-predicate> <exp-list>)
 
  >(help index)
  syntax: (index <exp-predicate> <exp-list>)
 
      其中的<exp-predicate>就是判断函数, 可以是自定义的, 也可以是系统内置的:
 
      NaN? array? atom? context? directory? empty? file? float? global?
      integer? lambda? legal? list? macro? nil? null? number? primitive?
      protected? quote? string? symbol? true? zero?
 
      判断函数的返回值不是 true 就是非 true .
      下面我们使用 integer? 将原列表中的整数过滤出来.
 
 
  (set 'data '(0 1 2 3 4.01 5 6 7 8 9.1 10))
  >(filter integer? data)
  (0 1 2 3 5 6 7 8 10)
 
 
      和filter 相反的就是 clean , 他的作用就是清除符合条件的.
 
 
  (set 'data '(0 1 2 3 4.01 5 6 7 8 9.1 10))
  >(clean integer? data)
  (4.01 9.1)
  ;只剩下浮点数 ,所有的符合 integer? 的都给过滤掉了
 
 
      接下来看个实际应用:
 
 
- (set 'empty-house-text
- (parse
      (read-file "/Users/me/Sherlock-Holmes/the-empty-house.txt") {,\s*|\s+} 0))
 
  (filter (fn (s) (find "pp" s)) empty-house-text)
 
  ;输出结果
- ("suppressed" "supply" "disappearance" "appealed" "appealed"
  "supplemented" "appeared" "opposite" "Apparently" "Suppose"
  "disappear" "happy" "appears" "gripped" "reappearance."
  "gripped" "opposite" "slipped" "disappeared" "slipped"
  "slipped" "unhappy" "appealed" "opportunities." "stopped"
  "stepped" "opposite" "dropped" "appeared" "tapped"
  "approached" "suppressed" "appeared" "snapped" "dropped"
  "stepped" "dropped" "supposition" "opportunity" "appear"
  "happy" "deal-topped" "slipper" "supplied" "appealing"
  "appear")
 
 
      我们再次读取了一篇小说<<the empty house>>, 然后使用 parse 文章中的单词全部
  提取出来,组合成一个列表.
      接着使用自定义的匿名函数 (fn (s) (find "pp" s)) 作为判断条件,将所有包含了
  "pp" 的单词找出来.
 
 
 
- (set 'word-list '("agencies" "being" "believe" "ceiling"
  "conceit" "conceive" "deceive" "financier" "foreign"
  "neither" "receive" "science" "sufficient" "their" "vein"
  "weird"))
 
- (define (i-before-e-after-c? wd) ; 判断函数
      (find {(c)(ie)(?# i before e after c...)} wd 0))
 
  >(index i-before-e-after-c? word-list)
  (0 7 11 12)
  ; agencies, financier, science, sufficient
 
 
      这个例子中我们使用到了全面的 find 函数. index 返回符合条件的列表元素的索引
  .
      除了ref ref-all 大部分的函数都是无法直接操作嵌套列表的.
 
- (set 'maze
-     '((1 2.1)
        (1 2 3)
        (1 2 3 4)))
  >(filter integer? maze)
  () ; filter 过滤不进去 ...
 
  >(filter list? maze)
  ((1 2.1) (1 2 3) (1 2 3 4)) ; 如果判断函数操作的元素在第一层,那就没问题
 
  >(filter integer? (flat maze))
  (1 1 2 3 1 2 3 4) ; 好了现在变成一层了,可以过滤了
 
 
 
  7.  测试列表
      Testing lists
 
      exists 的测试标准是, 只要有一个元素符合要求就返回这个元素, 如果一个都不符
  合返回 nil .
 
 
  >(exists string? '(1 2 3 4 5 6 "hello" 7))
  "hello"
  >(exists string? '(1 2 3 4 5 6 7))
  nil
 
 
      for-all 则要求每一个元素都符合要求, 如果有一个不符合则返回 nil ,否则返回
  true .
 
 
  >(for-all number? '(1 2 3 4 5 6 7))
  true
  >(for-all number? '("zero" 2 3 4 5 6 7))
  nil
 
 
 
 
  8.  搜索列表
      Searching lists
 
      前面介绍的搜索函数 find , ref , ref-all 和 replace , 默认情况下都是使用
  "相等" 作为判断条件. 其实他们都可以使用别的判断函数.
 
 
  >(set 's (sequence 1000 1020)) ;生成一个数字列表
- (1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
  1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020)
 
 
  >(set 'n 1002)
  1002 ; 接下来寻找 s 列表中第一个小于 1002 的数:
 
  >(find n s <)
  3 ; (s 3) 是 1003 , s 里 第一个大于 1002的数
 
  >(find n s (fn (x y) (< x y)))
  3
  ;这里我们使用了匿名函数 , 这个匿名函数接受2个参数, 第一个是我们提供给 find 的
  ;第一个参数 ,在这个例子中是n , find 轮询s列表, 一次抽取一个元素作为 参数 y 传
  ;递给匿名函数. 参数 x y 的顺序是固定的 , 切记!
 
 
      对于比较复杂的比较, 单独写一个判断函数, 是必须的.
 
 
  (set 'a-list '("elephant" "antelope" "giraffe" "dog" "cat" "lion" "shark" ))
- (define (longer? x y)
          (> (length x) (length y)))
 
  >(find "tiger" a-list longer?)
  3 ; "tiger" 比 "dog" 长
 
 
  >(find "tiger" alist (fn (x y) (> (length x) (length y))))
  3
 
 
      longer? 判断 参数x 是否比 参数y 长,长则返回 true , find 轮询 a-list 到
  "dog"的时候, (longer?  "tiger" "dog") 返回真 ,随之 find 返回 "dog" 在 a-list
  中的位置 3.
 
 
  (set 'data '(31 23 -63 53 8 -6 -16 71 -124 29))
 
- (define (my-func x y)
      (and (> x y) (> y 6)))
 
  >(find 15 data my-func) ; 必须小于 15 同时大于 6
  4 ;(data 4) 就是 8 ;
 
 
 
  9.  搜索函数一览
      Summary: compare and contrast
 
 
      来个大杂烩, 有不懂的地方就回头再看一遍, 然后翻翻手册:
 
 
  (set 'data '("this" "is" "a" "list" "of" "strings" "not" "of" "integers"))
 
  >(find "of" data) ; 默认的相等测试
  4 ; 第一个符合条件的 "of" 的位置 ,返回的是数字
 
  >(ref "of" data) ;
  (4) ; 返回的是列表哦
 
  >(ref-all "of" data)
  ((4) (7)) ; 所有的 "of" 的地址
 
  >(filter (fn (x) (= "of" x)) data) ; 留下 "of" 别的过滤掉
  ("of" "of")
 
  >(index (fn (x) (= "of" x)) data) ; 同上,不过返回的是索引
  (4 7)
 
  >(match (* "of" * "of" *) data)
  (("this" "is" "a" "list") ("strings" "not") ("integers"))
 
  >(member "of" data) ; 返回第一个 "of" 以及以后的所有元素
  ("of" "strings" "not" "of" "integers")
 
  >(count (list "of") data) ; 2 个参数都必须是列表
  (2) ; 返回 第一个列表的元素 在第二个列表中出现的次数
 
 
 
 
  四.  选取列表元素
      Selecting items from lists
 
      在nl可以使用显示和隐式两种形式访问列表数据.
      隐式访问,方便快捷,代码量少,显示访问,意义明确,功能更全.
 
      下面的函数都用于显示访问:
 
      first   获得列表的第一个元素
      rest    获得除第一个元素外的所有元素
      last    获得最后一个元素
      nth     获得指定位置的元素
      select  获取指定位置的若干个元素,组装成列表返回
      slice   抽取子列表
 
 
      相比于传统的 car 和 cdr , first 和 rest 功能一样,不过更符合阅读和编写.
 
 
  1.  选取元素: nth , select , 和 slice
      Picking elements: nth , select , and slice
 
      nth 操作简单列表:
 
 
- (set 'phrase '("the" "quick" "brown" "fox" "jumped" "over" "the" "lazy"
  "dog"))
  >(nth 1 phrase)
  "quick"
 
 
      nth 操作嵌套列表:
 
 
- (set 'zoo
- '(("ape" 3)
  ("bat" 47)
  ("lion" 4)))
  >(nth 2 1 zoo) ; 元素2 的 子元素1
  4
 
 
      nth 一次抽取一个 , select 一次可以抽取 N个 :
 
 
- (set 'phrase '("the" "quick" "brown" "fox" "jumped" "over" "the" "lazy"
  "dog"))
  >(select phrase 0 -2 3 4 -4 6 1 -1))
  ("the" "lazy" "fox" "jumped" "over" "the" "quick" "dog")
 
 
      正数表示从列表开始往后数,负数表示从列表末尾开始往前数:
 
 
     0      1       2      3       4      5      6      7     8
  ("the" "quick" "brown" "fox" "jumped" "over" "the" "lazy" "dog")
    -9     -8      -7     -6      -5     -4     -3     -2    -1
 
 
      slelct 也支持列表参数:
 
 
  >(select phrase (rand 9 20)))
- ("jumped" "lazy" "over" "brown" "jumped" "dog" "the" "dog" "dog"
  "quick" "the" "dog" "the" "dog" "the" "brown" "lazy" "lazy" "lazy"
  "quick")
 
 
      如果你需要一组不重复的数据可以用 randomize 函数:
 
 
  (randomize phrase)
 
 
      上面的都是一个个元素抽取, 如果要直接切割出一段子列表, 就要用到 slice :
 
 
  >(slice (explode "schwarzwalderkirschtorte") 7) ;默认取到列表末尾
  ("w" "a" "l" "d" "e" "r" "k" "i" "r" "s" "c" "h" "t" "o" "r" "t" "e")
 
  >(slice (explode "schwarzwalderkirschtorte") 7 6) ;第二个数字代表要抽取的个数
  ("w" "a" "l" "d" "e" "r")
 
 
      如果第二个数字是负数, 则不再代表个数, 而代表位置, 这个位置从末尾开始往前算
  .
 
 
  >(slice (explode "schwarzwalderkirschtorte") 19 -1)
  ("t" "o" "r" "t")
 
 
      上面的 -1 , 代表到距离最后一个元素 1 个位置为止, 切记不包含最后一个元素!
 
 
  >(slice (explode "schwarzwalderkirschtorte") 19 -2)
  ("t" "o" "r")
 
 
 
  五.  隐式访问
      Implicit addressing
 
 
      You will have wing if you would be free !
      世间本无法, 一切皆自然.
 
 
  1.  使用隐式访问获得列表元素
      Select elements using implicit addressing
 
 
  (set 'r '("the" "cat" "sat" "on" "the" "mat"))
  >(r 1) ; 获取第一个元素
  "cat"
  >(nth 1 r) ; 有没发现显示访问的数字参数在列表前, 而隐式访问的数字参数在列表后
  "cat"
  >(r 0)
  "the"
  >(r -1)
  "mat"
 
 
      同样提供多个数字参数, 也可以访问嵌套列表.
 
 
- (set 'zoo
-     '(("ape" 3)
        ("bat" 47)
        ("lion" 4))) ; 三个字列表的嵌套列表
  >(zoo 2 1)
  4
  >(nth 2 1 zoo) ; 效果一样
  4
 
 
      2 1 代表了 zoo 的元素2 ("lion" 4) 的元素1 4. 再次提醒nl的列表由元素0 开始.
 
 
  2.  使用隐式访问获得列表片段
      Selecting a slice using implicit addressing
 
 
      我猜大部分第一次看nl代码的人, 肯定会被数字做函数给搞蒙了:)
 
 
- (set 'alphabet '("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k"
  "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"))
 
  >(13 alphabet) ; 从元素13开始到末尾
  ("n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z")
 
  >(slice alphabet 13) ; slice显示访问的数字参数在列表后,而隐式访问,则在列表前
  ("n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z")
 
  >(3 7 alphabet) ; 从元素3开始,抽取7个元素
  ("d" "e" "f" "g" "h" "i" "j")
 
  >(slice alphabet 3 7) ; 同上
  ("d" "e" "f" "g" "h" "i" "j")
 
 
      让我们回到之前的 iTunes XML 的例子:
 
 
  (xml-type-tags nil nil nil nil)
- (silent
-     (set 'itunes-data
-         (xml-parse
          (read-file "/Users/me/Music/iTunes/iTunes Music Library.xml")
          (+ 1 2 4 8 16)))
  )
 
 
      下面使用隐式访问, 访问 XML 数据:
 
 
  >(set 'eno (ref "Brian Eno" itunes-data))
  (0 2 14 528 6 1) ; Brian Eno 的地址
 
  >(0 4 eno) ; 隐式slice
  (0 2 14 528)
 
  >(itunes-data (0 4 eno))
 
- (dict
      (key "Track ID")
      (int "305")
      (key "Name")
      (string "An Ending (Ascent)")
      (key "Artist")
      (string "Brian Eno") ; this was (0 2 14 528 6 1)
      (key "Album")
      (string "Ambient Journeys")
      (key "Genre")
      (string "ambient, new age, electronica")
      (key "Kind")
      (string "Apple Lossless audio file")
      (key "Size")
      (int "21858166")
  ...
 
 
      记住隐式的nth 和 隐式的slice 的数字参数位置, 刚好和显示的相反.
      还有应该在恰当的地方使用隐式访问. 否则会牺牲代码的可读性
 
 
 
  六.  修改列表
      List surgery
 
 
  1.  缩短列表
      Shortening lists
 
 
      先介绍 2 个常用的列表缩短函数: chop 和 pop . 前者从列表尾部剔除元素. 他是
  非破坏性函数, 后者则是破坏性函数.
 
  > (help chop)
  syntax: (chop <str> [<int-chars>])
  syntax: (chop <list> [<int-elements>])
 
 
      chop 也可以操作字符串. 第三个参数表示剔除的元素数量.
 
 
  (set 'vowels '("a" "e" "i" "o" "u"))
  >(chop vowels)
  ("a" "e" "i" "o")
 
  >(println vowels)
  ("a" "e" "i" "o" "u") ; 原列表未改变
 
  >(chop vowels 3)
  ("a" "e")
 
  >(println vowels)
  ("a" "e" "i" "o" "u") ; 原列表未改变
 
 
      pop 从列表中剔除指定位置的元素.
 
  > (help pop)
  syntax: (pop <list> [<int-index-1> [<int-index-2> ... ]])
  syntax: (pop <list> [<list-indexes>]) ; 元素位置也可以用列表的形式提供
  syntax: (pop <str> [<int-index> [<int-length>]])
 
 
  (set 'vowels '("a" "e" "i" "o" "u"))
  >(pop vowels) ; 默认剔除元素0
  "a"
  >(println vowels)
  ("e" "i" "o" "u")
  >(pop vowels -1)
  "u"
  >(println vowels)
  ("e" "i" "o")
 
 
      replace 也可以按要求剔除符合特定条件的元素.
 
 
  2.  改变列表元素
      Changing items in lists
 
      下面的函数能够快速方便的改变列表元素:
 
       replace 改变或者移除列表元素
       swap 交互列表元素
       set-ref 搜索符合条件的元素并修改他们
       set-ref-all 搜索指定条件的所有元素并修改他们
 
      这些函数和push , pop , reverse , sort , 一样都是破坏性函数,如果不想改变原
  列表, 请搭配 copy 函数一起使用.
 
 
  3.  替换元素: replace
      Replacing information: replace
 
      replace 可以搜索所有符合特定条件的元素, 然后修改他们.
 
 
  > (help replace)
  syntax: (replace <exp-key> <list> <exp-replacement> [<func-compare>])
  syntax: (replace <exp list>)
  syntax: (replace <str-key> <str-data> <exp-replacement>)
  syntax: (replace <str-pattern> <str-data> <exp-replacement><int-regex-option>)
 
 
      修改列表使用前 2 个语法.
 
 
  (set 'data (sequence 1 10))
  >(replace 5 data) ; 没有指定替换的值就会remove 符合条件的 元素
  (1 2 3 4 6 7 8 9 10) ; 5 被剔除了
 
  >(set 'data '(("a" 1) ("b" 2)))
  >(replace ("a" 1) data) ; ("a" 1) 被剔除
  '(("b" 2))
 
      replace 返回修改过的列表:
 
  (set 'data (sequence 1 10))
  >(replace 5 data 0) ; 10 替换成 0
  (1 2 3 4 0 6 7 8 9 10)
  >data
  (1 2 3 4 0 6 7 8 9 10)
 
 
      替换部分可以是值, 也可以是表达式.
 
 
  (set 'data (sequence 1 10))
  >(replace 5 data (sequence 0 5))
  (1 2 3 4 (0 1 2 3 4 5) 6 7 8 9 10)
 
 
      replace 会改变系统变量, $0 $1 $2 到 $15 . $0 代表了符合的元素, 在替换的时
  候, 很好用.
 
 
  >(replace 5 data (list (dup $0 2))) ; $0 holds 5
  (1 2 3 4 ((5 5)) 6 7 8 9 10)
 
 
      后面还会说到系统变量, 更多细节可以搜索手册 System Symbols and Constants :
      默认情况 replace 使用 = 作为判断函数:
 
  (set 'data (sequence 1 10))
  >(replace 5 data 0 =)
  (1 2 3 4 0 6 7 8 9 10)
 
  (set 'data (sequence 1 10))
  >(replace 5 data 0) ; 效果一样 默认使用 =
  (1 2 3 4 0 6 7 8 9 10)
 
 
      下面指定了另一个系统函数 < 作为判断函数:
 
  >(set 'data (randomize (sequence 1 10)))
  (5 10 6 1 7 4 8 3 9 2)
 
  >(replace 5 data 0 <) ; 替换掉所有 5 比之 的数 , 也就是 大于 5 的数
  (5 0 0 1 0 4 0 3 0 2)
 
 
-     由于可以使用任意的比较函数(只要这个函数比较输入 2 个值, 然后返回 true
  或者 false), 加上 系统变量 replace 可以发挥出非常强大的功能.
 
 
- (set 'scores '(
  ("adrian" 234 27 342 23 0)
  ("hermann" 92 0 239 47 134)
  ("neville" 71 2 118 0)
  ("eric" 10 14 58 12 )))
 
 
      如果哪个学生中的分数中 有 0 的, 就把他的所有分数加起来:
 
 
  >(replace '(* 0 *) scores (list (first $0) (apply + (rest $0))) match)
- (("adrian" 626)
  ("hermann" 512)
  ("neville" 191)
  ("eric" 10 14 58 12))
 
 
      这里使用了 match 作为比较函数, 找到有 0 的子列表, 则执行替换操作. $0 代表
  符合条件的子列表. (first $0) 子列表的元素0 , (rest $0) 就是子列表中剩下的的数
  字了. 用 apply 把他们一起加起来. 最后 list 把他们组装成新的列表, 替换原来的子
  列表.
 
 
 
  4.  修改指定元素
      Changing the nth element
 
      在nl的早期版本中 有 nth-set set-nth 等等这样的函数. 不过在现在的版本中,
  (v 10.4.1) 已经消失了. 不过可以使用隐式访问的方式, 改变指定位置的元素.
 
      注意: 从现在开始, 大部分的表达式输出 使用 ;-> 做前缀 .
 
      一方面遵循官方introduction, 另一方面我的笔记本键盘坏了几个键, 外置的不是很
  方便,所以就偷懒了-_-!建议大家下html版本或是用 scite4newLISP观看.
 
 
  (set 'data (sequence 100 110))
  ;-> (100 101 102 103 104 105 106 107 108 109 110)
  (setf  (data 5) 0)
  ;-> 0
  (setf (data 5) "")
  ;-> ""
  data
  ;-> (100 101 102 103 104 "" 106 107 108 109 110)
 
 
 
  5.  修改嵌套列表内的元素
      Modifying lists
 
 
      这里介绍的两个函数, 专门用来修改嵌套列表内的元素: set-ref , set-ref-all .
      当然隐式访问也可以修改嵌套列表, 只要你提供的正确的元素位置.
 
 
  <1>     查找并修改符合条件的元素
          Find and replace matching elements
 
 
  (set 'l '((aaa 100) (bbb 200)))
  ;-> ((aaa 100) (bbb 200))
 
  > (set-ref 200 l 399)  ; 可以再接一个比较函数 比如 < 默认使用 =
  ;-> ((aaa 100) (bbb 399))
 
 
  <2>     查找并修改符合条件
          Find and replace all matching elements: set-ref-all
 
 
          set-ref-all 查找所有符合判断函数的元素, 并修改他们:
 
- (("Mercury"
      (name "Mercury")
      (diameter 0.382)
      (mass 0.06)
      (radius 0.387)
      (period 0.241)
      (incline 7)
      (eccentricity 0.206)
      (rotation 58.6)
      (moons 0))
- ("Venus"
      (name "Venus")
      (diameter 0.949)
      (mass 0.82)
      (radius 0.72)
      (period 0.615)
      (incline 3.39)
      (eccentricity 0.0068)
      (rotation -243)
      (moons 0))
-     ("Earth"
  (name "Earth")
      (diameter 1)
  ...
 
      将所有的 incline 修改成 inclination .
 
 
  (set-ref-all (planets 'incline) 'inclination)
 
 
      使用match 匹配任何以 moon 开始的子列表, 判断子列表的最后一个值, 如果大于 9
  , 则用"lots" 替换这个数值. 如果不是则使用原值.
 
 
  (set-ref-all '(moons ?) planets (if (> (last $0) 9) "lots" (last $0)) match)
 
 
      $0 存储符合条件的原值.
 
 
 
  6.  交换元素
      Swap
 
      swap 可以交换同个(或者不同个)列表内, 任意位置的元素. 字符串也一样.
      swap 还可以交换两个symbol的值.
 
 
  > (help swap)
  syntax: (swap <place-1> <place-2>)
 
 
      语法很简单, 就是 2 个位置 , 应用却千变万化.
 
 
  ;交换不同列表内的元素
  > (setf alst '(a b c) nlst '(1 2 3))
  (1 2 3)
  > (swap (alst 0) (nlst 0))
  a
  > alst
  (1 b c)
  > nlst
  (a 2 3)
 
 
  > (setf xo 333 mo 222)
  222
  > (swap xo mo)
  333
  > xo
  222
  > mo
  333
 
  >(swap (setf str1 "nothing is possible ") (setf str2 "nothing is impossible"))
  "nothing is possible"
  > str1
  "nothing is impossible"
  > str2
  "nothing is possible "
 
 
      下面是个不常见的斐波纳生成函数, 他使用 swap 不断交换 2 个symbol 的值.
 
 
- (define (fibonacci n)
-     (let (current 1 next 0)
-         (dotimes (j n)
          (print current " ")
          (inc 'next current)
          (swap current next))))
 
 
  >(fibonacci 20)
  1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
 
      最常见的方式如下:
 
- (define (fibo:fibo)
      (if (not fibo:mem) (set 'fibo:mem '(0 1)))
      (last (push (+ (fibo:mem -1) (fibo:mem -2)) fibo:mem -1)))
 
      涉及到memoization, 你可以在代码模式手册中搜索:Speed up with memoization
      http://www.newlisp.org/CodePatterns-cn.html
 
 
 
  七. 多列表操作
      Working with two or more lists
 
 
      很多时候我们需要对多个列表就行操作. 比如 "对比哪个列表的元素多?", "哪个元
  素只出现在一个列表中?", "一个元素在另一个列表中出现的频率?" 等等. 这个时候下面
  的函数就可以为我们提供非常大的便利:
 
      count       统计一个列表中的元素在另一个列表中出现的次数
      difference  找出两个列表中不同的元素
      intersect   找出两个列表中相同的元素
      union       找出任意个列表之间共同元素 (v 10.4.0 开始)
 
      union 和 intersect 的差别就是 , 他可以操作两个以上的列表.
 
 
      下面的例子用来统计英文的元音字母在一个句子中出现的频率:
 
 
- (count '("a" "e" "i" "o" "u") (explode "the quick brown fox jumped over
  the lazy dog"))
  ;-> (1 4 1 4 2)
 
 
      (1 4 1 4 2) 意味着句子中友 1 个 a , 4 个 e , 1 个 i , 4 个 o , 和 2 个 u.
 
 
      difference 函数返回在第一个列表中出现过, 却没再第二个列表中出现的元素.
      下面使用 directory 函数获得 2 个不同目录的文件名列表 , 然后用 difference
  获得他们之间的差异.
 
 
  (set 'd1 (directory "/Users/me/Library/Application Support/BBEdit"))
  (set 'd2 (directory "/Users/me/Library/Application Support/TextWrangler"))
  (difference d1 d2)
  ;-> ("AutoSaves" "Glossary" "HTML Templates" "Stationery" "Text Factories")
  (difference d2 d1)
  ;-> ()
 
 
      因为在nl里, difference 和 intersect 都是以第一个列表为参考, 去比较第二个列
  表, 所以第一个列表和第二个列表之间的顺序非常重要, 不能颠倒 , 特别是 difference
  . 从上面的两个结果就能看出来, d1 包含有 d2 没有的文件, 而 d2 不包含有 d1 没有
  的文件.
 
      intersect 返回两个列表共有的元素:
 
 
  (difference d2 d1)
  ;-> ("." ".." ".DS_Store" "Language Modules" "Menu Scripts" "Plug-Ins"
  ;->"Read Me.txt" "Scripts" "Unix Support")
 
 
      如果你希望这两个函数返回的列表的时候, 不要把重复的元素剔除, 可以给他们加上
  第三个参数(任意的 true 值).
 
 
  > (intersect '(a a b c e) '(a 1 2 3) ture)
  (a a)
  > (intersect '(a a b c e) '(a 1 2 3 e) 0)
  (a a e)
  > (intersect  '(a 1 2 3 e) '(a a b c e) 0)
  (a e)
 
 
      下面是个实际应用的例子:
 
  ;先用 parse 将文本内容按行("\r") 划分好
  (set 'd1 (parse (read-file "/Users/me/f1-(2006-05-29)-1.html") "\r" 0))
  (set 'd2 (parse (read-file "/Users/me/f1-(2006-05-29)-6.html") "\r" 0))
  (println (difference d1 d2))
  ;--> (" <p class=\"body\">You could use this function to find" ...)
  ;获得了新版本中删除的内容
 
 
 
 
  八.  关联列表
      Association lists
 
      所谓的关联列表, 看起来就像是嵌套列表, 他的每一个子列表的首个元素, 就是这个
  子列表的键名, 而剩下的元素则是键值. 你也可以把他看成字典, 第一个元素是单词名,
  剩下的元素是单词解释.
 
 
      关联列表的创建很简单. 可以手工:
 
  (set 'ascii-chart '(("a" 97) ("b" 98) ("c" 99) ...))
 
 
      也可以用各种函数:
 
- (for (c (char "a") (char "z"))
      (push (list (char c) c) ascii-chart -1))
 
  ascii-chart
  ;-> (("a" 97) ("b" 98) ("c" 99) ... ("z" 122))
 
      键名不仅仅可以像上面那样用字符串, 也可用数字, symbol. 键值也可以是任意个.
      下面的这个列表, 包含了太阳系内各个的行星数据:
 
- (set 'sol-sys
- '(("Mercury" 0.382 0.06 0.387 0.241 7.00 0.206 58.6 0)
    ("Venus" 0.949 0.82 0.72 0.615 3.39 0.0068 -243 0)
    ("Earth" 1.00 1.00 1.00 1.00 0.00 0.0167 1.00 1)
    ("Mars" 0.53 0.11 1.52 1.88 1.85 0.0934 1.03 2)
    ("Jupiter" 11.2 318 5.20 11.86 1.31 0.0484 0.414 63)
    ("Saturn" 9.41 95 9.54 29.46 2.48 0.0542 0.426 49)
    ("Uranus" 3.98 14.6 19.22 84.01 0.77 0.0472 -0.718 27)
    ("Neptune" 3.81 17.2 30.06 164.8 1.77 0.0086 0.671 13)
    ("Pluto" 0.18 0.002 39.5 248.5 17.1 0.249 -6.5 3)
  )
 
  ; 0: 行星名  1: 赤道直径 (earth) 2: 质量 (earth)
  ; 3: 轨道半径 (AU)   4: 轨道周期 (years)
  ; 5: 轨道倾斜度      6: 轨道离心率
  ; 7: 自转周期 (days) 8: 卫星
  )
 
 
      每一个子列表都以行星名开始, 接着是行星的各种数据. 数据含义已经在注释中写出
  .
 
      除了一般的显示, 隐式访问外, 我们还可以使用nl专门提供的关联列表函数.
 
 
      assoc   通过匹配键名, 找到第一个符合条件的子列表, 并返回.
      lookup  同上, 不过只返回子列表中的一个元素, 默认是最后一个.
 
 
  (assoc "Uranus" sol-sys)
  ;-> ("Uranus" 3.98 14.6 19.22 84.01 0.77 0.0472 -0.718 27)
 
 
      lookup 可以直接挑出特定位置的元素.
 
 
  (lookup "Uranus" sol-sys)
  ;-> 27,     默认取最后一个元素  卫星值
  (lookup "Uranus" sol-sys 2)
  ;-> 14.6,   这里指定获取第二个元素 质量值
  (lookup "Uranus" sol-sys -2)
  ;-> -0.718
  (lookup "Uranus" sol-sys 0)
  ;-> "Uranus" 获得键名也是可以的
 
 
      当然在实际编程的时候, 你不可能像上面那样用数字来指定某种类型的位置, 除非你
  的脑子里放了个字典. 这时候, 就要用常量来指代了:
 
 
  (constant 'orbital-radius 3)
  (constant 'au 149598000) ; 1 au in km
- (println "Neptune's orbital radius is "
      (mul au (lookup "Neptune" sol-sys orbital-radius))" kilometres")
  ;-> Neptune's orbital radius is 4496915880 kilometres
 
 
      au 是天文单位, orbital-radius 指定了轨道半径值在子列表中的位置. constant
  和 set 很像, 不过他定义的symbol是受保护的, 除了使用 constant 改变外, 其他的方
  法都会报错. 这样就可以很好的保护重要的 symbol. 这里说下, nl 内置的函数也是可以
  用constant 改变的.
 
 
  > (setf mo  constant )
  constant@4074F9 ;保存系统的constant 函数
 
  > (constant 'constant add)
  add@411930      ; 给他赋值个新的函数
 
  > (constant 3 4)
  7               ; 现在他的功能是 add
 
  > (mo 'constant mo)
  constant@4074F9 ; 赶紧变回来 别给Boss抓到了 :)
 
 
      下面让我们把所有行星的轨道半径都打印出来:
 
 
- (dolist (planet-data sol-sys) ; 遍历列表
      (set 'planet (first planet-data)) ; 得到行星名
      (set 'orb-rad (lookup planet sol-sys orbital-radius)) ; 得到轨道半径
      (println planet (format "%12.2f %18.0f" orb-rad (mul au orb-rad)))))
      ;最后用 format 格式化半径数据  输出
 
  ;输出结果
  Mercury 0.39 57894426
  Venus 0.72 107710560
  Earth 1.00 149598000
  Mars 1.52 227388960
  Jupiter 5.20 777909600
  Saturn 9.54 1427164920
  Uranus 19.22 2875273560
  Neptune 30.06 4496915880
  Pluto 39.50 5909121000
 
 
      add , sub , mul , div   用来操作浮点数
      +   ,  -  , *   ,  /    用来操作整数
      他们分别表示, 加减乘除.
 
 
 
  1.  替换关联列表的子列表
      Replacing sublists in association lists
 
 
      如果你同时在看官方的introduction 就会在这节看到两个古老的函数. 可惜他们在
  目前的版本中已经彻底作古. 所以我们就只能用别的方法了.
 
  ;这里用最常见的隐式访问 其实我更习惯叫隐式索引
  > (setf lst '((a 2 3 1) (b 4 5 6)))
  ((a 2 3 1) (b 4 5 6))
 
  > (setf (assoc 'a lst ) '(b 4 5 6))
  (b 4 5 6)
  ;(assoc 'a lst ) 就是 子列表的位置, 与地址相关的赋值操作得用setf 或者 setq
  ;不能用set
 
 
 
  2.  添加新元素到关联列表
      Adding new items to association lists
 
 
      关联列表也是列表, 所以操作普通列表的方法也可以用到关联列表上.
 
 
      (push '("Sedna" 0.093 0.00014 .0001 502 11500 0 20 0) sol-sys -1)
 
 
      让我们确认是否添加成功:
 
 
  (assoc "Sedna" sol-sys)
  ;-> ("Sedna" 0.093 0.00014 0.0001 502 11500 0 20 0)
 
 
      下面我们根据每个卫星的质量来排序关联列表, 这里我们自定义一个比较函数:
 
 
  (constant 'mass 2)
  (sort sol-sys (fn (x y) (> (x mass) (y mass))))
  (println sol-sys)
 
  ;输出
  ("Jupiter" 11.2 318 5.2 11.86 1.31 0.0484 0.414 63)
  ("Saturn" 9.41 95 9.54 29.46 2.48 0.0542 0.426 49)
  ("Neptune" 3.81 17.2 30.06 164.8 1.77 0.0086 0.671 13)
  ("Uranus" 3.98 14.6 19.22 84.01 0.77 0.0472 -0.718 27)
  ("Earth" 1 1 1 1 0 0.0167 1 1)
  ("Venus" 0.949 0.82 0.72 0.615 3.39 0.0068 -243 0)
  ("Mars" 0.53 0.11 1.52 1.88 1.85 0.0934 1.03 2)
  ("Mercury" 0.382 0.06 0.387 0.241 7 0.206 58.6 0)
  ("Pluto" 0.18 0.002 39.5 248.5 17.1 0.249 -6.5 3)
 
 
      多个关联列表协同操作也不难:
 
 
  ; 先根据轨道半径将太阳系列表排序好
  (sort sol-sys (fn (x y) (< (x 3) (y 3))))
  ; 为每个行星定义 Unicode 字符
- (set 'unicode-symbols
- '(("Mercury" 0x263F )
  ("Venus" 0x2640 )
  ("Earth" 0x2641 )
  ("Mars" 0x2642 )
  ("Jupiter" 0x2643 )
  ("Saturn" 0x2644 )
  ("Uranus" 0x2645 )
  ("Neptune" 0x2646 )
  ("Pluto" 0x2647)))
- (map
-     (fn (planet)
-         (println (char (lookup (first planet) unicode-symbols))
          "\t"
          (first planet)))
  sol-sys)
 
  ;如果你看的是introduction 就会看到更全的字符
  ' Mercury
  ♀ Venus
  & Earth
  ♂ Mars
  X Jupiter
  Y Saturn
  Z Uranus
  [ Neptune
  \ Pluto
 
      map 作用到整个 sol-sys 列表, 然后将每个行星的子列表传递给, 匿名函数.
  planet 包含的数据就是子列表. 子列表是一个println 打印语句.
  (lookup (first planet) unicode-symbols) 根据传入的行星名(first planet), 从另一
  个关联列表内 unicode-symbols , 寻找相关的unicode 字符. 最后 将找到的 unicode
  数字用 char 转换后输出.
 
 
      要弹出关联列表可以使用 pop-assoc ,可惜现在没有 push-assoc 了.
 
 
  ;; 简单的关联列表
 
  (set 'L '((a 1) (b 2) (c 3)))
  (pop-assoc 'b L) → (b 2)
  L → ((a 1) (c 3))
 
  ;; 嵌套关联列表
 
  (set 'L '((a (b 1) (c (d 2)))))
  (pop-assoc 'a L) → (a (b 1) (c (d 2)))
  L → ()
 
  (set 'L '((a (b 1) (c (d 2)))))
  (pop-assoc '(a b) L)  → (b 1)
  L →  ((a (c (d 2))))
 
  (set 'L '((a (b 1) (c (d 2)))))
  (pop-assoc '(a c) L)  → (c (d 2))
  L → ((a (b 1))))
 
 
  3.  find-all 和 关联列表
      find-all and association lists
 
 
      通过提供通配符, find-all 可以非常方便的找出所有匹配的子列表.
 
 
- (set 'symphonies
-   '((Beethoven 9)
      (Haydn 104)
      (Mozart 41)
      (Mahler 10)
      (Wagner 1)
      (Schumann 4)
      (Shostakovich 15)
      (Bruckner 9)))
 
 
      这里我们要找做以数字 9 结尾的子列表 , '(? 9):
 
 
  (find-all '(? 9) symphonies)
  ;-> ((Beethoven 9) (Bruckner 9))
 
 
      我们再给他加个处理表达式:
 
 
- (find-all '(? 9) symphonies
      (println (first $0) { wrote 9 symphonies.}))
 
  ;输出
  Beethoven wrote 9 symphonies.
  Bruckner wrote 9 symphonies.
 
 
 
 
 
 
 
  2012-04-05 19:50:12 to 2012-04-13 11:53:20
 
  ps:老长了 得歇歇了 断断续续的 键盘也坏了
  不过这一切都是值得的 我不想还有更多人 像我当初那样 一个字一个字的抠 文档
  浪费时间就是浪费生命
  少年们努力吧
  本文档使用 scite for newLISP 编写
  http://code.google.com/p/scite-for-newlisp
 
  彩色版本请到
  http://code.google.com/p/newlisp-you-can-do 下载
 

转载于:https://my.oschina.net/darkcode/blog/60383

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值