2010-12-27 3 views
11

정수 인덱스 (예 : "점")를 사용하여 Clojure 함수를 여러 번 효율적으로 실행하고 기성 순서/목록 (예 : "for")으로 결과를 얻으려는 경우가 자주 있습니다. 분명히"dotimes"와 "for"기능 사이를 교차합니까?

(fortimes [i 10] (* i i)) 

=> (0 1 4 9 16 25 36 49 64 81) 

는 할 수있을 것입니다 :

즉, 나는 이런 식으로 뭔가 할 싶습니다

(for [i (range 10)] (* i i)) 

을하지만 작성 및 임시을 버리고 않도록하고 싶습니다 가능하다면 범위 목록.

Clojure에서 이것을 달성하는 가장 좋은 방법은 무엇입니까?

+0

이 질문에 대한 최신 내용은 무엇입니까? clj-iterate가 최상의 솔루션입니까 아니면 더 좋은 대안이 있습니까? – jcheat

답변

5

나는 매우 효율적으로 반복의이 다른 유형의 작업을 수행 할 수 있습니다 반복 매크로를 작성했습니다. 이 패키지는 github과 clojars 모두에 clj-iterate이라고합니다. 예 :

user> (iter {for i from 0 to 10} {collect (* i i)}) 
(0 1 4 9 16 25 36 49 64 81 100) 

이렇게하면 임시 목록이 생성되지 않습니다.

+0

+1 멋진 작은 도구! 관심의 문제로 임시 목록을 피하는 방법은 무엇입니까? – mikera

6

두 번째 예제에서와 같이 for 루프에서 범위를 생성하는 것은 Clojure에서이 문제를 해결하기위한 관용적 솔루션입니다.

Clojure는 기능 패러다임에 기반하므로 기본적으로 Clojure에서 프로그래밍하면 이와 같은 임시 데이터 구조가 생성됩니다. 그러나 "범위"명령과 "for"명령은 모두 지연 시퀀스로 작동하기 때문에이 코드를 작성해도 전체 임시 범위 데이터 구조가 강제로 메모리에 즉시 강제로 저장되지는 ​​않습니다. 따라서이 예제에서 사용 된 lazy seq의 메모리 오버 헤드가 적절히 사용되면 매우 낮습니다. 또한 예제에 대한 계산 오버 헤드는 겸손하며 범위의 크기에 따라 선형 적으로 증가해야합니다. 이는 일반적인 Clojure 코드에서 허용되는 오버 헤드로 간주됩니다.

이 오버 헤드를 완전히 피하는 적절한 방법은 상황에 따라 임시 범위 목록이 절대적으로 긍정적으로 받아 들여지지 않는 경우 원자 또는 과도 전류를 사용하여 코드를 작성하는 것입니다. http://clojure.org/transients. 그러나 이렇게하면 Clojure 프로그래밍 모델의 장점 중 일부를 약간 더 나은 성능을 대신하여 포기하게됩니다.

3

range 함수로 생성 된 지연 시퀀스를 "생성하고 버리는"이유가 확실하지 않습니다. dotimes에 의해 수행되는 제한적인 반복은 인라인 증분이고 각 단계와 비교하는 것이 더 효율적이지만, 거기에서 직접 목록 연결을 표현하기 위해 추가 비용을 지불 할 수 있습니다.

일반적인 Lisp 솔루션은 이동하면서 작성한 목록에 새 요소를 추가 한 다음 해당 작성된 목록을 파괴적으로 반전하여 반환 값을 산출합니다. 일정 시간에리스트에 추가 할 수있는 다른 기술은 잘 알려져 있지만, 항상 prepend-then-reverse 접근보다 더 효율적이라는 것은 아닙니다.

작동하는 것 같다
(let [r (transient [])] 
    (dotimes [i 10] 
    (conj! r (* i i))) ;; destructive 
    (persistent! r)) 

하지만 the documentation on transients 하나는 "conj!에 사용하지 말아야한다고 경고 :

Clojure에, 당신은 conj! 기능의 파괴적인 행동에 의존, 거기에 도착하는 과도을 사용할 수 있습니다 bash 값은 "—입니다. 즉, 반환 값을 포착하는 대신 파괴적인 동작을 고려해야합니다. 그러므로 그 형식을 다시 작성해야합니다.

위의 r을 다시 호출하여 conj!을 호출 할 때마다 얻게되는 새 값을 다시 지정하려면 atom을 사용하여 더 많은 간접 참조를 도입해야합니다.그 시점에서, 우리는 단지 dotimes과 싸우고 있으며, looprecur을 사용하여 자신의 양식을 작성하는 것이 좋습니다.

반복 할당 된 것과 동일한 크기로 벡터를 미리 할당 할 수 있으면 좋을 것입니다. 그렇게 할 방법이 보이지 않습니다.

+0

여분의 시퀀스를 생성하고 버리는 것을 피하는 주된 이유는 가비지를 최소화하는 것입니다. 예, 가비지 수집이 요즘 정말 저렴하다는 것을 알고 있지만 대기 시간/GC 일시 중지 문제는 일부 애플리케이션에서 진짜 문제입니다. – mikera

3
(defmacro fortimes [[i end] & code] 
    `(let [finish# ~end] 
    (loop [~i 0 results# '()] 
     (if (< ~i finish#) 
     (recur (inc ~i) (cons [email protected] results#)) 
     (reverse results#))))) 

예 :

(fortimes [x 10] (* x x)) 

가 제공은 :

(0 1 4 9 16 25 36 49 64 81) 
+0

감사합니다. John :-)! 그것은 매우 우아한 작은 해결책입니다. 역방향으로하기 전에 여전히 불필요한 임시 목록을 생성하지만, 맞습니까? 그걸 피하는 방법은 없나요? 나는 그것이 부작용으로 혼란 스러울 수는 있지만 역순으로 코드를 실행하는 것이 실제로 의미가있을 것이라고 생각하고 있습니다. – mikera

1

흠, 내가 등록되지 않았기 때문에 귀하의 의견에 답할 수 없습니다. 그러나 clj-iterate는 런타임 라이브러리의 일부이지만 독자를 통해 노출되지 않는 PersistentQueue를 사용합니다.

기본적으로 당신이 마지막에 결합 할 수있는 목록입니다.

관련 문제