Trashtalk
Trashtalk

Reputation: 341

How to swap two rows in a twodimensional array in common lisp nondestructively

I'm trying to swap two rows in a twodimensional array in Common Lisp. I have found a way to this with aref. This a destructive way of doing it and I like to keep it more functional. Has anyone a better idea?

(defun swap-rows (matrix r1 r2)
  "Returns a modified matrix with two swapped rows"
  (loop for i upto (1- (array-dimension matrix 1))
    do (rotatef (aref copy r1 i) (aref copy r2 i))))

I have been looking at making a copy of the original array, but it still changes the original array. This is my second try:

(defun swap-rows (matrix r1 r2)
  "Returns a modified matrix with two swapped rows"
  (let ((copy matrix))
    (loop for i upto (1- (array-dimension matrix 1))
      do (rotatef (aref copy r1 i) (aref copy r2 i))
      finally (return copy))))

There are some other things I've looked at but some ways to copy the array seem somewhat overly complicated. Thanks in advance for any advice.

P.S. I prefer not to use any outside libraries (very sorry to the person who recommended Alexandria).

Upvotes: 0

Views: 210

Answers (2)

Renzo
Renzo

Reputation: 27444

To copy an array in Common Lisp is not particularly simple, and I think this is due to the fact that arrays in this language are data structures particularly suitable for side-effect programming instead of side-effect free (or functional) programming. As @coredump pointed out, if you prefer to use side-effect free programming you should probably use other data structures, like list of lists, or sequences of vectors.

If you want to stick with arrays, here is an alternative way of doing your copy (not very simple or efficient!):

(defun swap-rows (matrix r1 r2)
  "returns a copy of matrix with rows r1  ≤ r2 swapped"
  (let* ((rows (array-dimension matrix 0))
         (cols (array-dimension matrix 1)))
    (flet ((get-rows (from-r to-r)
             "get block of matrix from row from-r to row to-r excluded"
             (loop for i from from-r below to-r
                   collect (loop for j from 0 below cols
                                 collect (aref matrix i j)))))
      (make-array
       (list rows cols)
       :initial-contents
       (append (get-rows 0 r1)
               (get-rows r2 (1+ r2))
               (get-rows (1+ r1) r2)
               (get-rows r1 (1+ r1))
               (get-rows (1+ r2) rows))))))

In practice this convert the original array in blocks of lists, and rebuild a new array starting from those lists.

Upvotes: 2

coredump
coredump

Reputation: 38967

Here below you are not doing a copy, just binding a variable (copy) to an existing value (the one bound to matrix):

(let ((copy matrix))
  ...)

As seen in the other answer, you can use the Alexandria library to copy array without much complexity; for example:

(alexandria:copy-array #2A((1 0 0)
                           (0 1 0)
                           (0 0 1)))

In your case, if you import the symbol, it is sufficent to write:

(let ((copy (copy-array matrix)))
  ...)

If you only ever swap rows without modifying their content, maybe you can define matrices as sequences of vectors. You would share the same rows, but in a different orders (and if you need to change values, then you can copy vectors).

Upvotes: 4

Related Questions