LISP - 数组
Lisp - 数组操作
Section titled “Lisp - 数组操作”尽管 Lisp 以其列表而闻名,但数组是一种基本且高性能的数据结构,用于存储元素集合。数组将其元素存储在连续的内存块中,允许通过索引非常快速、常数时间地访问任何元素。Common Lisp 支持一维和多维数组。
与列表不同,列表中访问第 N 个元素需要遍历 N-1 个元素,而在数组中访问任何元素所需的时间是相同的。这使得它们非常适合需要随机访问的场景。
创建和访问数组
Section titled “创建和访问数组”创建数组的主要函数是 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: 77Updated scores: #(98 85 80 92 100)示例:一个二维数组(矩阵)
Section titled “示例:一个二维数组(矩阵)”多维数组通过一个维度列表来指定。
;; 创建一个 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): 3The matrix:#2A((1 2 3) (4 5 6))make-array 函数深度解析
Section titled “make-array 函数深度解析”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 | 创建一个与另一个数组共享内容的数组。这允许对数据进行零拷贝“视图”操作。 |
示例:专用数组和位移数组
Section titled “示例:专用数组和位移数组”;; 一个用于性能优化的专用数组。此数组只能容纳整数。(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))。