17.3 Информация о массиве

Следующие функции извлекают интересную информацию, и это не элементы массива.

[Функция] array-element-type array

array-element-type возвращает спецификатор типа для множества объектов, которые могут быть сохранены в массиве array. Это множество может быть больше чем то, которое запрашивалось в функции make-array. Например, результат

(array-element-type (make-array 5 :element-type ’(mod 5)))

может быть (mod 5), (mod 8), fixnum, t или любой другой тип, для которого (mod 5) является подтипом. Смотрите subtypep.


[Функция] array-rank array

Эта функция возвращает количество измерений (осей) массива array. Результат будет неотрицательным целым. Смотрите array-rank-limit.


[Функция] array-dimension array axis-number

Данная функция возвращает размер измерения axis-number массива array. array может быть любым видом массива, и axis-number должен быть неотрицательным целым меньшим чем ранг массива array. Если array является вектором с указателем заполнения, array-dimension возвращает общий размер вектора, включая неактивные элементы, а не размер ограниченный указателем заполнения. (Функция length будет возвращать размер ограниченный указателем заполнения.)


[Функция] array-dimensions array

array-dimensions возвращает список, элементы которого являются размерами измерений массива array.


[Функция] array-total-size array

array-total-size возвращает общее количество элементов массива array, которое вычислено как произведение размеров всех измерений.

(array-total-size x)
    (apply #’* (array-dimensions x))
    (reduce #’* (array-dimensions x))

Следует отметить, что общий размер нульмерного (FIXME) массива равен 1. Общий размер одномерного массива вычисляется без учёта указателя заполнения.


[Функция] array-in-bounds-p array &rest subscripts

Данный предикат проверяет, являются ли индексы subscripts для массива array корректными. Если они корректны, предикат истинен, иначе ложен. subscripts должен быть целыми числами. Количество индексов subscripts должно равняться рангу массива. Как и aref, array-in-bounds-p игнорирует указатели заполнения.


[Функция] array-row-major-index array &rest subscripts

Данная функция принимает массив и корректные для него индексы и возвращает одиночное неотрицательное целое значение меньшее чем общий размер массива, которое идентифицирует элемент, полагаясь на построчный порядок хранения элементов. Количество указанных индексов subscripts должно равняться рангу массива. Каждый индекс должен быть неотрицательным целым числом меньшим чем соответствующий размер измерения. Как и aref, array-row-major-index игнорирует указатели заполнения.

Возможно определение array-row-major-index, без проверки на ошибки, может выглядеть так:

(defun array-row-major-index (a &rest subscripts)
  (apply #’+ (maplist #’(lambda (x y)
                          (* (car x) (apply #’* (cdr y))))
                      subscripts
                      (array-dimensions a))))

Для одномерного массива, результат array-row-major-index всегда равен переданному индексу.


[Функция] row-major-aref array index

Данная функция позволяет получить доступ к элементу, как если бы массив был одномерный. Аргумент index должен быть неотрицательным целым меньшим чем общий размер массива array. Данная функция индексирует массив, как если бы он был одномерный с построчным порядком. Эту функцию можно понять в терминах aref:

(row-major-aref array index)
  (aref (make-array (array-total-size array))
                    :displaced-to array
                    :element-type (array-element-type array))
        index)

Другими словами, можно обработать массив как одномерный с помощью создания нового одномерного массива, который соединён с исходным, и получить доступ к новому массиву. И наоборот, aref может быть описана в терминах row-major-aref:

(aref array i0 i1 ... in−1)
  (row-major-aref array
                  (array-row-major-index array i0 i1 ... in−1)

Как и aref, row-major-aref полностью игнорирует указатели заполнения. Для изменения элемента массива, можно комбинировать вызов row-major-aref с формой setf.

Эта операция облегчает написание кода, который обрабатывает массивы различных рангов. Предположим, что необходимо обнулить содержимое массива tennis-scores. Можно решить это так:

(fill (make-array (array-total-size tennis-scores)
                  :element-type (array-element-type tennis-scores)
                  :displaced-to tennis-scores)
      0)

К сожалению, так как fill не может принимать многомерные массивы, в данном примере создаётся соединённый массив, что является лишней операцией. Другим способом является отдельная обработка каждого измерения многомерного массива:

(ecase (array-rank tennis-scores)
  (0 (setf (aref tennis-scores) 0))
  (1 (dotimes (i0 (array-dimension tennis-scores 0))
       (setf (aref tennis-scores i0) 0)))
  (2 (dotimes (i0 (array-dimension tennis-scores 0))
       (dotimes (i1 (array-dimension tennis-scores 1))
         (setf (aref tennis-scores i0 i1) 0))))
  ...
  (7 (dotimes (i0 (array-dimension tennis-scores 0))
       (dotimes (i1 (array-dimension tennis-scores 1))
         (dotimes (i2 (array-dimension tennis-scores 1))
           (dotimes (i3 (array-dimension tennis-scores 1))
             (dotimes (i4 (array-dimension tennis-scores 1))
               (dotimes (i5 (array-dimension tennis-scores 1))
                 (dotimes (i6 (array-dimension tennis-scores 1))
                   (setf (aref tennis-scores i0 i1 i2 i3 i4 i5 i6)
                         0)))))))))
  )

От такого кода быстро приходит усталость. Кроме того, данный подход не желателен, так как некоторые реализации Common Lisp’а будут фактически поддерживать не более 7 измерений. Рекурсивно вложенные циклы справляются с задачей лучше, но код всё ещё выглядит как лапша:

(labels
  ((grok-any-rank (&rest indices)
     (let ((d (- (array-rank tennis-scores) (length indices)))
       (if (= d 0)
           (setf (apply #’row-major-aref indices) 0)
           (dotimes (i (array-dimension tennis-scores (- d 1)))
             (apply #’grok-any-rank i indices))))))
  (grok-any-rank))

Является ли этот код эффективным зависит от многих параметров реализации, таких как способ обработки &rest аргументов и компиляции apply вызовов. Только посмотрите как просто использовать для задачи row-major-aref!

(dotimes (i (array-total-size tennis-scores))
  (setf (row-major-aref tennis-scores i) 0))

Нет сомнения, что этот код, слаще любых медовых сот.


[Функция] adjustable-array-p array

Если аргумент, который должен быть массивом, может быть расширен, данный предикат истинен, иначе ложен.

X3J13 voted in June 1989 to clarify that adjustable-array-p is true of an array if and only if adjust-array, when applied to that array, will return the same array, that is, an array eq to the original array. If the :adjustable argument to make-array is non-nil when an array is created, then adjustable-array-p must be true of that array. If an array is created with the :adjustable argument nil (or omitted), then adjustable-array-p may be true or false of that array, depending on the implementation. X3J13 further voted to define the terminology “adjustable array” to mean precisely “an array of which adjustable-array-p is true.” See make-array and adjust-array.


[Функция] array-displacement array

функция возвращает два значение. Первое значение является массивом соединенным с данным, и второе значение обозначает смещение соединения. Если массив не был соединен ни с одним массивом возвращаются значения nil и 0.