astar

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2023 License: MIT Imports: 2 Imported by: 1

README

A* GoDoc Build status

Implementation of the A* Search algorithm.

Install

$ go get github.com/pietv/astar

Overview

A* Search is one of the intelligent exhaustive search algorithms which gets from Start to Finish by exploring successor states.

It's intelligent because it uses a special guidance in selecting the states that are going to be explored next. The algorithm uses a minimum value of a sum of the next successor cost and the heuristic estimate (distance, for example) of how close that next successor is to Finish. These values are named Cost and Estimate in this implementation.

Without any guidance (that is when both Cost and Estimate values are constant), it explores all successor states, making it essentially the Breadth First Search algorithm (Go's container/heap implementation behaves like a queue if keys are equal).

Depending on whether Cost and Estimate are constant or not, A* Search behaves like other well-known algorithms:

Cost Estimate Behavior
const const Breadth First Search
variable const [Dijkstra's Shortest Path] (http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) / Uniform-Cost Search
const variable Greedy Best First Search
variable variable A*

It is not necessary to use a graph data structure. Crawling the internet and feeding harvested links as successors would do or, as another example, the provided maze demo uses a rectangular matrix and uses surrounding cells as successors.

Maze Demo Install the demo:

$ go get github.com/pietv/astar/cmd/maze

Documentation

Overview

Package astar implements the A* (“A Star”) search algorithm as described in the paper by Peter E. Hart et al, “A Formal Basis for the Heuristic Determination of Minimum Cost Paths” http://ai.stanford.edu/~nilsson/OnlinePubs-Nils/PublishedPapers/astar.pdf

The “open” and “closed” sets in this implementation are named “priority queue” and “explored set” respectively.

Time complexity of the algorithm depends on the quality of the heuristic function Estimate().

If Estimate() is constant, the complexity is the same as for the uniform cost search (UCS) algorithm – O(b^m), where b is the branching factor (how many successor states on average) and m is the maximum depth of the decision tree.

If Estimate() is optimal, the complexity is O(n).

The algorithm is implemented as a Search() function which takes astar.Interface as a parameter.

Basic usage (counting to 10):

type Number int

func (n Number) Start() interface{}             { return Number(1) }
func (n Number) Finish() bool                   { return n == Number(10) }
func (n *Number) Move(x interface{})            { *n = x.(Number) }
func (n Number) Successors() []interface{}      { return []interface{}{n - 1, n + 1} }
func (n Number) Cost(x interface{}) float64     { return 1 }
func (n Number) Estimate(x interface{}) float64 {
  return math.Abs(10 - float64(x.(Number)))
}

var n Number = 10
if path, walk, err := astar.Search(&n); err == nil {
  fmt.Println(path)
  fmt.Println(walk)
}
// Output: [1 2 3 4 5 6 7 8 9 10] —— the solution.
// [1 2 3 4 5 6 7 8 9 10]         —— states explored by the algorithm
                                     before it could find the best solution.

You could allow only “subtract 7” and “add 5” operations to get to 10:

func (n Number) Successors() []interface{} { return []interface{}{n - 7, n + 5} }

// Output: [1 6 11 4 9 14 7 12 5 10]
// [1 6 11 4 9 16 14 7 12 -1 2 5 10]

Or subtract 3, 7, and multiply by 9:

func (n Number) Successors() []interface{} { return []interface{}{n - 3, n - 7, n * 9} }

// Output: [1 9 6 3 27 20 13 10]
// [1 9 6 2 3 18 11 8 15 12 4 5 -2 -1 0 -5 -6 -4 -3 27 20 13 10]

Etc.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("final state is not reachable")

ErrNotFound means that the final state cannot be reached from the given start state.

Functions

func Search(p Interface) ([]interface{}, []interface{}, error)

Search finds the p.Finish() state from the given p.Start() state by invoking p.Successors() and p.Move() at each step. Search returns two slices: 1) the shortest path to the final state, and 2) a sequence of explored states. If the shortest path cannot be found, ErrNotFound error is returned.

