{"id":50344873,"url":"https://github.com/kjkrol/astar","last_synced_at":"2026-05-29T19:03:33.258Z","repository":{"id":359355650,"uuid":"1244417275","full_name":"kjkrol/astar","owner":"kjkrol","description":"A highly performant, fully generic A* solver for Go. Perfect for optimal pathfinding and abstract state-space search.","archived":false,"fork":false,"pushed_at":"2026-05-23T00:03:27.000Z","size":47,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-23T01:25:00.918Z","etag":null,"topics":["algorithm","astar","astar-algorithm","astar-pathfinding","astar-search","generic","go","high-performance","pathfinder","zero-allocation"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kjkrol.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-20T08:44:26.000Z","updated_at":"2026-05-23T00:03:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kjkrol/astar","commit_stats":null,"previous_names":["kjkrol/goka","kjkrol/astar"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/kjkrol/astar","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kjkrol%2Fastar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kjkrol%2Fastar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kjkrol%2Fastar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kjkrol%2Fastar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kjkrol","download_url":"https://codeload.github.com/kjkrol/astar/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kjkrol%2Fastar/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33666290,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["algorithm","astar","astar-algorithm","astar-pathfinding","astar-search","generic","go","high-performance","pathfinder","zero-allocation"],"created_at":"2026-05-29T19:03:33.059Z","updated_at":"2026-05-29T19:03:33.242Z","avatar_url":"https://github.com/kjkrol.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# kjkrol/astar\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\".github/docs/img/logo.png\" alt=\"kjkrol/astar Logo\" width=\"100\"\u003e\n  \u003cbr\u003e\n    \u003ca href=\"https://go.dev\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Go-1.23+-00ADD8?style=flat-square\u0026logo=go\" alt=\"Go Version\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://pkg.go.dev/github.com/kjkrol/astar\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/GoDoc-Reference-007d9c?style=flat-square\u0026logo=go\" alt=\"GoDoc\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square\" alt=\"License\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/kjkrol/astar/actions\"\u003e\n    \u003cimg src=\"https://github.com/kjkrol/astar/actions/workflows/go.yml/badge.svg\" alt=\"Go Quality Check\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n**kjkrol/astart** is a **highly performant, fully generic A*** solver for Go. Perfect for optimal pathfinding and abstract state-space search.\n\n## What is A*?\n\nThe [A* (A-star) algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm) is a graph traversal and state-space search algorithm heavily used in computer science due to its completeness, optimality, and optimal efficiency. It is widely considered the industry standard for finding the shortest path or optimal sequence of transitions between nodes.\n\nWhile most commonly used as a **Pathfinder**, this library provides a completely domain-agnostic `Solver`. You can use it to solve complex puzzles, optimize network routing, or navigate grids, simply by providing your own `Heuristic` and `Transitions` logic. The Solver remains strictly agnostic of the underlying graph structure or domain-specific cost metrics, making it a universal state-space resolution engine.\n\n# 📦 Installation\n\nGOKe requires **Go 1.23** or newer.\n\n```bash\ngo get github.com/kjkrol/goke\n```\n\n---\n\n# ⏱️ Performance\n\nThis solver is built for extreme performance. By utilizing Go 1.18+ Generics, custom memory arenas, and highly optimized data structures, we drastically reduce memory allocations and prevent heap escapes during the hot path of the search.\n\n**Key Optimizations:**\n* **`WithIndexedSliceDict`:** Replaces standard Go maps with a pre-allocated slice for tracking visited nodes. It completely eliminates hashing overhead and interface boxing.\n* **Node Arena:** An internal chunk-based memory arena ensures that millions of nodes can be processed with near-zero allocations after the initial setup.\n\n## Benchmarks (Apple M1 Max)\n\nThe benchmarks below represent execution metrics under standard A* operational stress (`CostWeight_1.0`), where the algorithm fully evaluates path costs and alternative routes. \n\nAs the state space expands, the `IndexedSliceDict` provides massive scaling advantages over map-based lookups. For a large **2048x2048** environment, it reduces execution time from **1.41 seconds down to just 0.54 seconds**—outperforming the standard Go map implementation by **2.6x**.\n\n| Grid Size | Dictionary Type | Time (ms/op) | Memory (kB/op) | Allocs (allocs/op) |\n| :--- | :--- | :--- | :--- | :--- |\n| **64x64** | `IndexedSliceDict` | 0.36 ms | 4.2 kB | 12 |\n| | `IndexedMapDict` | 0.58 ms | 4.1 kB | 12 |\n| | `DefaultMapDict` | 0.62 ms | 4.1 kB | 12 |\n| **256x256** | `IndexedSliceDict` | 6.85 ms | 33.2 kB | 14 |\n| | `IndexedMapDict` | 10.98 ms | 16.2 kB | 14 |\n| | `DefaultMapDict` | 11.94 ms | 16.3 kB | 14 |\n| **512x512** | `IndexedSliceDict` | 30.15 ms | 388.3 kB | 16 |\n| | `IndexedMapDict` | 59.11 ms | 49.8 kB | 16 |\n| | `DefaultMapDict` | 60.11 ms | 49.8 kB | 16 |\n| **1024x1024** | `IndexedSliceDict` | 126.10 ms | 6,253.1 kB | 21 |\n| | `IndexedMapDict` | 288.12 ms | 124.6 kB | 21 |\n| | `DefaultMapDict` | 308.14 ms | 124.7 kB | 21 |\n| **2048x2048** | `IndexedSliceDict` | **541.76 ms** | 98,545.3 kB | **34** |\n| | `IndexedMapDict` | 1,411.57 ms | 324.5 kB | 34 |\n| | `DefaultMapDict` | 1,416.12 ms | 324.5 kB | 34 |\n\n---\n\n# 🚀 Getting Started (Pathfinding Example)\n\nHere is a step-by-step example of how to configure the solver to find the optimal path on a 2D terrain grid.\n\n### Define your domain state and world grid\nThe solver is generic, so you define the state representation. For a grid map, a Point struct represents coordinates, and a 2D slice simulates the world terrain.\n\n```go\ntype Point struct {\n\tX, Y int\n}\n\nconst (\n\tGridSize = 64\n\t\n\t// Terrain types and their cost/weights\n\tTerrainWalkway = 1.0\n\tTerrainMud     = 3.5\n\tTerrainWall    = 999.0 // Insurmountable obstacle\n)\n\n// Example world grid layout (pre-allocated or loaded from game data)\nvar grid [GridSize][GridSize]float64\n```\n\nAdditionally, you need to define a heuristic function (e.g., Manhattan distance) to estimate the remaining distance to the target:\n\n```go\n// Heuristic is part of your domain definition\nheuristic := func(from, to Point) float64 {\n\tdx := math.Abs(float64(to.X - from.X))\n\tdy := math.Abs(float64(to.Y - from.Y))\n\treturn dx + dy\n}\n```\n\n### Initialize the Solver\n\nPass your static heuristic into astar.New. To unlock maximum performance on fixed state spaces, use WithIndexedSliceDict by providing an indexer function mapped to your grid dimensions.\n\n**Note:** The solver allocates its internal memory structures once during initialization. This instance is designed to be reused sequentially across multiple distinct pathfinding queries to avoid GC pressure. It is not thread-safe; if you need concurrent pathfinding, use separate solver instances per goroutine or orchestrate them via a pool.\n```go\n// The Indexer maps a 2D coordinate to a unique 1D slice index\nindexer := func(p Point) int { \n\treturn p.Y * GridSize + p.X \n}\nmaxNodes := GridSize * GridSize\n\n// Initialize the Solver once with static configuration\nsolver := astar.New(\n\theuristic,\n\tastar.WithIndexedSliceDict(maxNodes, indexer),\n)\n```\n\n### Define Rules \u0026 Execute Search (Reusing Buffers)\n\nEvery time you call Solve(), you pass a transition rule. This allows you to dynamicly change movement logic on the fly without reallocating solver internal buffers, ensuring zero-allocation hot paths.\n\n```go\n// Transitions: Populates the pre-allocated buffer with valid moves and terrain costs in a single pass.\ndirs := []Point{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}\n\ntransitions := func(from, prev Point, buffer []astar.Transition[Point]) []astar.Transition[Point] {\n\tfor _, d := range dirs {\n\t\tnx, ny := from.X+d.X, from.Y+d.Y\n\t\t\n\t\t// 1. Boundary check\n\t\tif nx \u003e= 0 \u0026\u0026 nx \u003c GridSize \u0026\u0026 ny \u003e= 0 \u0026\u0026 ny \u003c GridSize {\n\t\t\t\n\t\t\t// 2. Prevent immediate backtracking to the parent node\n\t\t\tif nx == prev.X \u0026\u0026 ny == prev.Y {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t\n\t\t\t// 3. Static obstacle pruning (e.g., skip walls entirely)\n\t\t\tterrainCost := grid[ny][nx]\n\t\t\tif terrainCost \u003e= TerrainWall {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t\n\t\t\t// 4. Register valid transition with its intrinsic edge weight\n\t\t\tbuffer = append(buffer, astar.Transition[Point]{\n\t\t\t\tTo:   Point{X: nx, Y: ny},\n\t\t\t\tCost: terrainCost, \n\t\t\t})\n\t\t}\n\t}\n\treturn buffer\n}\n\nstart := Point{X: 0, Y: 0}\ntarget := Point{X: 63, Y: 63}\n\n// Execute the search by injecting the grid transition rules into the reused solver\npath := solver.Solve(start, target, transitions)\n\nif path != nil {\n\tfmt.Println(\"Found optimal path with steps:\", len(path))\n}\n```\n\n# License\n\n**kjkrol/astar** is licensed under the MIT License. See the LICENSE [file](./LICENSE) for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkjkrol%2Fastar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkjkrol%2Fastar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkjkrol%2Fastar/lists"}