[SBCL] CL-USER> (defun map-multidimensional-array (function array) "Map `function` over the contents and indices of `array`. For each element of `array`, `function` will be called with that element and its indices, and so `function` must accept `(1+ (array-rank a))` arguments. `array` can be any number of dimensions. " ;; We keep a few extra data structures around to efficiently compute ;; everything we need: ;; ;; BOUNDS: a vector of the array bounds. ;; INDICES: a list of the indices of the current element, to apply FUNCTION over. ;; INDICES%: a vector of the cons cells of indices, so we can efficiently ;; read/write indices in the middle of the list. (let* ((bounds (coerce (array-dimensions array) 'vector)) (indices (make-list (array-rank array) :initial-element 0)) (indices% (coerce (maplist 'identity indices) 'vector)) (last (1- (array-rank array)))) (macrolet ((iref (index) `(car (aref indices% ,index)))) (labels ((bound (index) (aref bounds index)) (increment-indices (&optional (index last)) ;; After each iteration we increment the last (low-order) index. ;; If that pushes it over the bound we'll reset it to zero and ;; "carry" the increment over to the next index. We're done when ;; we overflow and fall off the left end of the index array. (unless (minusp index) (when (= (incf (iref index)) (bound index)) (setf (iref index) 0) (increment-indices (1- index)))))) (loop :for i :from 0 :below (array-total-size array) :do (progn (apply function (row-major-aref array i) indices) (increment-indices))))))) MAP-MULTIDIMENSIONAL-ARRAY [SBCL] CL-USER> (defun test (arr) (flet ((work (value &rest indices) (format t "~A / ~A / ~S~%" value (apply #'aref arr indices) indices))) (map-multidimensional-array #'work arr))) TEST [SBCL] CL-USER> (test "hello") h / h / (0) e / e / (1) l / l / (2) l / l / (3) o / o / (4) NIL [SBCL] CL-USER> (test "x") x / x / (0) NIL [SBCL] CL-USER> (test "") NIL [SBCL] CL-USER> (test #2a((a b c) (d e f))) A / A / (0 0) B / B / (0 1) C / C / (0 2) D / D / (1 0) E / E / (1 1) F / F / (1 2) NIL [SBCL] CL-USER> (test #4A((((:start 1 2 3) (4 5 6 7) (8 9 10 11)) ((12 13 14 15) (16 17 18 19) (20 21 22 23))) (((1000 1001 1002 1003) (1004 1005 1006 1007) (1008 1009 10010 10011)) ((10012 10013 10014 10015) (10016 10017 10018 10019) (10020 10021 10022 10023))))) START / START / (0 0 0 0) 1 / 1 / (0 0 0 1) 2 / 2 / (0 0 0 2) 3 / 3 / (0 0 0 3) 4 / 4 / (0 0 1 0) 5 / 5 / (0 0 1 1) 6 / 6 / (0 0 1 2) 7 / 7 / (0 0 1 3) 8 / 8 / (0 0 2 0) 9 / 9 / (0 0 2 1) 10 / 10 / (0 0 2 2) 11 / 11 / (0 0 2 3) 12 / 12 / (0 1 0 0) 13 / 13 / (0 1 0 1) 14 / 14 / (0 1 0 2) 15 / 15 / (0 1 0 3) 16 / 16 / (0 1 1 0) 17 / 17 / (0 1 1 1) 18 / 18 / (0 1 1 2) 19 / 19 / (0 1 1 3) 20 / 20 / (0 1 2 0) 21 / 21 / (0 1 2 1) 22 / 22 / (0 1 2 2) 23 / 23 / (0 1 2 3) 1000 / 1000 / (1 0 0 0) 1001 / 1001 / (1 0 0 1) 1002 / 1002 / (1 0 0 2) 1003 / 1003 / (1 0 0 3) 1004 / 1004 / (1 0 1 0) 1005 / 1005 / (1 0 1 1) 1006 / 1006 / (1 0 1 2) 1007 / 1007 / (1 0 1 3) 1008 / 1008 / (1 0 2 0) 1009 / 1009 / (1 0 2 1) 10010 / 10010 / (1 0 2 2) 10011 / 10011 / (1 0 2 3) 10012 / 10012 / (1 1 0 0) 10013 / 10013 / (1 1 0 1) 10014 / 10014 / (1 1 0 2) 10015 / 10015 / (1 1 0 3) 10016 / 10016 / (1 1 1 0) 10017 / 10017 / (1 1 1 1) 10018 / 10018 / (1 1 1 2) 10019 / 10019 / (1 1 1 3) 10020 / 10020 / (1 1 2 0) 10021 / 10021 / (1 1 2 1) 10022 / 10022 / (1 1 2 2) 10023 / 10023 / (1 1 2 3) NIL [SBCL] CL-USER> (test #0acool) COOL / COOL / NIL NIL