Skip to content

LISP - 数组

尽管 Lisp 以其列表而闻名,但数组是一种基本且高性能的数据结构,用于存储元素集合。数组将其元素存储在连续的内存块中,允许通过索引非常快速、常数时间地访问任何元素。Common Lisp 支持一维和多维数组。

与列表不同,列表中访问第 N 个元素需要遍历 N-1 个元素,而在数组中访问任何元素所需的时间是相同的。这使得它们非常适合需要随机访问的场景。

创建数组的主要函数是 make-array。要访问元素,我们使用 aref 函数(array reference 的缩写)。

示例:一个简单的一维数组(向量)

Section titled “示例:一个简单的一维数组(向量)”

一维数组通常被称为向量。让我们创建一个向量来存储分数列表。

;; 定义一个参数来保存一个大小为 5 的向量。
;; 我们可以直接使用 :initial-contents 来初始化它。
(defparameter *scores* (make-array 5 :initial-contents '(98 85 77 92 100)))
;; 访问索引为 2 的元素(第三个元素)。
;; Lisp 数组是 0 索引的。
(format t "第三个分数是: ~a~%" (aref *scores* 2))
;; 修改索引为 2 的元素。
(setf (aref *scores* 2) 80)
;; 打印整个数组。
(format t "更新后的分数: ~a~%" *scores*)
The third score is: 77
Updated scores: #(98 85 80 92 100)

多维数组通过一个维度列表来指定。

;; 创建一个 2x3 矩阵(2 行,3 列)
(defparameter *matrix*
(make-array '(2 3) :initial-contents '((1 2 3)
(4 5 6))))
;; 访问第一行(索引 0)和第三列(索引 2)中的元素
(format t "(0, 2) 处的元素: ~a~%" (aref *matrix* 0 2))
;; 打印矩阵表示
(format t "矩阵:~%~a~%" *matrix*)
Element at (0, 2): 3
The matrix:
#2A((1 2 3) (4 5 6))

make-array 函数功能非常多样。其完整语法包含多个控制数组属性的关键字参数:

(make-array dimensions &key :element-type :initial-element
:initial-contents :adjustable
:fill-pointer :displaced-to
:displaced-index-offset)

以下是最重要的关键字参数:

参数描述
dimensions对于向量,是一个数字;对于多维数组,是一个数字列表。例如,5 或 '(2 3)。
:element-type指定数组可以容纳的元素类型。默认为 T(任何类型)。使用更具体的类型,如 fixnum 或 single-float,可以显著提高性能。
:initial-element用于填充新数组每个单元格的值。不能与 :initial-contents 同时使用。
:initial-contents用于填充数组的嵌套元素序列。其结构必须与 dimensions 匹配。
:adjustable一个布尔值(t 或 nil)。如果为 t,数组的维度可以在以后通过 adjust-array 更改。
:fill-pointer如果设置为 t,会创建一个带有填充指针的向量,使其易于添加或移除元素。适用于动态集合。(参见“可调整数组”章节)。
:displaced-to创建一个与另一个数组共享内容的数组。这允许对数据进行零拷贝“视图”操作。
;; 一个用于性能优化的专用数组。此数组只能容纳整数。
(defparameter *int-vector* (make-array 5 :element-type 'fixnum :initial-element 0))
(format t "整数向量: ~a~%" *int-vector*)
;; 字符串只是字符向量。
(defparameter *char-vector* (make-array 5 :element-type 'character :initial-contents '(#\h #\e #\l #\l #\o)))
(format t "字符向量是字符串: ~a~%" *char-vector*)
(terpri)
;; --- 位移数组示例 ---
(defparameter *source-data* (make-array 10 :initial-contents '(0 1 2 3 4 5 6 7 8 9)))
;; 创建 *source-data* 中间 4 个元素的“视图”
;; 此视图从源的索引 3 开始。
(defparameter *view* (make-array 4 :displaced-to *source-data* :displaced-index-offset 3))
(format t "源数据: ~a~%" *source-data*)
(format t "位移视图: ~a~%" *view*)
;; 修改视图也会修改源数组!
(setf (aref *view* 0) 99)
(format t "修改后的源数据: ~a~%" *source-data*)
Integer vector: #(0 0 0 0 0)
Character vector is a string: "hello"
Source data: #(0 1 2 3 4 5 6 7 8 9)
Displaced view: #(3 4 5 6)
Source data after modification: #(0 1 2 99 4 5 6 7 8 9)
  • 数组 vs. 列表:用于固定大小的集合或需要快速随机访问时使用数组。对于开头频繁改变大小的集合,或主要使用顺序处理的场景,使用列表。
  • 为性能优化而专用:如果数组中的所有元素都属于相同的特定类型(例如 integer、single-float),请使用 :element-type 声明它。Lisp 编译器可以为这些专用数组生成更高效的机器代码。
  • 为简洁性使用 vector:如果您只需要一个简单的一维数组(向量),可以使用 (vector ...) 简写形式:(defparameter *my-vec* (vector 1 2 3)) 等同于 (make-array 3 :initial-contents '(1 2 3))。