Examples
VacuumWorld.State
vacuumWorld : Problem VacuumWorld
This is the simplest example problem: The world of a vacuum cleaner.
Let's have a complete look at how it works internally:
There are two locations, A and B, and each of them can be either clean or dirty.
type alias State =
{ location : Location
, a : Condition
, b : Condition
}
type Location
= A
| B
type Condition
= Clean
| Dirty
initialState
At the start, both are dirty:
initialState =
{ location = A
, a = Dirty
, b = Dirty
}
actions
There are three actions: Moving left, moving right, and sucking:
type alias Action =
State -> State
left : Action
left state =
{ state | location = A }
right : Action
right state =
{ state | location = B }
suck : Action
suck state =
case state.location of
A ->
{ state | a = Clean }
B ->
{ state | b = Clean }
We also need to specify a step cost for each action. Let's just set it at 1 for every action. We can add the step cost to all actions like this:
actions =
\state ->
List.map
(\action -> { stepCost = 1, result = action state })
[ left, right, suck ]
heuristic
We could provide a heuristic for each state. This can speed up the search algorithms a lot when we are dealing with complex problems. Our present problem is not very complex, so we just set it at 0 for every state:
heuristic =
\_ -> 0
goalTest
The objective of our vacuum cleaner robot is to clean both locations:
goalTest =
\state -> state.a == Clean && state.b == Clean
SlidingPuzzle.State
slidingPuzzle : SlidingPuzzle -> Problem SlidingPuzzle
Sliding Puzzle
A quadratic grid with n²-1 numbers. They are in a disturbed order and should be brought back to their natural order, by only moving around the tile with number 0. A (4²-1)-puzzle = 15-puzzle looks like this:
A sliding puzzle is represented internally as a simple list. The positions where a new line begins are determined by the square root of the length of the list.
type alias State =
List Int
It could be represented as a list of lists of equal lengths alternatively. (Then it is harder to deal with the equal-length assumption, though.)
simpleEightPuzzle : Problem SlidingPuzzle
simpleEightPuzzle =
slidingPuzzle <|
concat
[ [ 1, 4, 2 ]
, [ 3, 0, 5 ]
, [ 6, 7, 8 ]
]
mediumEightPuzzle : Problem SlidingPuzzle
mediumEightPuzzle =
slidingPuzzle <|
concat
[ [ 1, 4, 2 ]
, [ 3, 5, 8 ]
, [ 0, 6, 7 ]
]
complexEightPuzzle : Problem SlidingPuzzle
Complicated eight puzzle.
complexEightPuzzle =
slidingPuzzle <|
concat
[ [ 7, 2, 4 ]
, [ 5, 0, 6 ]
, [ 8, 3, 1 ]
]
slidingPuzzleVisual : SlidingPuzzle -> Html msg
Queens.State
queens : Basics.Int -> Problem Queens
On an n times n chess board, place 8 queens without any queen attacking another.
type alias State =
List ( Int, Int )
KnuthConjecture.State
knuth : Basics.Float -> Problem Knuth
Knuth conjecture.
This is an artificial problem devised by Donald Knuth. (Donald Knuth is the person who is writing the book with all the algorithms in it, of which, if you understand it completely, you have high chances of getting a nice job.)
The idea is that every number can be represented by starting from 4 and then repeatedly applying one of these three functions:
We want to find the sequence of concatenated functions to arrive at a given number.
Internally, this looks like so:
type alias State =
Float
actions =
\n ->
if toFloat (round n) == n then
[ { stepCost = 1, result = sqrt n }
, { stepCost = 1, result = toFloat (factorial (round n)) }
]
else
[ { stepCost = 1, result = toFloat (floor n) }
, { stepCost = 1, result = sqrt n }
]
simpleKnuth : Problem Knuth
simpleKnuth =
knuth 1
complexKnuth : Problem Knuth
complexKnuth =
knuth 5
In order to solve any problem, we use search techniques that create a graph. But we can also use these search techniques to search not an abstract problem, but a concrete graph.
Dict comparable (List { stepCost : Basics.Float
, result : comparable }
}
A graph is represented as a dictionary for our purposes. The keys are the start nodes, and the values are lists of all the reachable end nodes and the distance between the start and end node.
romania : { distance : Graph String, bucharestDistance : String -> Basics.Float }
Map of cities in Romania. Contains a graph of the distances between all connected cities, and a graph of the straight line distance.
(Unfortunately, we only have data for the straight line distance between Bucharest and the other cities, and not mutually between the other cities; so we call it bucharestDistance
instead of straightLineDistance
.)
routeFinding : (comparable -> String) -> comparable -> comparable -> Graph comparable -> Problem comparable
Find a route of nodes in a graph.
The implementation is very straightforward:
routeFinding nodeToString root goal graph =
{ initialState = root
, actions = \a -> Dict.get a graph |> Maybe.withDefault []
, heuristic = \_ -> 0
, goalTest = (==) goal
, stateToString = nodeToString
}
Heuristics are not yet supported, unfortunately. (The reason is that I am not sure how best to deal with heuristics that are only defined for a subset of nodes, such as romania.bucharestDistance
.)
simpleRouteFinding : Problem String
Find a route from Arad to Bucharest.
simpleRouteFinding =
routeFinding identity "Arad" "Bucharest" romania.distance
MotionPlanning.State
motionPlanning : MotionPlanning.Config -> Problem MotionPlanning
Planning a movement on a rectangular grid. (Such as they occur in old computer games, for example.)
type alias Config =
{ size : Position
, obstacles : List Obstacle
, start : Position
, goal : Position
}
type alias Obstacle =
List Position
type alias Position =
( Int, Int )
On improving grid representations.
simpleMotionPlanning : Problem MotionPlanning
This example is inspired by an animation by user Subh83 on Wikimedia. The animation is licensed under a Creative Commons Attribution 3.0 Unported license. It shows the problem configuration, and how the problem is solved by using A* search.