Please read the guide to breadth-search before these notes.

A*search provides a simple, partially optimised implementation of an A*, cost sensitive search mechanism.

breadth-search takes 3 arguments...

· start state

· goal state/fn – either a predicate to take a state determine if it is a goal or a state equal to the goal

· legal move generator

...and 3 (optional) key arguments...

· get-state – removes cost & other information from a state returning only the raw state

· get-cost – removes other information from a state returning only the cost

·
selector – takes list of states &
selects the next one to explore from

· debug – when true, the search prints some information as it runs

– see default operations specified below.

The legal move generator is a function which takes a state representation as its only argument and returns a collection of successor states.

Goal states are (by default) detected using the "=" function. This is often over-simplistic (there may be multiple goal states for example). To deal with cases like this the goal may be specified as a predicate which returns non-false if it is passed a goal state.

This A*search allows a default state structure as follows...

{:state **s**, :cost **c**, ...other keys...}

Where **s** is a raw
state and **c** is a numeric cost value.
If this structure is used get-state and get-cost do not need to be provided nor
does the selector function (assuming that costs are to be minimised).

If other structures are to be used then get-state and get-cost should be provided.

If you require an alternative to the default selector function – please check the comments in the source code.

This example is based on a numbers puzzle where one number can be transformed into another using siimple rules. In this case the rules allow a number to be incremented, have 5 added or be doubled. Each operation incurs a cost.

Number transformations and costs are specified in a legal move generator as follows...

(defn a*lmg [state]

(let [n (:state
state)

c
(:cost state)

]

(list

{:state (+ n 1), :cost (+ c 2)}

{:state (+ n 5), :cost (+ c 7)}

{:state (* n 2), :cost (+ c 1)}

)))

The A*search will find the "cheapest" route from some start state to a goal and, because the state representation is the default, key arguments are not needed.

user=> (A*search {:state 0, :cost 0} (fn [x] (> x 20))
a*lmg)

( {:state 0, :cost 0} {:state 1, :cost 2} {:state 2, :cost 3}

{:state 4, :cost 4} {:state 8, :cost 5} {:state 16, :cost 6}

{:state 32, :cost 7})

Changing the calculation of costs changes the route which the search finds...

(defn a*lmg [state]

(let [n (:state
state)

c
(:cost state)

]

(list

{:state (+ n 1), :cost (+ c 2)}

{:state (+ n 5), :cost (+ c 7)}

{:state (* n 2), :cost (+ c 8)} ;; note change of cost

)))

user=> (A*search {:state 0, :cost 0} (fn [x] (> x 20))
a*lmg)

({:state 0, :cost 0} {:state 1, :cost 2} {:state 6, :cost 9}

{:state 11, :cost 16} {:state 22, :cost 24})