Example (GraphTraversal)
// Finding the shortest path between Arad and Bucharest
// on a Romanian road map fragment. The road map is represented
// as an undirected graph; edge costs are distances between cities.
//
// An example from Stuart Russell and Peter Norvig's
// “Artificial Intelligence. A Modern Approach”, 3rd ed., 2009, p. 68.
package main

import (
	"fmt"
)

type Graph struct {
	edges map[string]map[string]float64
	curr  string
}

func (g Graph) Start() interface{} { return "Arad" }
func (g Graph) Finish() bool       { return "Bucharest" == g.curr }

func (g *Graph) Move(state interface{}) { g.curr = state.(string) }
func (g Graph) Successors(transitions map[interface{}]interface{}) []interface{} {
	successors := []interface{}{}
	for succ := range g.edges[g.curr] {
		successors = append(successors, succ)
	}

	return successors
}

func (g Graph) Cost(given interface{}) float64 {
	return g.edges[g.curr][given.(string)]
}

// Precalculated distances to Bucharest (Ibid., figure 3.22, p. 93.)
func (g Graph) Estimate(given interface{}) float64 {
	estimates := map[string]float64{
		"Arad":           366,
		"Bucharest":      0,
		"Craiova":        160,
		"Drobeta":        242,
		"Eforie":         161,
		"Făgăraș":        176,
		"Giurgiu":        77,
		"Hârșova":        151,
		"Iași":           226,
		"Lugoj":          244,
		"Mehadia":        241,
		"Neamt":          234,
		"Oradea":         380,
		"Pitești":        100,
		"Râmnicu Vâlcea": 193,
		"Sibiu":          253,
		"Timișoara":      329,
		"Urziceni":       80,
		"Vaslui":         199,
		"Zerind":         374,
	}

	return estimates[given.(string)]
}

func main() {
	g := &Graph{
		edges: map[string]map[string]float64{
			"Arad":           {"Zerind": 75, "Timișoara": 118, "Sibiu": 140},
			"Bucharest":      {"Pitești": 101, "Făgăraș": 211, "Urziceni": 85, "Giurgiu": 90},
			"Craiova":        {"Drobeta": 120, "Râmnicu Vâlcea": 146, "Pitești": 138},
			"Drobeta":        {"Mehadia": 75, "Craiova": 120},
			"Eforie":         {"Hârșova": 86},
			"Făgăraș":        {"Sibiu": 99, "Bucharest": 211},
			"Giurgiu":        {"Bucharest": 90},
			"Hârșova":        {"Urziceni": 98, "Eforie": 86},
			"Iași":           {"Neamt": 87, "Vaslui": 92},
			"Lugoj":          {"Timișoara": 111, "Mehadia": 70},
			"Mehadia":        {"Lugoj": 70, "Drobeta": 75},
			"Oradea":         {"Zerind": 71, "Sibiu": 151},
			"Pitești":        {"Râmnicu Vâlcea": 97, "Craiova": 138, "Bucharest": 101},
			"Râmnicu Vâlcea": {"Sibiu": 80, "Pitești": 97, "Craiova": 146},
			"Sibiu":          {"Arad": 140, "Oradea": 151, "Făgăraș": 99, "Râmnicu Vâlcea": 80},
			"Timișoara":      {"Arad": 118, "Lugoj": 111},
			"Urziceni":       {"Bucharest": 85, "Vaslui": 142, "Hârșova": 98},
			"Vaslui":         {"Urziceni": 142, "Iași": 92},
			"Zerind":         {"Arad": 75, "Oradea": 71},
		}}
	if path, _, err := Search(g); err == nil {
		fmt.Printf("%q\n", path)
	}
}
Output:

