The most basic way to copy a list or an array is simply to assign a new variable with SETQ or SETF, but this may behave in unexpected ways. Let us look at an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
; Make a list. (setf mylist (list 1 2 3 4 5)) ; Copy it. (setf mylist-copy mylist) ; Modify the copy (setf (nth 2 mylist-copy) 9) ; As expected, the third element (index 2) is now '9'. (print mylist-copy) ; Prints (1 2 9 4 5) ; but now look at the original list. (print mylist) |
Try running the above in Audacity’s Nyquist Prompt effect (use the Debug button to see the output), and you may be surprised to see that mylist has also been modified, and the final line also prints (1 2 9 4 5).
What has happened is that mylist-copyis a shallow copy of mylist. What that means is that the variable mylist-copy points to the same data object in memory as the original list. Both variables, mylist and mylist-copy are names for the same list object. If either variable is modified, it is the same list object that is modified, so we see the change in both mylist and mylist-copy.
The same thing occurs with arrays, as we can see in this example:
1 2 3 4 5 6 |
(setf myarray (vector 1 2 3 4)) (setf ma-copy myarray) (setf (aref ma-copy 2) 9) (print ma-copy) ;prints #(1 2 9 4) (print myarray) ;prints #(1 2 9 4) |
How to create an independent copy of a list
If we want to create an independent copy of a list, we need to create a new list object with the same contents as the list that we are copying.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
(setf mylist (list 1 2 3 4)) ; Make a new list (setf new-list ()) ; Copy the contents of mylist into the new list (dolist (val mylist) (setf new-list (append new-list (list val)))) ; Modify the new list (setf (nth 2 new-list) 9) ; Print the two lists (print mylist) ;Prints (1 2 3 4) (print new-list) ;Prints (1 2 9 4) |
Duplicating long lists
As you have no doubt noticed, the code uses a Lisp loop to copy the elements of the original list. While this is fine for short lists, it could be quite slow for long lists. Unfortunately Nyquist does not have a built-in function for creating a deep copy of a list, so we need to devise our own way of doing so.
Quick and dirty
This first method is reasonably fast because the heavy lifting is done by the REVERSE function, which is written in C. The reason that it works is because (reverse list) returns a new list. All we do is to reverse the original list, and reverse it back again (if necessary) into the correct order:
1 2 3 4 5 6 7 8 9 10 11 12 |
(setf mylist (list 1 2 3 4)) ; Make a new list (setf new-list (reverse (reverse mylist))) ; Modify the new list (setf (nth 2 new-list) 9) ; Print the two lists (print mylist) ;Prints (1 2 3 4) (print new-list) ;Prints (1 2 9 4) |
This is very much faster on long lists than our original loop method, but it is clearly an ugly hack as we never had any intention to actually reverse the list.
A better solution
In this version we will use MAPCAR (also written in C) to construct a new list. So that we can easily reuse the code when required, we will write our new version as a function that we can call whenever we need a deep copy.
1 2 3 |
(defun copy-list (lst) (mapcar #'(lambda (x) x) lst)) |
MAPCAR applies a function (in this case a lambda expression) to each element of a list, and returns the new list. The LAMBDA expression here simply returns the value passed to it, so the new list is a copy of the original list.
Testing our new list copy function with our original code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
; Make a list. (setf mylist (list 1 2 3 4 5)) ; Our new list copy function. (defun copy-list (lst) (mapcar #'(lambda (x) x) lst)) ; Copy it. (setf mylist-copy (copy-list mylist)) ; Modify the copy (setf (nth 2 mylist-copy) 9) ; Print the results (print mylist) ;Prints (1 2 3 4 5) (print mylist-copy) ;Prints (1 2 9 4 5) |
Writing a suitable function for arrays is an exercise left for the reader. Happy coding.