forked from arnemart/aoc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
astar.ts
107 lines (92 loc) · 2.66 KB
/
astar.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Typescriptified version of https://github.com/superjoe30/node-astar
import Heap = require('heap')
type Node<T> = {
data: T
g: number
h: number
f: number
parent: Node<T>
}
type Params<T> = {
start: T
isEnd: (node: T) => boolean
neighbors: (node: T) => T[]
cost?: (a: T, b: T) => number
heuristic?: (node: T) => number
hash?: (node: T) => string
}
type Result<T> = {
cost: number
path: T[]
}
export default function aStar<T>(params: Params<T>): Result<T> {
const hash = params.hash || JSON.stringify
const cost = params.cost || (() => 1)
const heuristic = params.heuristic || (() => 1)
const startNode: Node<T> = {
data: params.start,
g: 0,
h: heuristic(params.start),
f: 0,
parent: null
}
startNode.f = startNode.h
const closedDataSet = new Set<string>()
const openHeap = new Heap<Node<T>>((a, b) => a.f - b.f)
const openDataMap = new Map<string, Partial<Node<T>>>()
openHeap.push(startNode)
openDataMap.set(hash(startNode.data), startNode)
while (!openHeap.empty()) {
const node = openHeap.pop()
const nodeHash = hash(node.data)
openDataMap.delete(nodeHash)
if (params.isEnd(node.data)) {
// done
return {
cost: node.g,
path: reconstructPath(node)
}
}
// not done yet
closedDataSet.add(nodeHash)
for (const neighborData of params.neighbors(node.data)) {
const neighborHash = hash(neighborData)
if (closedDataSet.has(neighborHash)) {
// skip closed neighbors
continue
}
const gFromThisNode = node.g + cost(node.data, neighborData)
let neighborNode = openDataMap.get(neighborHash)
let update = false
if (!neighborNode) {
neighborNode = {
data: neighborData
// other properties will be set later
}
// add neighbor to the open set
openDataMap.set(neighborHash, neighborNode)
} else {
if (neighborNode.g < gFromThisNode) {
// skip this one because another route is faster
continue
}
update = true
}
// found a new or better route.
// update this neighbor with this node as its new parent
neighborNode.parent = node
neighborNode.g = gFromThisNode
neighborNode.h = heuristic(neighborData)
neighborNode.f = gFromThisNode + neighborNode.h
if (update) {
openHeap.heapify()
} else {
openHeap.push(neighborNode as Node<T>)
}
}
}
// all the neighbors of every accessible node have been exhausted
return null
}
const reconstructPath = <T>(node: Node<T>): T[] =>
node.parent ? [...reconstructPath(node.parent), node.data] : [node.data]