2011-12-06 30 views
7

Tôi đang trải qua một hướng dẫn Haskell và đang được đoạn mã này để làm với di chuyển một hiệp sĩ trong cờ vua:Haskell Monad Chức năng

import Control.Monad 

type KnightPos = (Int,Int) 

moveKnight :: KnightPos -> [KnightPos] 
moveKnight (c,r) = do 
    (c',r') <- [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1) 
       ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2) 
       ] 
    guard (c' `elem` [1..8] && r' `elem` [1..8]) 
    return (c',r') 

in3 :: KnightPos -> [KnightPos] 
in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight 

canReachIn3 :: KnightPos -> KnightPos -> Bool 
canReachIn3 start end = end `elem` in3 start 

Một tập thể dục là để sửa đổi các chức năng để canReachIn3 cho bạn biết những gì di chuyển bạn có thể thực hiện để đến vị trí cuối cùng nếu có thể đến đó. Hướng dẫn này về cơ bản không có bài tập nên tôi gặp rắc rối với những thứ cơ bản như thế này ... Tôi đã nghĩ đến việc thay đổi giá trị trả về của cả 3 hàm thành [[KnightPos]] trong đó 1 danh sách lớn chứa danh sách mọi thứ có thể có của di chuyển. Điều đó có lẽ sau đó sẽ liên quan đến moveKnight có một tham số [KnightPos] thay vì một KnightPos một, mà sau đó sẽ đánh bại toàn bộ điểm của monads phải không?

Bất kỳ trợ giúp/suy nghĩ nào sẽ được đánh giá cao, cảm ơn.

Trả lời

7

Có thể giúp bạn lùi lại từ khái niệm Monad một chút khi suy nghĩ về mã này, nếu bạn thấy rằng các thao tác danh sách cũ đơn giản hơn là tự nhiên đối với bạn. Vì vậy, bạn có thể viết lại mã ví dụ (với một chút dọn dẹp cho mức độ dễ đọc) như:

type KnightPos = (Int,Int) 

moveKnight :: KnightPos -> [KnightPos] 
moveKnight (c,r) = filter legal candidates where 
    candidates = [(c+2,r-1), (c+2,r+1), (c-2,r-1), (c-2,r+1), 
        (c+1,r-2), (c+1,r+2), (c-1,r-2), (c-1,r+2)] 
    legal (c',r') = c' `elem` [1..8] && r' `elem` [1..8] 

in3 :: KnightPos -> [KnightPos] 
in3 start = concatMap moveKnight (concatMap moveKnight (moveKnight start)) 

canReachIn3 :: KnightPos -> KnightPos -> Bool 
canReachIn3 start end = end `elem` in3 start 

Nước sốt bí mật là concatMap. Như bạn có thể đã biết, nó đồng nghĩa với >>= trong đơn List, nhưng bây giờ có thể hữu ích hơn khi nghĩ về nó như ánh xạ hàm số KnightPos -> [KnightPos] trên danh sách [KnightPos] để tạo danh sách các danh sách [[KnightPos]] và sau đó làm phẳng kết quả trở lại vào một danh sách duy nhất. Được rồi, vì vậy bây giờ chúng tôi đã phân phối với monads cho thời điểm này, chúng ta hãy nhìn lại câu đố ... Giả sử KnightPos ban đầu của bạn là (4,4) và bạn muốn theo dõi tất cả các chuỗi di chuyển có thể có từ vị trí đó.Vì vậy, xác định một loại từ đồng nghĩa:

type Sequence = [KnightPos] 

Sau đó, bạn muốn moveKnight để hoạt động trên những trình tự này, việc tìm kiếm tất cả các di chuyển có thể từ vị trí cuối cùng trong dãy:

moveKnight :: Sequence -> [Sequence] 

Vì vậy, bắt đầu từ một chuỗi [(4,4)] , chúng tôi sẽ có danh sách các chuỗi: [[(4,4), (6,3)], [(4,4), (6,5)], [(4,4), (2,3)], ... ]. Sau đó, tôi nghĩ rằng sự thay đổi duy nhất mà bạn sẽ cần phải thực hiện để in3 là để sửa chữa chữ ký kiểu của nó cho phù hợp:

in3 :: Sequence -> [Sequence] 

