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(from, to T) float64
Heuristic defines the estimated cost to travel from one state to another.
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(from, to T, transitions Transitions[T]) iter.Seq[bool]
Iter yields false while searching and true once the 'to' state is reached. The sequence terminates immediately after reaching the target.
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(from, to T, transitions Transitions[T]) []T
Solves the optimal transition sequence between the 'from' and 'to' states. It returns the full sequence of states, or nil if no solution exists.
Example ΒΆ
package main
import (
"fmt"
"github.com/kjkrol/astar"
)
// Point represents a coordinate on a 2D grid.
type Point2d struct {
X, Y int
}
func main() {
// Define a simple 3x3 grid where 1 represents a wall/blocked cell.
grid := [][]int{
{0, 0, 0},
{1, 1, 0},
{0, 0, 0},
}
width, height := 3, 3
// Indexer maps a Point to a unique dense integer for maximum performance.
// This allows the slice-based dictionary to bypass heavy hashmap lookups.
indexer := func(p Point2d) int {
return p.Y*width + p.X
}
// Define a simple Manhattan distance heuristic
heuristic := func(from, to Point2d) float64 {
dx := from.X - to.X
dy := from.Y - to.Y
if dx < 0 {
dx = -dx
}
if dy < 0 {
dy = -dy
}
return float64(dx + dy)
}
// Setup the solver with maximum optimizations for a bounded space.
solver := astar.New[Point2d](
heuristic,
astar.WithInitCapacity[Point2d](width*height),
astar.WithIndexedSliceDict(width*height, indexer),
)
// Define transitions (allowed movements on our grid).
transitions := func(from, prev Point2d, buffer []astar.Transition[Point2d]) []astar.Transition[Point2d] {
// Reset buffer slice while preserving underlying allocation
buffer = buffer[:0]
// 4-directional movement vectors (Up, Down, Left, Right)
dirs := []Point2d{{0, -1}, {0, 1}, {-1, 0}, {1, 0}}
for _, d := range dirs {
next := Point2d{X: from.X + d.X, Y: from.Y + d.Y}
// 1. Boundary check
if next.X < 0 || next.X >= width || next.Y < 0 || next.Y >= height {
continue
}
// 2. Obstacle check
if grid[next.Y][next.X] == 1 {
continue
}
// 3. Prevent immediate backtracking to the previous state
if next == prev {
continue
}
// Append valid step with a uniform move cost of 1.0
buffer = append(buffer, astar.Transition[Point2d]{To: next, Cost: 1.0})
}
return buffer
}
// Define start and target states
from := Point2d{X: 0, Y: 0}
to := Point2d{X: 0, Y: 2}
// Solve the problem
path := solver.Solve(from, to, transitions)
fmt.Printf("Path length: %d\n", len(path))
fmt.Printf("Steps: %v\n", path)
}
Output: Path length: 7 Steps: [{0 0} {1 0} {2 0} {2 1} {2 2} {1 2} {0 2}]
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(from, prev T, buffer []Transition[T]) []Transition[T]
Transitions defines how steps between states are discovered and how their costs are calculated. It populates the provided reusable buffer with all valid, directly reachable transitions from the 'from' 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 neighbor nodes and compute dynamic transition costs (edge weights).
- Filter out invalid, blocked, or out-of-bounds destinations.
- Prevent immediate backtracking or simple cycles by utilizing the 'prev' state.
- Perform early pruning by excluding transitions whose cost exceeds custom thresholds.
Returns the sliced buffer containing the valid transitions.