2011-08-20 28 views
9

Tôi đang làm việc với Project Euler Problem 14. Đây là giải pháp của tôi.Tại sao biểu thức Haskell này lại quá chậm?

import Data.List 

collatzLength :: Int->Int 
collatzLength 1 = 1 
collatzLength n | odd n = 1 + collatzLength (3 * n + 1) 
       | even n = 1 + collatzLength (n `quot` 2) 

maxTuple :: (Int, Int)->(Int, Int)->Ordering 
maxTuple (x1, x2) (y1, y2) | x1 > y1 = GT 
       | x1 < y1 = LT 
       | otherwise = EQ 

Tôi đang chạy sau ra khỏi GHCi

maximumBy maxTuple [(collatzLength x, x) | x <- [1..1000000]] 

Tôi biết rằng nếu Haskell đánh giá nghiêm ngặt, thời gian trên đây sẽ là một cái gì đó như O (n). Kể từ khi Haskell đánh giá lazily mặc dù, nó có vẻ như thế này nên được một số liên tục nhiều n. Điều này đã được chạy gần một giờ rồi. Có vẻ rất không hợp lý. Có ai có bất kỳ ý tưởng tại sao?

+3

Tôi không thấy lý do tại sao sự lười biếng của Haskell nên thực hiện bất kỳ sự khác biệt với sự phức tạp của việc này thuật toán. – sepp2k

+2

Chức năng của bạn 'collatzLength' không phải là đệ quy đuôi. Điều này làm chậm chức năng và gây ra phân bổ không cần thiết. Và 'maxTuple' của bạn giống như' so sánh fst'. – fuz

+0

@sepp Tôi thực sự không biết cách thực thi Danh sách. Nếu họ đang sử dụng Bản đồ, nếu Haskell đánh giá đúng, có vẻ như nó sẽ phải vượt qua danh sách nhiều lần. –

Trả lời

22

Bạn giả sử rằng chức năng collatzLength sẽ bị ghi nhớ. Haskell không tự động ghi nhớ. Bạn sẽ cần phải làm điều đó cho mình. Dưới đây là ví dụ sử dụng gói data-memocombinators.

import Data.List 
import Data.Ord 
import qualified Data.MemoCombinators as Memo 

collatzLength :: Integer -> Integer 
collatzLength = Memo.arrayRange (1,1000000) collatzLength' 
    where 
    collatzLength' 1 = 1 
    collatzLength' n | odd n = 1 + collatzLength (3 * n + 1) 
        | even n = 1 + collatzLength (n `quot` 2) 

main = print $ foldl1' max $ [(collatzLength n, n) | n <- [1..1000000]] 

Điều này chạy trong khoảng 1 giây khi được biên dịch với -O2.

1

Để có thể tìm tối đa danh sách, toàn bộ danh sách cần được đánh giá.

Vì vậy, nó sẽ tính collatzLength từ 1 đến 1000000collatzLength là đệ quy. Điều tồi tệ nhất là, định nghĩa của bạn về collatzLength thậm chí không phải là đệ quy đuôi.

+0

Câu trả lời này bỏ lỡ điểm. Toàn bộ danh sách * không * cần được đánh giá, nhưng bằng cách ghi lại (ghi nhớ) kết quả của 'collatzLength', nó có thể được đẩy nhanh chính xác * bởi vì * nó được định nghĩa đệ quy. Tôi nghĩ đó là sự khôn ngoan của sự khôn ngoan mà vấn đề Euler này được cho là truyền đạt. –

+0

được, nhưng anh ta hỏi về đánh giá lười biếng. Và tôi nói rằng đánh giá lười biếng sẽ không làm cho nó nhanh hơn ở đây bởi vì tất cả mọi thứ cần phải được đánh giá (ít nhất một lần) anyway. Nhưng bạn nói đúng! Vấn đề không phải là đánh giá tất cả mọi thứ một lần, vấn đề là đánh giá mọi thứ _more_ hơn một lần. –

+0

Ah, tôi thấy bạn đang đến từ đâu.Bạn cũng vậy, chính xác: toàn bộ danh sách cần phải được đánh giá, do đó sự lười biếng sẽ không thực sự hữu ích; sự nhầm lẫn của tấm poster dường như đến từ việc không hiểu rõ sự đánh giá lười biếng, giả sử nó bao gồm ma thuật ghi nhớ mà nó không có. –

0

cL là viết tắt của collatzLength

cL!!n đứng cho collatzLength n

cL :: [Int] 
cL = 1 : 1 : [ 1 + (if odd n then cL!!(3*n+1) else cL!!(n `div` 2)) | n <- [2..]] 

đơn giản kiểm tra:

ghci> cL !! 13 
10 
+0

Về cơ bản, điều này sử dụng danh sách ghi nhớ 'collatzLength' khi nó tự xây dựng. Nhưng nó khá là bướng bỉnh khi bạn cố gắng theo dõi thứ tự thực hiện cho 'cL !! 3', phụ thuộc vào 'cL !! 10', phần tử sau trong danh sách sẽ được đánh giá trước đó! –

+0

@ Bur Burton, tệ nhất là nó chạy chậm, có thể gây ra bởi (!!). – wenlong

+0

Có, '!!' thêm một phần * O (n) * thời gian phức tạp vào trung tâm của thuật toán (ngược lại, một hàm ghi nhớ nên là * O (1) * để gọi hoặc * O (log n) * tồi tệ nhất cho việc tra cứu), vì vậy đối với 'n' lớn, bạn chắc chắn sẽ thấy sự sụt giảm lớn. –

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