Tôi không nghĩ rằng những thay đổi thực hiện thực tế. Cuối cùng, bạn sẽ muốn canReachIn3 để tìm một cái gì đó như:

canReachIn3 :: KnightPos -> KnightPos -> [Sequence] 

Tôi đi các chi tiết thực hiện ra ở đây vào mục đích vì tôi không muốn làm hỏng các câu đố cho bạn hoàn toàn, nhưng tôi hy vọng tôi đã minh họa quan điểm ở đây rằng không có bất cứ điều gì đặc biệt "đặc biệt" về một danh sách, hoặc một danh sách các danh sách, hoặc bất cứ điều gì. Tất cả những gì chúng ta đã thực hiện được thay thế một hàm của kiểu KnightPos -> [KnightPos] với một hàm mới của kiểu Sequence -> [Sequence] - khá nhiều hình dạng giống nhau. Vì vậy, điền vào việc thực hiện của từng chức năng sử dụng bất kỳ phong cách cảm thấy tự nhiên, và sau đó một khi bạn có nó làm việc, quay trở lại phong cách monadic, thay thế concatMap với >>= và như vậy.

+0

Cảm ơn bạn đã cho tôi đủ để tôi tiếp tục tồn tại. –

2

"Điều đó có thể sau đó sẽ liên quan đến moveKnight có thông số [KnightPos] thay vì một số KnightPos một ..." Không, bạn sẽ không muốn làm điều đó. Giữ các chức năng càng cơ bản càng tốt.

"... sau đó sẽ đánh bại toàn bộ điểm của các đơn vị phải không?" Vâng, nếu bạn muốn tất cả các đơn đặt hàng di chuyển, bạn chỉ không sử dụng mọi động thái sử dụng join ngụ ý trong >>=. Bạn có thể xác định hàm trả về danh sách tất cả đường dẫn có độ dài n từ điểm bắt đầu đã cho, nơi chúng tôi có thể triển khai các đường dẫn đó dưới dạng danh sách các vị trí đã qua, vì lý do hiệu quả theo thứ tự ngược lại.

waysFrom :: KnightPos -> Int -> [[KnightPos]] 

đầu tiên cho một di chuyển (hoặc không có)

waysFrom start 0 = [[start]] 
waysFrom start 1 = map (\t -> [t, start]) $ moveKnight start 

và sau đó cho số tùy ý di chuyển, lúc này bạn có thể sử dụng một lần nữa sử dụng >>= tham gia cấu trúc Trie giống như kết quả từ đệ quy đơn giản vào danh sách danh sách di chuyển:

waysFrom start n = waysFrom start (n-1) >>= (\w -> [t:w | t<-moveKnight$head w]) 

có thể có cách tốt hơn để làm điều này, nhưng thực sự không "đánh bại" toàn bộ điểm của các đơn nguyên.

3

tôi sẽ đề nghị làm KnightPos một cấu trúc dữ liệu có khả năng giữ không chỉ là liều thuốc hiện nay, mà còn là KnightPos rằng nó đến từ:

data KnightPos = KnightPos {history :: Maybe KnightPos, position :: (Int,Int) } 

Sau đó, bạn cần phải thực hiện các lớp Eq và Show, và sửa chữa moveKnight để nó trả về cấu trúc này với lịch sử được thiết lập phù hợp:

moveKnight :: KnightPos -> [KnightPos] 
moveKnight [email protected]{position = (c,r)} = do 
    (c',r') <- [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1) 
       ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2) 
       ] 
    guard (c' `elem` [1..8] && r' `elem` [1..8]) 
    return $ KnightPos (Just p) (c',r') 

Đảm bảo bạn hiểu dòng cuối cùng của hàm này.

Chức năng in3 giờ đây sẽ hoạt động mà không cần sửa đổi. Tôi đã viết một hàm mới, pathIn3 :: KnightPos -> KnightPos -> [[KinghtPos]] trả về tất cả các đường dẫn có thể từ start đến end trong 3 câu lệnh đơn điệu.

Spoiler Alert

pathIn3 :: KnightPos -> KnightPos -> [[KnightPos]]
pathIn3 bắt đầu cuối = làm
p < - IN3 bắt đầu
bảo vệ (p == end)
trả lại $ getHistory p

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