在Common Lisp中实现多维关联数组

nrz*_*nrz 4 lisp associative-array hashtable common-lisp multidimensional-array

我正在寻找在Common Lisp中实现多维关联数组的最佳方法.我正在使用SBCL.

例如在MATLAB中,我可以像这样创建一个struct数组.有一些地理数据:

my_array.Finland.capital = 'Helsinki';
my_array.Finland.northernmost_municipality = 'Utsjoki';
my_array.Norway.capital = 'Oslo';
Run Code Online (Sandbox Code Playgroud)

然后使用动态字段名称引用它:

country = 'Finland';
city_status = 'capital';

my_array.(country).(city_status)
Run Code Online (Sandbox Code Playgroud)

会给出字符串'Helsinki'作为结果.

那么,在Common Lisp中实现多维(n维)关联数组的最佳方法是什么?(如上面MATLAB中的例子)

我找到了一个工作解决方案,它基于使用哈希表将值/键(以字符串形式给出)转换为数字地址,然后将这些数字地址aref作为索引传递给多维数组.但我想知道有更好的方法吗?我需要将字符串转换为其他字符串,将字符串转换为列表,将字符串转换为数字(如下例所示).

我的解决方案就是这个(有一个示例数据可以将用英语写的数字转换成数字表示):

list-to-3d-array下面的函数是Rainer Joswig对Common Lisp的回答的最小修改版本:列表和数组之间的转换.

;;; functions.
(defun list-to-3d-array (my-list)
  (make-array (list (length my-list)
                    (length (first my-list))
                    (length (first (first my-list))))
              :initial-contents my-list))

(defun prepare-hash-table (my-hash-table factor-name factor-values-list)
  "This function stores values from 0 to n for the keys created by concatenating the factor-name and the values given in a list."
  (loop for i from 0 to (1- (length factor-values-list))
        do (setf (gethash (concatenate 'string factor-name "-" (nth i factor-values-list)) my-hash-table) i)))

(defun return-value-from-assoc-array (my-array my-hash-table hundreds tens ones)
  (aref my-array
        (gethash (concatenate 'string "hundreds-" hundreds) my-hash-table)
        (gethash (concatenate 'string "tens-" tens) my-hash-table)
        (gethash (concatenate 'string "ones-" ones) my-hash-table)))
;;; functions end here.

;;; some example data.
(defparameter *array-contents*
  (list 
    (list
      (list 111 112 113)
      (list 121 122 123)
      (list 131 132 133))
    (list
      (list 211 212 213)
      (list 221 222 223)
      (list 231 232 233))
    (list
      (list 311 312 313)
      (list 321 322 323)
      (list 331 332 333))))

(defparameter *hundreds-in-english* (list "hundred" "twohundred" "threehundred"))
(defparameter *tens-in-english* (list "ten" "twenty" "thirty"))
(defparameter *ones-in-english* (list "one" "two" "three"))

(defparameter *my-hash-table* (make-hash-table :test 'equal))
;;; example parameters end here.

;;; prepare the array.
(defparameter *my-array* (list-to-3d-array *array-contents*))

;;;; prepare the hash table.
(prepare-hash-table *my-hash-table* "hundreds" *hundreds-in-english*)
(prepare-hash-table *my-hash-table* "tens" *tens-in-english*)
(prepare-hash-table *my-hash-table* "ones" *ones-in-english*)
Run Code Online (Sandbox Code Playgroud)

然后例如.(return-value-from-assoc-array *my-array* *my-hash-table* "hundred" "ten" "two") 输出:

112
Run Code Online (Sandbox Code Playgroud)

小智 6

我不同意get-nested-hash上面给出的设计和实现.(get-nested-hash 1)不应该返回1,并且(get-nested-hash 1 2)不应该返回nil.不传递两个参数应该是一个错误.应该返回多个值以区分故意的nils,例如gethash.这loop是混乱和繁琐的.不应发出可纠正的错误,check-type因为可能导致数据丢失.

这是一个改进的解决方案:

(defun rec-hash (rec-hash key &rest more-keys)
  (if more-keys
      (apply #'rec-hash (gethash key rec-hash) more-keys)
      (gethash key rec-hash)))

(defun auto-vivify (rec-hash key)
  (multiple-value-bind (child exist) (gethash key rec-hash)
    (if exist
        child
        (setf (gethash key rec-hash)
              (make-hash-table :test (hash-table-test rec-hash))))))

(defun (setf rec-hash) (value rec-hash key &rest more-keys)
  (if more-keys
      (apply #'(setf rec-hash) value (auto-vivify rec-hash key) more-keys)
      (setf (gethash key rec-hash) value)))
Run Code Online (Sandbox Code Playgroud)