正如其他人指出的那样,您不能使用位移数组来表示矩阵(也许可以使用非标准函数)。但是您需要做的就是改变与原始数组的交互方式。以下是一些可能的选择。
位移数组序列
(defun area (matrix tlx tly brx bry)
(assert (<= tlx tly))
(assert (<= brx bry))
(loop
for y from tly upto bry
collect (make-array (1+ (- brx tlx))
:displaced-to matrix
:displaced-index-offset
(array-row-major-index matrix y tlx))))
(tl
意思是左上角,br
意思是右下角)。
接着,假设你将矩阵定义如下:
(defparameter *matrix* #2A((1 2 3)
(4 5 6)
(7 8 9)))
...子矩阵的获取方法如下:
(area *matrix* 1 1 2 2)
=> (#(5 6) #(8 9))
...并且可以像这样访问:
(aref (nth ROW *) COL)
任何对
*matrix*
的更改都会反映在两个被替换的数组中之一,反之亦然。但是,如果您将结果列表强制转换为
vector
,那么您将获得一个数组向量。这与多维数组不同,但为您提供了行的常数时间访问:
(aref (aref area ROW) COL)
包装器闭包
提供原矩阵受限视图的另一种方法是创建一个访问函数,该函数仅适用于感兴趣的范围:
(defun sub-matrix (matrix tlx tly brx bry)
(assert (<= tlx tly))
(assert (<= brx bry))
(lambda (x y &optional (value nil valuep))
(incf x tlx)
(incf y tly)
(assert (<= tlx x brx))
(assert (<= tly y bry))
(if valuep
(setf (aref matrix y x) value)
(aref matrix y x))))
这将返回一个闭包函数,接受 2 或 3 个参数。前两个参数是相对于内部矩阵的 x
和 y
坐标。当给定第三个参数时,闭包函数会 设置 值。否则,它 获取 值。
这可以进一步泛化。我部分参考了 sds 的答案,但尝试以稍微不同的方式进行处理;这里我可以生成设置器或获取器函数。在创建函数之前和执行已创建的函数期间,我还添加了一些检查:
(defun slice-accessor (array ranges mode)
(let* ((dimensions (array-dimensions array))
(max-length (length dimensions)))
(check-type array array)
(loop
with r = (copy-list ranges)
for range = (pop r)
for (lo hi) = range
for d in dimensions
for x from 0
for $index = (gensym x)
collect $index into $indices
when range
do (assert (<= 0 lo hi d))
and collect `(check-type ,$index (integer 0 ,(- hi lo))) into checks
and collect `(incf ,$index ,lo) into increments
finally (let ((body `(apply #'aref ,array ,@$indices ())))
(return
(compile nil
(ecase mode
(:read `(lambda ,$indices
,@checks
,@increments
,body))
(:write (let (($v (make-symbol "VALUE")))
`(lambda (,$v ,@$indices)
(check-type ,$v ,(array-element-type array))
,@checks
,@increments
(setf ,body ,$v)))))))))))
CLOS
一旦您拥有上述内容,就可以通过对象提供一个漂亮的界面。每当我们更改范围或被切片的数组时,setter和getter函数都会更新:
(defclass array-slice ()
((array :initarg :array :accessor reference-array)
(ranges :initarg :ranges :accessor slice-ranges :initform nil)
(%fast-getter :accessor %fast-getter)
(%fast-setter :accessor %fast-setter)))
(flet ((update-fast-calls (o)
(setf (%fast-setter o)
(slice-accessor (reference-array o) (slice-ranges o) :write)
(%fast-getter o)
(slice-accessor (reference-array o) (slice-ranges o) :read))))
(defmethod initialize-instance :after ((o array-slice) &rest k)
(declare (ignore k))
(update-fast-calls o))
(defmethod (setf reference-array) :after (new-array (o array-slice))
(declare (ignore new-array))
(update-fast-calls o))
(defmethod (setf slice-ranges) :after (new-ranges (o array-slice))
(declare (ignore new-ranges))
(update-fast-calls o)))
(defgeneric slice-aref (slice &rest indices)
(:method ((o array-slice) &rest indices)
(apply (%fast-getter o) indices)))
(defgeneric (setf slice-aref) (new-value slice &rest indices)
(:method (new-value (o array-slice) &rest indices)
(apply (%fast-setter o) new-value indices)))
示例
(defparameter *slice*
(make-instance 'array-slice :array *matrix*))
(slice-aref *slice* 0 0)
=> 1
(setf (slice-ranges *slice*) '((1 2) (1 2)))
(slice-aref *slice* 0 0)
=> 5
(incf (slice-aref *slice* 0 0) 10)
=> 15
*matrix*
=> #2A((1 2 3) (4 15 6) (7 8 9))
(setf (reference-array *slice*) (make-array '(3 3) :initial-element -1))
(slice-aref *slice* 0 0)
=> -1