2012-07-01 39 views
7

Tôi đã viết hai phiên bản của vấn đề nqueens và tôi nghĩ rằng họ nên có hiệu quả tương tự nhưng nó không phải như vậy. Tôi nghĩ rằng đó là do hành vi đánh giá lười biếng của Haskell. Có thể ai đó xin vui lòng giải thích cách hoạt động cho các ví dụ sau,Cần giúp đỡ trong việc hiểu hành vi đánh giá lười biếng của Haskell

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) 

Bạn có thể đánh giá chúng bằng cách gọi nqueens1 8 8 hoặc nqueens2 8 8 để đánh giá nó cho một hội đồng quản trị kích thước 8.

Trong khi nqueens2 hoạt động khá hiệu quả nqueens1 có vấn đề hiệu suất. Tôi tin rằng đó là bởi vì các cuộc gọi đệ quy (nqueens n (k-1)) đang được đánh giá nhiều lần. Từ sự hiểu biết của tôi về đánh giá lười biếng Haskells này không phải là trường hợp.

Hãy giúp tôi hiểu rõ hành vi này.

Xin cảm ơn trước.

+1

"đánh giá Lazy" là về đánh giá điều muộn - không phải về tránh đánh giá một cái gì đó nhiều lần. –

+4

@DanielWagner Trên thực tế sự khác biệt giữa đánh giá lười biếng và gọi theo tên là chính xác rằng các biểu thức nhất định sẽ được đánh giá nhiều lần bằng cách sử dụng gọi theo tên chỉ được đánh giá một lần bằng cách sử dụng đánh giá lười biếng. Đó là không liên quan đến vấn đề ở đây mặc dù. – sepp2k

+2

@ sepp2k Bạn nói đúng, tôi nên nói chính xác hơn, hoặc bằng cách nói "gọi theo tên" thay vì "đánh giá lười biếng" hoặc bằng cách nói "ghi nhớ" thay vì "tránh đánh giá điều gì đó nhiều lần". –

Trả lời

10

Có, cuộc gọi đệ quy được đánh giá nhiều lần. Cụ thể, nó được đánh giá một lần cho mỗi giá trị cho i.

Nếu bạn muốn tránh điều này, bạn có thể sắp xếp lại các máy phát điện để các phần q <- đến trước phần i <-:

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

Tuy nhiên điều này sẽ thay đổi thứ tự của các kết quả. Nếu đó là không thể chấp nhận, bạn có thể sử dụng let để tính toán kết quả của các cuộc gọi đệ quy một lần và sau đó sử dụng nó như thế này:

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

Tôi đoán rằng các cuộc gọi đệ quy đã được đánh giá nhiều hơn một lần và đó là lý do cho sự chậm lại trong nqueens1 nhưng thay đổi duy nhất trong nqueens2 là để cung cấp cho một tên để biểu hiện đó. Điều này có thể dễ dàng được thực hiện bởi trình biên dịch Haskell. Tôi muốn biết tại sao trình biên dịch không thể làm được. Cảm ơn bạn. – prasannak

+2

Loại "tối ưu hóa" này giao dịch không gian theo thời gian. Trong khi subterm bây giờ không được đánh giá nhiều lần, nó phải được giữ trong bộ nhớ miễn là nó có thể được yêu cầu một lần nữa. Do đó, GHC cực kỳ cẩn thận và thường không tự động thực hiện loại chuyển đổi này. Nguyên tắc chung là: nếu bạn muốn đảm bảo rằng cụm từ được đánh giá nhiều nhất một lần, thì hãy đặt tên cho nó. – kosmikus

Các vấn đề liên quan