I tried to translate a (working !) solution of the cabbage-goat-wolf puzzle from Scala to Haskell, but the code throws and error when calling head
in findSolutions
because the solution list is empty, so the problem seems to be somewhere in loop. findMoves
seems to work fine.
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)
Of course I would also appreciate hints concerning improvements not related to the actual error.
[Update]
Just for the record, I followed the suggestion to use a Set
. Here is the working code:
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)
The call to head
in findSolution
could be made safer, and a better way to print out the solution should be used, but apart from that I'm quite happy with it.
[Update 2]
I think the former representations of the positions were suboptimal for this kind of problem. I switched to the following data model, which made moving etc slightly more verbose, but much more readable:
data Place = Here | There deriving (Eq, Show)
data Pos = Pos { cabbage :: Place
, goat :: Place
, wolf :: Place
, farmer :: Place
} deriving (Eq, Show)
The problem is that
[Farmer,Goat,Cabbage,Wolf]
is not the same that[Farmer,Cabbage,Goat,Wolf]
and you don't check it when useelem
andnotElem
. One solution is always sort the list of elements, e.g in the functionfindMoves
you can use:Or you can use a set of Item instead a list of Item.