davidpomerenke / elm-problem-solving / Problem.Search

These search algorithms will solve our Problem, if it's soluble and not too hard. They all have different advantages and drawbacks.

For an introduction to search algorithms, read through this wonderful interactive visualization.

(I plan to add some precise information about their runtime complexity and space complexity.)

Graph Search

Graph search means that the algorithm assumes that the search space looks like a graph. This means that there might be multiple ways to arrive at a state. The algorithm therefore takes care of this and avoids visiting the same state twice.

Uninformed search

breadthFirst : Problem a -> Model a

depthFirst : Problem a -> Model a

uniformCost : Problem a -> Model a

Dijkstra's algorithm.

Informed search

greedy : Problem a -> Model a

bestFirst : Problem a -> Model a

A* search.

Tree Search

Tree search means that the algorithm assumes that the search space looks like a tree. This means that there will always be just a single way to arrive at a state.

Tree search is usually slightly more efficient than graph search (how much exactly?), but:

treeBreadthFirst : Problem a -> Model a

treeDepthFirst : Problem a -> Model a

treeUniformCost : Problem a -> Model a

treeGreedy : Problem a -> Model a

treeBestFirst : Problem a -> Model a

Performing the search

The functions above only initialize a search model. We can use one of the functions in here to run the search model.


type alias Model a =
{ strategy : Strategy a
, queue : QueuePopper a
, problem : Problem a
, explored : Dict String (Node a)
, frontier : List a
, result : Result (Node a)
, maxPathCost : Basics.Float 
}

This record represents the inner state of the search algorithm. You can integrate it into the model of your web application.

The type parameter a refers to the State type of the search problem. For example, if you want to search a sliding puzzle, you can import it with import Problem.Search.SlidingPuzzle exposing (State).

Initialize your model with searchInit (see below).

next : Model a -> Model a

Performs a single step of the search algorithm. Returns a new search model.

nextN : Basics.Int -> Model a -> Model a

Performs n steps of the search algorithm. Returns a new search model.

solve : Model a -> ( Maybe (Node a), Model a )

Tries to solve the search. Returns a pair of the result (if it can find one), and the search model.

exhaust : Model a -> Model a

Searches through the complete search space without stopping at solutions. Returns a new search model. Note that the complete search space is usually very large.

exhaustBoundary : (Node a -> Basics.Bool) -> Model a -> Model a

Searches through the search space until the boundary condition is no longer fulfilled. Returns a new search model. Could perhaps be used to implement IDA*, but I have not yet attempted that. Experimental.

Inspecting the Result


type Result a
    = Pending
    | Solution a
    | Failure

The current result of any search.

mapResult : (a -> b) -> Result a -> Result b

resultWithDefault : a -> Result a -> a

resultHasState : a -> Result (Node a) -> Basics.Bool

Internal infrastructure

Use these for building you own visualizations. First, check out the ready-made visualizations in the Problem.Search.Visual submodule.


type alias Node a =
{ state : a
, parent : Maybe a
, pathCost : Basics.Float
, children : Maybe (List { pathCost : Basics.Float
, state : a }) 
}

expand : Problem a -> Node a -> { updatedParent : Node a, children : List (Node a) }

path : Model a -> Node a -> List ( Basics.Float, a )

reversePathWithPosition : Model a -> Node a -> List ( Basics.Float, a, ( Basics.Int, Basics.Int ) )

unestrangedChildren : Model a -> Node a -> Maybe (List a)