问题
The following code generates prime from 1 to n:
(defun prime-list(n)
(let ((a)(b)(x (floor (sqrt n))))
(loop for i from (floor n 6) downto 1 do
(push (1+ (* 6 i)) a)
(push (1- (* 6 i)) a))
(loop while (<= (car a) x) do
(push (car a) b)
(setf a (remove-if #'(lambda(m)(or (= 0 (mod m (car a))) (> m n))) a)))
(append '(2 3) (reverse b) a)))
It seems to me the part
(setf a (remove-if #'XXX a))
can be replaced by
(delete-if #'XXX a)
And I hoped this would make it faster. However when I made that change the function now get into an infinite loop and never returns. Why?
回答1:
As mentioned in the comments, you need to set the variable.
DELETE-IF
is mostly a destructive version of REMOVE-IF
. REMOVE-IF
returns a freshly consed sequence, which does not contain the removed elements. DELETE-IF
may return a sequence which is reused.
If you have a variable, which is bound to a list, you still need to set the result. Above functions return results, but they don't set variables to the result. In case of a list, the result of a DELETE-IF
operation can be the empty list and there is no way the side effect can be, that a variable can be set to it - when it was pointing to a non-empty list.
回答2:
I don't have very much CL experience, but I've done a lot of work in Scheme.
In the second version (sans setf a) the remove-if expression is evaluated, but it does nothing to actually change a. loop is a macro in CL, it just evaluates expressions, but doesnt use the results of those expressions like a recursive function would.
So in the first version, the value of a is changed each time the loop runs due to the setf, but in the second, the value of a is constant throughout. Thus (car a) never changes and the loop never terminates.
we can compare the results of macroexpand on both the loop statements:
without setf:
(MACROLET ((LOOP-FINISH NIL (SYSTEM::LOOP-FINISH-ERROR)))
(BLOCK NIL
(LET NIL
(MACROLET ((LOOP-FINISH NIL '(GO SYSTEM::END-LOOP)))
(TAGBODY SYSTEM::BEGIN-LOOP
(PROGN (UNLESS (< (CAR A) X) (LOOP-FINISH))
(PROGN (PUSH (CAR A) B) (REMOVE-IF #'(LAMBDA (M) (= 0 (MOD M (CAR A)))) A)))
(GO SYSTEM::BEGIN-LOOP) SYSTEM::END-LOOP
(MACROLET
((LOOP-FINISH NIL (SYSTEM::LOOP-FINISH-WARN) '(GO SYSTEM::END-LOOP))))))))) ;
with setf:
(MACROLET ((LOOP-FINISH NIL (SYSTEM::LOOP-FINISH-ERROR)))
(BLOCK NIL
(LET NIL
(MACROLET ((LOOP-FINISH NIL '(GO SYSTEM::END-LOOP)))
(TAGBODY SYSTEM::BEGIN-LOOP
(PROGN (UNLESS (< (CAR A) X) (LOOP-FINISH))
(PROGN (PUSH (CAR A) B)
(SETF A (REMOVE-IF #'(LAMBDA (M) (= 0 (MOD M (CAR A))))) A)))
(GO SYSTEM::BEGIN-LOOP) SYSTEM::END-LOOP
(MACROLET
((LOOP-FINISH NIL (SYSTEM::LOOP-FINISH-WARN) '(GO SYSTEM::END-LOOP))))))))) ;
you can see that in the first loop the remove-if expression is evaluated, but its result isn't used.
回答3:
Chris is correct.
You may speed things up by using delete-if
in place of remove-if
来源:https://stackoverflow.com/questions/11660804/common-lisp-is-delete-if-the-same-as-setf-remove-if