I want to iterate over a list, perform an action with the elements and based on some criteria, I want to get rid of the active element. However, when using the function below I end up in an infinite loop.
(defun foo (list action test)
(do ((elt (car list) (car list)))
((null list))
(funcall action elt)
(when (funcall test elt)
(delete elt list))))
(setq list '(1 2 3 4))
(foo list #'pprint #'oddp)
-> infinite loop
Is it not possible as it points to itself? In the end, elt
is (car list)
of course.
Is this a correct assessment? And how could I solve this efficiently?
Actually you can alter the state of your list while iterating over it. You will just have to use rplacd
in addition to delete
, and control the advancement along the list not in the iteration clause, but inside the do body:
(defun nfoo (lst action test)
(do* ((list (cons 1 lst))
(elt (cadr list) (cadr list)))
((null (cdr list))
(if (funcall test (car lst)) (cdr lst) lst))
(funcall action elt)
(if (funcall test elt)
(rplacd list (delete elt (cddr list)))
(setf list (cdr list)))))
You should call it via copy-list
if you don't want it to destroy the argument list.
If you want to remove from your list not all elements equal to elt
that passed the test, but rather all such that will pass the test, then the delete
call will need to be passed the test
function as the :test
argument.
(edit:) and even much simpler and straightforward, like this (non-destructive) version:
(defun foo (list action test)
(do* ((elt (car list) (car list)))
((null list))
(funcall action elt)
(if (funcall test elt)
(setf list (delete elt list))
(setf list (cdr list)))))