Here is a puzzle:
U2 has a concert that starts in 17 minutes and they must all cross a bridge to get there. All four men begin on the same side of the bridge. You must help them across to the other side. It is night. There is one flashlight. A maximum of two people can cross at one time. Any party who crosses, either 1 or 2 people, must have the flashlight with them. The flashlight must be walked back and forth, it cannot be thrown, etc. Each band member walks at a different speed. A pair must walk together at the rate of the slower man`s pace:
Bono - 1 minute to cross
Edge - 2 minutes to cross
Adam - 5 minutes to cross
Larry - 10 minutes to cross
For example: If Bono and Larry walk across first, 10 minutes have elapsed when they get to the other side of the bridge. If Larry then returns with the flashlight, a total of 20 minutes have passed and you have failed the mission. Your goal is to get all the men across in 17 mins or less.
My approach was to generate all possible solutions in a tree, and do a fold across the tree to accumulate the results.
Suggestions on style, better approaches, or whatever, are welcome.
import Data.List
import Data.Maybe
import Text.Printf
data Person = Bono | Edge | Adam | Larry
deriving (Eq, Show)
data LastMove = ToLeftSide | ToRightSide
deriving (Show)
data State = State {
leftSide :: [Person],
rightSide :: [Person],
lastMove :: LastMove,
moveSeq :: [[Person]]
} deriving (Show)
data Tree = Node State [Tree]
deriving (Show)
-- List of Persons "here" and "there" relative to the last move.
personsHereThere :: State -> ([Person], [Person])
personsHereThere s@State {lastMove = ToLeftSide} = (leftSide s, rightSide s)
personsHereThere s@State {lastMove = ToRightSide} = (rightSide s, leftSide s)
-- List of Persons on left and right side given Persons "here" and "there".
personsLeftRight :: LastMove -> [Person] -> [Person] -> ([Person], [Person])
personsLeftRight ToLeftSide here there = (here, there)
personsLeftRight ToRightSide here there = (there, here)
-- Swap the direction of movement.
flipDir :: LastMove -> LastMove
flipDir ToLeftSide = ToRightSide
flipDir ToRightSide = ToLeftSide
-- Takes a State and some movers, and returns the new state.
move :: State -> [Person] -> State
move state movers =
let (h, t) = personsHereThere state
h' = h \\ movers
t' = t ++ movers
(left, right) = personsLeftRight (lastMove state) h' t'
dir = flipDir $ lastMove state
moveSeq' = movers : moveSeq state
in
State {
leftSide = left, rightSide = right, lastMove = dir, moveSeq = moveSeq'
}
-- The time taken by the slowest Person in the list.
maxTime :: [Person] -> Int
maxTime s = maximum $ map (fromJust . flip lookup times) s
-- The sum of the times of the given movers.
totalTime :: [[Person]] -> Int
totalTime = foldr ((+) . maxTime) 0
-- List of all possible combinations of walkers.
walkers :: [Person] -> [[Person]]
walkers candidates =
[[x] | [x] <- movers] ++ [[x, y] | [x, y] <- movers]
where
movers = subsequences candidates
-- Is success if no one left on left side.
isSuccess :: State -> Bool
isSuccess s = null $ leftSide s
-- True if we haven't gone overtime.
isContinue :: State -> Bool
isContinue State {moveSeq = ms} = totalTime ms <= 17
-- The solution space.
makeTree :: State -> Tree
makeTree s =
let
(h, _) = personsHereThere s
ww = walkers h
childStates = map (move s) ww
childNodes = map makeTree childStates
in
Node s childNodes
-- Search the solution space for solutions and return them in a list.
-- Each solution is in reverse order (last movers are at the head).
foldTree :: Tree -> [[[Person]]]
foldTree (Node state childNodes)
| not $ isContinue state = []
| isSuccess state = [moveSeq state]
| otherwise = concatMap foldTree childNodes
-- Sequence of movers and the time they took as a string for display.
showResult :: [[Person]] -> String
showResult moveSequence =
let t = totalTime moveSequence
r = intercalate "," $ reverse $ map show moveSequence in
printf "%s : %d minutes" r t
-- Constants.
times = [(Bono,1), (Edge,2), (Adam,5), (Larry,10)]
startList = [Bono, Edge, Adam, Larry]
startState = State {
leftSide = startList, rightSide = [], lastMove = ToLeftSide, moveSeq = []
}
main :: IO ()
main = do
let tree = makeTree startState
let result = foldTree tree
putStrLn $ intercalate "\n" $ map showResult result