2012-02-27 4 views
0

목록을 반복하고 요소를 사용하여 작업을 수행하고 일부 기준에 따라 활성 요소를 제거하려고합니다. 그러나, 아래의 함수를 사용하면 무한 루프가됩니다.목록을 반복 할 때 현재 요소를 삭제할 수없는 이유는 무엇입니까?

(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 

그 자체로 가리킬 수 있습니까? 결국 elt(car list)입니다.

올바른 평가입니까? 어떻게 이것을 효율적으로 해결할 수 있을까요?

+0

@quartus에 대한 임의의 참고 사항 : 예상되는 동작 섹션이 포함되어 있으면 신속하게이 질문에 답을 할 것입니다. - 인쇄 할 것으로 예상되는 내용 (및/또는 기타 부작용)은 무엇입니까? 반환 될 것으로 예상됩니다. – lindes

답변

1

실제로 반복하는 동안 목록의 상태를 변경할 수 있습니다. 당신은 delete 이외에 rplacd를 사용할 필요가, 그리고 반복 절에있는 목록을 따라 발전을 제어하지만, 이렇게 몸 안에됩니다 :

(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))))) 

당신은 copy-list를 통해 그것을 호출해야 당신이 원하지 않는 경우 그것은 인수 목록을 파괴합니다.

당신이 시험 테스트를 통과 elt와 같은 모든 요소되지 목록에서 제거하는 것이 아니라 이러한 모든 것을 합격하려면

다음 delete 호출은 :testtest 기능을 전달해야합니다 논의.

(편집 :)이 (비파괴) 버전처럼, 심지어 훨씬 간단하고 간단 : 나는 그래서 아마도 내가 부족, LISP에 새로운 조금 해요

(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))))) 
+0

예제가 작동하는 것을 볼 수 있지만,'(setf list (elf list)를 삭제하십시오')'(setf list (cdr list))와 같이 _feels_는 효과적으로 같은 것을 두 번하고 있습니다. – quartus

+0

의도가 목록을 수정하려는 경우 깨집니다. 변수'list'는 지역 함수 바인딩이고,'setf'는 함수의 내부에만 영향을 미칩니다. 원래리스트가'delete'에 의해 파괴되고 함수가 새로운리스트를 반환하지 않기 때문에, 호출 코드에서이리스트에 대한 어떤 참조도 여전히 semi-random cons 셀을 가리킬 것입니다. – Ramarren

+0

@Ramarren * 첫 번째 버전 *은 의도적 인 경우 작동합니다 (목록의 첫 번째 요소를 제거하는 것만 제외하고 테스트에 합격 한 경우). 그러면 '팝 (pop)'전화가 그걸 할 수 있습니다. 두 번째 버전의 이름을'foo'로 바꿔야했습니다. –

3

루프는 당신이 아무것도 반복되지 않기 때문에, 당신이 반복적으로 action을 적용하지만,이 요소를 변이하지 않는 경우 test 결과는 다음 음수 인 경우, pprint으로 분명 그때는 유지됩니다하지 않는 무한 삭제를 시도해도 목록이 비어 있지는 않습니다.

DELETE은 파괴적인 기능입니다. Common Lisp에서 파괴적인 작업은 에 허용되며, 그들의 인수는입니다. 인수에 대한 참조를 버리고 리턴 값만 사용하도록되어 있습니다. 작업이 완료된 후 인수 상태에 대한 보장은 없습니다. 특히, 구현이 비파괴 형 카운터 파트와 동일하게 작동하도록 허용되기 때문에 아무런 효과가 없지만 일반적으로 시퀀스의 구성 요소 부분은 예측하기 어려운 방식으로 다시 어셈블됩니다. 당신은 또한 당신의 예에서 정의되지 않은 행동을하는 리터럴을 파괴하고 있으므로 피해야합니다.

일반적으로 커먼 리스프의 목록을 불변의 파괴적인 조작으로 처리하는 것이 가장 좋은 방법입니다. 미세 조정은 아무 것도 깨뜨리지 않을 것이라고 확신 할 때만 사용해야합니다. 이 문제의 경우 결과 목록을 조건부 COLLECT으로 어셈블하는 LOOP를 사용하여 목록을 반복 할 수 있습니다. LOOP chapter of PCL을 참조하십시오.

+0

답변을 표시하고 싶습니다. 내 기능의 동작에 대해 설명해주었습니다. 게다가 코드를 게시하기 위해 코드 조각을 단순화하려고 할 때 조금 어지럽 혀서 결국 어쨌든 무한 루프로 끝날 수도 있습니다. – quartus

+0

그리고 정말로 미세 최적화에만 파괴적인 연산을 사용합니까? 그것은 나에게 흥미 롭다. 그래서'(elt lst)'대신에'(setq lst (elt lst) 삭제')를 사용하겠습니까? – quartus

+2

예. (삭제 elt lst) 후에는 예측할 수없는 상태이므로 lst에 대한 기존 참조를 사용할 수 없습니다. 반환 값만 유용하게 사용할 수 있습니다. – Ramarren

0

너의 질문에 뭔가있어. 그래도, 내가 라고 생각하면 나는 당신이 무엇을 요구하고 있는지 이해하고있다. 그리고 내가 왜 이것에 대한 기존의 구조를 사용하지 않는지 궁금하다. 즉, remove-if-not (또는 뒤로 물건을 가지고 있다면 remove-if) 그리고 mapcar ...

(mapcar #'pprint (remove-if-not #'oddp '(1 2 3 4)) 

위의 인쇄 13 (및 (nil nil)를 반환하지만, 아마도 당신은 무시할 수 있습니다 ... 또는 위를 수행하고 (values)로 끝나는 defun는 할 수있다). (만약 당신이 evens를 원한다면 remove-if-notremove-if으로 바꿔라.)

이것은 내가 교육적인 이유로 이것을하고 있거나 뭔가를 놓치지 않는 한 나를 사물에 대해 좀더 합리적인 방법으로 생각 나게한다. 그 중 제가 인정하는 것은 아주 가능합니다. :)

P.S. Hyperspec info on remove-if, remove-if-not, etc.

+1

OP는 테스트를 통과 한 목록의 요소 중 첫 번째 항목뿐만 아니라 테스트에 실패한 모든 항목을 인쇄하려고했습니다. 사용자의 접근 방식은 테스트를 통과 한 요소를 인쇄하지 않습니다. 그리고 우연히도, 당신은 당신의'-if-not'를 뒤로 가져 왔습니다. :) MIT CLHS 링크를 이용해 주셔서 감사합니다. –

+0

@WillNess : 아 글쎄 ... 나는 어쨌든 다른 사람에게 유용 할 수 있도록이 대답을 남겨 둘 것입니다. 흠, 내가 선호하는 행동이 무엇인지 잊어 버린다. HyperSpec 링크에 대한 확실한 사실. 매우 유용한 물건 - 대부분은 그냥 임의의 Google 검색을 통해 얻을 수 있지만. :) – lindes

+0

좋은 질문, 일반적으로 나는 그러한 옵션을 확실히 사용할 것이지만 특정 경우에는 테스트에 따라 실제로 반복되는 동안 목록에서 _several_ 요소를 삭제할 수있는 좀 더 정교한 기능을 개발하게됩니다. 앞에서 언급했듯이 이전 예제는 훨씬 단순화되었습니다. – quartus

관련 문제