Documentation
ΒΆ
Index ΒΆ
Examples ΒΆ
Constants ΒΆ
This section is empty.
Variables ΒΆ
This section is empty.
Functions ΒΆ
This section is empty.
Types ΒΆ
type Heuristic ΒΆ
type Heuristic[T comparable] func(current, goal T) float64
Heuristic defines the estimated cost to reach the goal state from the current state.
Note on Performance vs. Optimality: To drastically narrow the search space and speed up the solver, the heuristic's contribution should dominate the actual step cost. You can achieve this by multiplying the heuristic result by a weight (e.g., > 1.0) or by reducing the step cost. This effectively turns the algorithm into Weighted A*, which visits far fewer nodes but sacrifices the guarantee of finding the absolute shortest path.
type Indexer ΒΆ
type Indexer[T comparable] func(T) int
Indexer maps a complex state of type T to a unique, contiguous integer identifier. It is required when using highly optimized internal structures like IndexedSliceDict.
type Solver ΒΆ
type Solver[T comparable] struct { // contains filtered or unexported fields }
Solver is a generic, high-performance pathfinding and state-space search engine based on the A* algorithm. It is entirely agnostic to the underlying domain problem.
func New ΒΆ
func New[T comparable]( heuristic Heuristic[T], opts ...SolverOption[T], ) *Solver[T]
New initializes and returns a new Solver configured with the provided heuristic and optional configuration parameters.
func (*Solver[T]) Iter ΒΆ
func (a *Solver[T]) Iter(start, goal T, successors Transitions[T]) iter.Seq[bool]
Iter returns a Go 1.23 iterator sequence that allows stepping through the algorithm's execution. It yields 'false' while actively searching, and yields 'true' exactly once the moment the goal state is reached, immediately terminating the sequence afterwards.
This is exceptionally useful for visualizing the state space traversal, step-by-step debugging, or aborting the search early based on custom external conditions.
Example (DefaultDict) ΒΆ
// Requesting a 64x64 grid with a cost weight of 1.0
const size = 64
grid := setupGrid(size)
pathFinder := setupPathFinder()
transitions := setupTransitions(1.0, grid)
start := Point{X: 0, Y: 0}
goal := Point{X: size - 1, Y: size - 1}
// Consume the iterator until completion.
// The sequence self-terminates as soon as the goal is found.
for range pathFinder.Iter(start, goal, transitions) {
}
path := pathFinder.Result()
if len(path) > 0 {
fmt.Println("Path found:", true)
fmt.Println("Start is correct:", path[0] == start)
fmt.Println("Goal is correct:", path[len(path)-1] == goal)
} else {
fmt.Println("Path not found")
}
Output: Path found: true Start is correct: true Goal is correct: true
Example (IndexedMapDict) ΒΆ
const size = 64
grid := setupGrid(size)
indexer := func(p Point) int { return p.Y*size + p.X }
pathFinder := setupPathFinder(
astar.WithInitCapacity[Point](size*size),
astar.WithIndexedMapDict(indexer),
)
transitions := setupTransitions(1.0, grid)
start := Point{X: 0, Y: 0}
goal := Point{X: size - 1, Y: size - 1}
for range pathFinder.Iter(start, goal, transitions) {
}
path := pathFinder.Result()
if len(path) > 0 {
fmt.Println("Path found:", true)
fmt.Println("Start is correct:", path[0] == start)
fmt.Println("Goal is correct:", path[len(path)-1] == goal)
} else {
fmt.Println("Path not found")
}
Output: Path found: true Start is correct: true Goal is correct: true
Example (IndexedSliceDict) ΒΆ
const size = 64
grid := setupGrid(size)
indexer := func(p Point) int { return p.Y*size + p.X }
pathFinder := setupPathFinder(
astar.WithIndexedSliceDict(size*size, indexer),
)
transitions := setupTransitions(1.0, grid)
start := Point{X: 0, Y: 0}
goal := Point{X: size - 1, Y: size - 1}
for range pathFinder.Iter(start, goal, transitions) {
}
path := pathFinder.Result()
if len(path) > 0 {
fmt.Println("Path found:", true)
fmt.Println("Start is correct:", path[0] == start)
fmt.Println("Goal is correct:", path[len(path)-1] == goal)
} else {
fmt.Println("Path not found")
}
Output: Path found: true Start is correct: true Goal is correct: true
func (*Solver[T]) Result ΒΆ
func (a *Solver[T]) Result() []T
Result reconstructs and returns the path from the starting state to the current state. It is typically called immediately after the Iter sequence yields 'true'.
func (*Solver[T]) Solve ΒΆ
func (a *Solver[T]) Solve(start, goal T, successors Transitions[T]) []T
Solve executes the search from the start state to the goal state. It runs the iterator to completion and returns the final path. If no path is found, it returns nil.
Example (DefaultDict) ΒΆ
const size = 64
grid := setupGrid(size)
pathFinder := setupPathFinder()
transitions := setupTransitions(1.0, grid)
start := Point{X: 0, Y: 0}
goal := Point{X: size - 1, Y: size - 1}
path := pathFinder.Solve(start, goal, transitions)
if len(path) > 0 {
fmt.Println("Path found:", true)
fmt.Println("Start is correct:", path[0] == start)
fmt.Println("Goal is correct:", path[len(path)-1] == goal)
} else {
fmt.Println("Path not found")
}
Output: Path found: true Start is correct: true Goal is correct: true
Example (IndexedMapDict) ΒΆ
const size = 64
grid := setupGrid(size)
indexer := func(p Point) int { return p.Y*size + p.X }
pathFinder := setupPathFinder(
astar.WithInitCapacity[Point](size*size),
astar.WithIndexedMapDict(indexer),
)
transitions := setupTransitions(1.0, grid)
start := Point{X: 0, Y: 0}
goal := Point{X: size - 1, Y: size - 1}
path := pathFinder.Solve(start, goal, transitions)
if len(path) > 0 {
fmt.Println("Path found:", true)
fmt.Println("Start is correct:", path[0] == start)
fmt.Println("Goal is correct:", path[len(path)-1] == goal)
} else {
fmt.Println("Path not found")
}
Output: Path found: true Start is correct: true Goal is correct: true
Example (IndexedSliceDict) ΒΆ
const size = 64
grid := setupGrid(size)
indexer := func(p Point) int { return p.Y*size + p.X }
pathFinder := setupPathFinder(
astar.WithIndexedSliceDict(size*size, indexer),
)
transitions := setupTransitions(1.0, grid)
start := Point{X: 0, Y: 0}
goal := Point{X: size - 1, Y: size - 1}
path := pathFinder.Solve(start, goal, transitions)
if len(path) > 0 {
fmt.Println("Path found:", true)
fmt.Println("Start is correct:", path[0] == start)
fmt.Println("Goal is correct:", path[len(path)-1] == goal)
} else {
fmt.Println("Path not found")
}
Output: Path found: true Start is correct: true Goal is correct: true
type SolverOption ΒΆ
type SolverOption[T comparable] func(*config[T])
SolverOption configures the internal behavior and data structures of the Solver.
func WithIndexedMapDict ΒΆ
func WithIndexedMapDict[T comparable](indexer Indexer[T]) SolverOption[T]
WithIndexedMapDict configures the solver to use standard Go maps backed by an Indexer function. By mapping complex state types to simple integer keys, it bypasses Go's interface boxing and complex hashing overhead.
This provides a noticeable performance boost (often in the double-digit percentages) compared to the default map implementation, while remaining more memory-efficient than slice-based dictionaries for problems with sparse state spaces.
func WithIndexedSliceDict ΒΆ
func WithIndexedSliceDict[T comparable](maxSize int, indexer Indexer[T]) SolverOption[T]
WithIndexedSliceDict configures the solver to use slice-based dictionaries for its internal "open" and "closed" sets. Because the underlying slice does not grow dynamically, the provided maxSize must represent the absolute maximum number of possible states (nodes). It also requires an Indexer function to map node properties to contiguous integer indices.
This is the fastest dictionary implementation available, typically doubling the solver's overall performance by completely eliminating map hashing overhead and reducing memory allocations.
func WithInitCapacity ΒΆ
func WithInitCapacity[T comparable](capacity int) SolverOption[T]
WithInitCapacity sets the initial capacity for the solver's internal data structures, such as dictionaries and memory arenas. Providing an accurate estimate prevents costly dynamic allocations during the search execution. If the capacity is exceeded, the underlying structures will automatically grow (typically doubling their size).
func WithSuccessorCapacity ΒΆ
func WithSuccessorCapacity[T comparable](capacity int) SolverOption[T]
WithSuccessorCapacity pre-allocates the internal buffer used for fetching neighbor nodes. Setting this to the maximum expected number of successors per node (e.g., 4 or 8 for 2D grids) guarantees zero allocations for successor retrieval from the very first search iteration.
type Transition ΒΆ added in v1.0.4
type Transition[T comparable] struct { To T // The destination state of this transition. Cost float64 // The edge weight or cost associated with this state change. }
Transition represents a valid movement or mutation in the state space, capturing the destination state and the cost incurred to reach it.
type Transitions ΒΆ added in v1.0.4
type Transitions[T comparable] func(current, parent T, buffer []Transition[T]) []Transition[T]
Transitions defines how states mutate and how transition costs are calculated. It populates the provided reusable buffer with all valid, directly reachable transitions from the current state in a single pass.
This decoupled design keeps the Solver strictly generic and agnostic of the underlying graph structure, state representation, or domain-specific cost metrics. It is the ideal place to:
- Evaluate valid state mutations and compute dynamic transition costs (edge weights).
- Filter out invalid, blocked, or out-of-bounds states.
- Prevent immediate backtracking or simple cycles by utilizing the 'parent' state.
- Perform early pruning by excluding transitions whose cost exceeds custom thresholds.
Returns the sliced buffer containing the valid transitions.