Не могу найти ошибку в моем коде на Haskell
Я пытался перевести (работающее!) Решение головоломки "капуста-козел-волк" из Scala в Haskell, но код выдает ошибку при вызове head
в findSolutions
потому что список решений пуст, поэтому проблема, кажется, где-то в цикле. findMoves
Кажется, работает нормально.
import Data.Maybe(fromMaybe)
data Item = Farmer | Cabbage | Goat | Wolf deriving (Eq, Show)
type Position = ([Item], [Item])
validPos :: Position -> Bool
validPos p = valid (fst p) && valid (snd p) where
valid list = elem Farmer list || notElem Goat list ||
(notElem Cabbage list && notElem Wolf list)
findMoves :: Position -> [Position]
findMoves (left,right) = filter validPos moves where
moves | elem Farmer left = map (\item -> (delItem item left, addItem item right)) left
| otherwise = map (\item -> (addItem item left, delItem item right)) right
delItem item = filter (\i -> notElem i [item, Farmer])
addItem Farmer list = Farmer:list
addItem item list = Farmer:item:list
findSolution :: Position -> Position -> [Position]
findSolution from to = head $ loop [[from]] where
loop pps = do
(p:ps) <- pps
let moves = filter (\x -> notElem x (p:ps)) $ findMoves p
if elem to moves then return $ reverse (to:p:ps)
else loop $ map (:p:ps) moves
solve :: [Position]
solve = let all = [Farmer, Cabbage, Goat, Wolf]
in findSolution (all,[]) ([],all)
Конечно, я был бы также признателен за советы, касающиеся улучшений, не связанных с фактической ошибкой.
[Обновить]
Просто для записи, я последовал предложению использовать Set
, Вот рабочий код:
import Data.Set
data Item = Farmer | Cabbage | Goat | Wolf deriving (Eq, Ord, Show)
type Position = (Set Item, Set Item)
validPos :: Position -> Bool
validPos p = valid (fst p) && valid (snd p) where
valid set = or [Farmer `member` set, Goat `notMember` set,
Cabbage `notMember` set && Wolf `notMember` set]
findMoves :: Position -> [Position]
findMoves (left,right) = elems $ Data.Set.filter validPos moves where
moves | Farmer `member` left = Data.Set.map (move delItem addItem) left
| otherwise = Data.Set.map (move addItem delItem) right
move f1 f2 item = (f1 item left, f2 item right)
delItem item = delete Farmer . delete item
addItem item = insert Farmer . insert item
findSolution :: Position -> Position -> [Position]
findSolution from to = head $ loop [[from]] where
loop pps = do
ps <- pps
let moves = Prelude.filter (\x -> notElem x ps) $ findMoves $ head ps
if to `elem` moves then return $ reverse $ to:ps
else loop $ fmap (:ps) moves
solve :: [Position]
solve = let all = fromList [Farmer, Cabbage, Goat, Wolf]
in findSolution (all, empty) (empty, all)
Призыв к head
в findSolution
можно было бы сделать более безопасным и использовать лучший способ распечатать решение, но кроме этого я вполне доволен этим.
[Обновление 2]
Я думаю, что прежние представления позиций были неоптимальными для такого рода проблемы. Я переключился на следующую модель данных, которая сделала перемещение и т.д. немного более многословным, но гораздо более читабельным:
data Place = Here | There deriving (Eq, Show)
data Pos = Pos { cabbage :: Place
, goat :: Place
, wolf :: Place
, farmer :: Place
} deriving (Eq, Show)
1 ответ
Проблема в том, что [Farmer,Goat,Cabbage,Wolf]
это не то же самое, что [Farmer,Cabbage,Goat,Wolf]
и вы не проверяете это при использовании elem
а также notElem
, Одним из решений всегда является сортировка списка элементов, например, в функции findMoves
ты можешь использовать:
import Data.List(ord)
import Control.Arrow((***))
data Item = Farmer | Cabbage | Goat | Wolf deriving (Eq, Show, Ord)
findMoves (left,right) = map (sort***sort) $ filter validPos moves where
-- ....
solve = let all = sort [Farmer, Cabbage, Goat, Wolf]
-- ....
Или вы можете использовать набор Item вместо списка Item.