["Arad" "Sibiu" "Râmnicu Vâlcea" "Pitești" "Bucharest"]
Example (PouringPuzzle)
// Water pouring puzzle.
//
// Measure out 6 ounces of water using two glasses of 9 and 4 oz.
// You're allowed to pour water from one glass to another as well as emptying
// and refilling them.
//
// Use A* Search to solve it.
//
//	             =()=
//	            .-||--|
//	           .   ___|
//	           '==’
//	            ||
//	            ||
//	|     |     ||
//	|     |
//	|     |   |    |
//	|     |   |    |
//	|     |   |    |
//	|     |   |    |
//	+-----+   +----+
//	 9 oz.     4 oz.
package main

import (
	"os"
	"text/template"
)

// Glasses’ capacities and the goal.
type Puzzle struct {
	CapFirst, CapSecond int
	Goal                int
}

// Glasses state.
type State struct {
	p      *Puzzle
	Action string
	First  int
	Second int
}

func (s State) Start() interface{} { return State{s.p, "Both Empty", 0, 0} }
func (s State) Finish() bool {
	// One of the glasses contains the goal amount.
	return s.p.Goal == s.First || s.p.Goal == s.Second
}
func (s *State) Move(x interface{})            { *s = x.(State) }
func (s State) Cost(x interface{}) float64     { return 1 }
func (s State) Estimate(x interface{}) float64 { return 1 }
func (s State) Successors(transitions map[interface{}]interface{}) []interface{} {
	succ := []interface{}{}

	First, Second, CapFirst, CapSecond := s.First, s.Second, s.p.CapFirst, s.p.CapSecond

	// Fill first glass.
	succ = append(succ, State{s.p, "Fill First", CapFirst, Second})

	// Fill second glass.
	succ = append(succ, State{s.p, "Fill Second", First, CapSecond})

	// Empty first glass.
	succ = append(succ, State{s.p, "Empty First", 0, Second})

	// Empty second glass.
	succ = append(succ, State{s.p, "Empty Second", First, 0})

	// Pour from the first glass into the second.
	if First+Second > CapSecond {
		succ = append(succ, State{s.p, "First –> Second", First - (CapSecond - Second), CapSecond})
	} else {
		succ = append(succ, State{s.p, "First –> Second", 0, First + Second})
	}

	// Pour from the second glass into the first.
	if First+Second > CapFirst {
		succ = append(succ, State{s.p, "Second –> First", CapFirst, Second - (CapFirst - First)})
	} else {
		succ = append(succ, State{s.p, "Second –> First", First + Second, 0})
	}
	return succ
}

const PouringTmpl = `
{{range .}}  {{printf "%-16s (%v %v)\n" .Action .First .Second}}{{end}}
`

func main() {
	if path, _, err := Search(&State{p: &Puzzle{
		CapFirst:  9,
		CapSecond: 4,
		Goal:      6,
	}}); err == nil {
		template.Must(template.New("Pouring Puzzle").Parse(PouringTmpl)).Execute(os.Stdout, path)
	}
}
Output:

  Both Empty       (0 0)
  Fill First       (9 0)
  First –> Second  (5 4)
  Empty Second     (5 0)
  First –> Second  (1 4)
  Empty Second     (1 0)
  First –> Second  (0 1)
  Fill First       (9 1)
  First –> Second  (6 4)

Types

type Interface

type Interface interface {
	// Initial state.
	Start() interface{}

	// Is this state final?
	Finish() bool

	// Move to a new state.
	Move(interface{})

	// Available moves from the current state.
	Successors(transitions map[interface{}]interface{}) []interface{}

	// Path cost between the current and the given state.
	Cost(interface{}) float64

	// Heuristic estimate of “how far to go?” between the given
	// and the final state. Smaller values mean closer.
	Estimate(interface{}) float64
}

Interface describes a type suitable for A* search. Any type can do as long as it can change its current state and tell legal moves from it. Knowing costs and estimates helps, but not necessary.

Directories

Path Synopsis
cmd
maze
Use A* search algorithm to traverse a maze.
Use A* search algorithm to traverse a maze.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL