I'm trying to write a function that will destructively remove N
elements from a list and return them. The code I came up with (see below) looks fine, except the SETF
is not working the way I intended.
(defun pick (n from)
"Deletes (destructively) n random items from FROM list and returns them"
(loop with removed = nil
for i below (min n (length from)) do
(let ((to-delete (alexandria:random-elt from)))
(setf from (delete to-delete from :count 1 :test #'equal)
removed (nconc removed (list to-delete))))
finally (return removed)))
For most cases, this works just fine:
CL-USER> (defparameter foo (loop for i below 10 collect i))
CL-USER> (pick 3 foo)
(1 3 6)
CL-USER> foo
(0 2 4 5 7 8 9)
CL-USER> (pick 3 foo)
(8 7 0)
CL-USER> foo
(0 2 4 5 9)
As you can see, PICK
works just fine (on SBCL) unless the element being picked happens to be the first on the list. In that case, it doesn't get deleted. That's because the only reassignment happening is the one that goes on inside DELETE
. The SETF
doesn't work properly (i.e. if I use REMOVE
instead, FOO
does not change at all).
Is there any scoping rule going on that I'm not aware of?