Question

I'm new to lisp and I'm trying to trade two elements in an array. I would like to know if there is a function to get a specified position so I can use rotatef to swap them.

I've tried the function position but it doesn't work on arrays since it isn't a sequence.

What is the best computational solution in case there isn't a built in function for array?

I've searched around and can't seem to find a simple solution. Is row-major-aref the solution?

Basically, i want to find the position of an element in the 2 dimensional array and return the position to be used in rotatef

Was it helpful?

Solution 2

I think it should work - arrays are indeed sequences.

(let* ((an-array (make-array 6 :initial-contents '(#\f #\o #\o #\b #\a #\r)))
       (f-pos (position #\f an-array))
       (r-pos (position #\r an-array)))
  (rotatef (elt an-array f-pos)
           (elt an-array r-pos))
  an-array)
;=> #(#\r #\o #\o #\b #\a #\f)

Of course, you don't need to bind the positions to names. This will work too:

(let ((an-array (make-array 6 :initial-contents '(#\f #\o #\o #\b #\a #\r))))
  (rotatef (elt an-array (position #\f an-array))
           (elt an-array (position #\r an-array)))
  an-array)
;=> #(#\r #\o #\o #\b #\a #\f)

If the problem is with position not finding the element you need, its :test argument may be helpful. There are also position-if and position-if-not functions which let you supply your own predicate for identifying the element. All three are described here in the HyperSpec.

Here's an example that probably won't work without the :test argument, since the default (which is eql for all sequence functions with :test arguments - see table 11-2 here for a nice summary of the standard sequence function keyword arguments) doesn't work on lists.

(let ((an-array (make-array 3 :initial-contents '((1 2 3)
                                                  (4 5 6)
                                                  (7 8 9)))))
  (rotatef (elt an-array (position '(1 2 3) an-array :test #'equal))
           (elt an-array (position '(7 8 9) an-array :test #'equal)))
  an-array)
;=> ((7 8 9) (4 5 6) (1 2 3))

(Tested on SBCL 1.0.55.0.debian).

Added:

Here's a brute force way to do it with a two-dimensional array. find-position assumes the array is of size (3 3) but it would be easy to make it somewhat more general.

I don't advocate this as the best solution, but I didn't want to leave you empty handed after misunderstanding your question :)

(defvar an-array #2A((1 2 3) (4 5 6) (7 8 9)))

(defun find-position (array item &key (test #'eql))
  (loop for i below 3 do
        (loop for j below 3 do
              (when (funcall test (aref array i j) item)
                (return-from find-position (list i j))))))

(defun swap-4-and-7 (array)
  ;; Example use
  (destructuring-bind (i1 j1) (find-position array 4)
    (destructuring-bind (i2 j2) (find-position array 7)
      (rotatef (aref array i1 j1)
               (aref array i2 j2))))
  array)

(swap-4-and-7 an-array)
;=> #2A((1 2 3) (7 5 6) (4 8 9))

OTHER TIPS

You can make a one-dimensional displaced array and use that as a vector for position.

Example:

CL-USER 9 > (let ((a0 (make-array '(2 3)
                                  :initial-contents '((foo1 bar foo2)
                                                      (foo3 baz foo4)))))
              (let ((a1 (make-array (reduce #'+ (array-dimensions a0))
                                    :displaced-to a0)))
                (let ((pos1 (position 'baz a1))
                      (pos2 (position 'bar a1)))
                  (when (and pos1 pos2)
                    (rotatef (aref a1 pos1)
                             (aref a1 pos2)))))
              a0)
#2A((FOO1 BAZ FOO2) (FOO3 BAR FOO4))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top