Skip to content

LISP - 序列

序列 是 Common Lisp 中一个基本抽象数据类型。它代表了一个元素的有序集合。最常见的两种具体序列类型是列表和向量。大多数序列函数在这两者上都有效,使你的代码灵活且通用。

你可以直接创建序列,也可以使用 make-sequence 创建。访问元素是通过 elt 函数完成的。

;; 列表通过 ' 或 (list ...) 创建
(defvar my-list '(a b c d e))
;; 向量通过 #(...) 创建
(defvar my-vector #(10 20 30 40 50))
;; 以编程方式创建序列
(defvar my-generated-vector (make-sequence 'vector 5 :initial-element 0))
;=> #(0 0 0 0 0)
;; 访问列表的第 3 个元素(0 索引)
(format t "List element at index 2: ~a~%" (elt my-list 2)) ;=> C
;; 访问向量的第 4 个元素
(format t "Vector element at index 3: ~a~%" (elt my-vector 3)) ;=> 40
;; 获取序列的长度
(format t "Length of my-list: ~d~%" (length my-list)) ;=> 5

2. 修改序列:破坏性 vs. 非破坏性

Section titled “2. 修改序列:破坏性 vs. 非破坏性”

Lisp 中的一个关键概念是区分原地修改序列的函数(破坏性)和返回新修改序列的函数(非破坏性)。

  • 非破坏性:更安全,但由于需要分配新内存,效率可能较低。示例:remove、substitute、reverse。
  • 破坏性:更节省内存,但会修改原始数据。通常以 n 为前缀(例如 nreverse),或以 delete 或 fill 等动词命名。谨慎使用!
(let ((data '(1 5 2 5 3 5)))
;; REMOVE(非破坏性)返回一个新列表
(let ((new-data (remove 5 data)))
(format t "Original data after REMOVE: ~a~%" data) ;=> (1 5 2 5 3 5)
(format t "Result of REMOVE: ~a~%" new-data)) ;=> (1 2 3)
(terpri)
;; DELETE(破坏性)修改原始列表结构
(let ((modifiable-data (list 1 5 2 5 3 5)))
(delete 5 modifiable-data)
(format t "Data after DELETE: ~a~%" modifiable-data)) ;=> (1 2 3) (or similar)
(terpri)
;; SUBSTITUTE(非破坏性)
(let ((substituted (substitute 99 5 data :count 1)))
(format t "Result of SUBSTITUTE: ~a~%" substituted)) ;=> (1 99 2 5 3 5)
)

Lisp 擅长将函数作为数据使用。你可以将函数应用于序列以转换或过滤它们,而无需编写显式循环。

(defvar numbers '(1 2 3 4 5 6))
;; MAPCAR 将函数应用于列表的每个元素
(let ((squared (mapcar #'(lambda (x) (* x x)) numbers)))
(format t "Squared numbers: ~a~%" squared)) ;=> (1 4 9 16 25 36)
;; REMOVE-IF-NOT(过滤)保留满足谓词的元素
(let ((even-numbers (remove-if-not #'evenp numbers)))
(format t "Even numbers: ~a~%" even-numbers)) ;=> (2 4 6)
;; COUNT-IF 统计满足谓词的元素
(let ((odd-count (count-if #'oddp numbers)))
(format t "Count of odd numbers: ~d~%" odd-count)) ;=> 3
;; REDUCE 使用二元函数组合元素
(let ((sum (reduce #'+ numbers)))
(format t "Sum of numbers: ~d~%" sum)) ;=> 21

sort 函数原地修改序列,而 merge 将两个已排序的序列合并。

(let ((unsorted-data (list 9 1 8 2 7 3)))
;; SORT 是破坏性的!
(sort unsorted-data #'<)
(format t "Sorted data: ~a~%" unsorted-data)) ;=> (1 2 3 7 8 9)
(let ((list1 '(1 5 10))
(list2 '(2 4 12)))
;; MERGE 是非破坏性的
(let ((merged (merge 'list list1 list2 #'<)))
(format t "Merged list: ~a~%" merged))) ;=> (1 2 4 5 10 12)

这些函数测试序列的元素是否满足条件。它们效率很高,因为一旦结果已知(短路求值),它们就会停止。

(defvar nums '(2 4 6 8))
(defvar mixed-nums '(1 2 3 4))
;; EVERY - 所有元素都是偶数吗?
(format t "All even in ~a? ~a~%" nums (every #'evenp nums)) ;=> T
(format t "All even in ~a? ~a~%" mixed-nums (every #'evenp mixed-nums)) ;=> NIL
;; SOME - 至少有一个元素是奇数吗?
(format t "Some odd in ~a? ~a~%" nums (some #'oddp nums)) ;=> NIL
(format t "Some odd in ~a? ~a~%" mixed-nums (some #'oddp mixed-nums)) ;=> T

许多序列函数接受关键字参数来定制其行为:

ArgumentMeaningDefault
:test一个双参数函数,用于比较元素(例如,用于嵌套列表的 #'equal)。#'eql
:key一个单参数函数,用于在比较前从元素中提取值。#'identity
:start, :end对从起始索引到(但不包括)结束索引的子序列进行操作。0, nil
:from-end如果为真,则从右到左遍历序列。nil
:count限制受影响的元素数量(remove、substitute)。nil