2012-07-01 4 views
7

두 가지 버전의 문제가 생겼습니다. 비슷한 효율성을 가져야한다고 생각합니다. 그렇지만 그렇지 않습니다. 나는 그것이 하스켈의 게으른 평가 행동 때문이라고 생각한다. 그것은 다음과 같은 예를 들어 어떻게 작동하는지 사람이 꽤 작동 nqueens2 동안 크기 8하스켈의 게으른 평가 동작을 이해하는 데 도움이 필요합니다

의 보드를 평가하기 위해 nqueens1 8 8 8 8 nqueens2를 호출하여

nqueens1 n 1 = [[i] | i <- [1..n]] 
nqueens1 n k = [ i:q | i <- [1..n], q <- (nqueens1 n (k - 1)), isSafe i q k] 

isSafe i q n = isSafeHelper i (zip q [(n-1),(n-2)..1]) 
     where isSafeHelper i [] = True 
       isSafeHelper i (x:xs) = (i /= fst x) && abs(i - (fst x)) /= abs(n - (snd x)) && 
             isSafeHelper i xs 
nqueens2 n 1 = [[i] | i <- [1..n]] 
nqueens2 n k = [ i:q | i <- [1..n], q <- boards, isSafe i q k] 
      where boards = nqueens2 n (k-1) 

당신이 그들을 평가할 수 설명해 주시겠습니까 효율적으로 nqueens1 성능 문제가 있습니다. 재귀 호출 (nqueens n (k-1))이 여러 번 평가되기 때문입니다. 하스켈의 게으른 평가에 대한 나의 이해에서 보면, 그렇게해서는 안된다.

이 동작을 이해하는 데 도움을주십시오. 사전에

감사합니다.

+1

"게으른 평가를"늦게 물건을 평가에 관한 것입니다 -하지 평가 뭔가 많은 시간을 피하는. –

+4

@DanielWagner 실제로 게으른 평가와 call-by-name의 차이점은 call-by-name을 사용하여 여러 번 평가되는 특정 표현식은 lazy evaluation을 사용하여 한 번만 평가된다는 것입니다. 그것은 여기의 문제와 관련이 없습니다. – sepp2k

+2

@ sepp2k 네가 맞아, 나는 "게으른 평가"대신 "전화 걸기"라고 말하거나 "여러 번 평가하는 것을 피하는"대신 "메모 작성"이라고 말하면서보다 정확 해졌어야했다. –

답변

10

예, 재귀 호출은 여러 번 평가됩니다. 특히 i에 대한 각 값에 대해 한 번 평가됩니다. 당신이 피하고 싶은 경우 q <- 부분은 i <- 부분 앞에 오는 있도록

, 당신은 발전기를 다시 정렬 할 수 있습니다 :

[ i:q | q <- nqueens2 n (k - 1), i <- [1..n], isSafe i q k] 

그러나이 결과의 순서를 변경합니다. 그 허용 아니라면, 한 번 재귀 호출의 결과를 계산하는 let을 사용하여 다음과 같이 사용할 수 있습니다 :

[ i:q | let rec = nqueens2 n (k - 1), i <- [1..n], q <- rec, isSafe i q k] 
+0

재귀 호출이 두 번 이상 평가되고 nqueens1의 속도가 느려졌을 것으로 추측했지만 nqueens2의 유일한 변경 사항은 해당 식에 이름을 지정하는 것입니다. 이것은 Haskell 컴파일러 자체로 쉽게 할 수있었습니다. 컴파일러가 왜 그것을 할 수 없는지 알고 싶었습니다. 감사합니다. – prasannak

+2

이런 종류의 "최적화"는 시간을위한 공간을 제공합니다. 하위 용어는 여러 번 평가되지 않지만, 다시 필요할 때까지는 기억 속에 보관해야합니다. 따라서 GHC는 매우 조심스럽고 일반적으로 이러한 종류의 변환을 자동으로 수행하지 않습니다. 엄지 손가락의 일반적 규칙은 다음과 같습니다. 용어가 한 번만 평가되도록하려는 경우 이름을 지정하십시오. – kosmikus

관련 문제