abstract-astar
Version:
Versatile A* (A Star) implementation in TypeScript.
128 lines (127 loc) • 5.16 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.aStar = exports.MinHeap = void 0;
const parent = (i) => Math.floor((i - 1) / 2);
const left = (i) => 2 * i + 1;
const right = (i) => 2 * i + 2;
class MinHeap {
constructor(_getValue) {
this._getValue = _getValue;
this._heapArray = [];
this._itemToIndex = new Map();
}
upsert(item) {
const existingIndex = this._itemToIndex.get(item);
if (existingIndex !== undefined)
this._heapifyDownFromIndex(existingIndex);
else
this.insert(item);
}
contains(item) {
return this._itemToIndex.get(item) !== undefined;
}
insert(item) {
const { _getValue, _heapArray, _itemToIndex } = this;
// add to the end
_heapArray.push(item);
let index = this._heapArray.length - 1;
_itemToIndex.set(item, index);
// swap it up the heap until the invariant condition restored
while (index !== 0) {
const parentIndex = parent(index);
const parentItem = _heapArray[parentIndex];
if (_getValue(parentItem) <= _getValue(_heapArray[index]))
return;
this._swapIndexes(index, parentIndex);
index = parentIndex;
}
}
removeMinimum() {
const { _heapArray, _itemToIndex } = this;
if (_heapArray.length <= 0)
return undefined;
if (_heapArray.length === 1) {
_itemToIndex.delete(_heapArray[0]);
return _heapArray.pop();
}
const root = _heapArray[0];
_itemToIndex.delete(root);
// put the last item in the first position and restore invariant
_heapArray[0] = _heapArray.pop();
this._heapifyDownFromIndex(0);
return root;
}
_swapIndexes(firstIndex, secondIndex) {
const { _heapArray, _itemToIndex } = this;
const firstItem = _heapArray[firstIndex];
const secondItem = _heapArray[secondIndex];
_itemToIndex.set(firstItem, secondIndex);
_itemToIndex.set(secondItem, firstIndex);
_heapArray[firstIndex] = secondItem;
_heapArray[secondIndex] = firstItem;
}
/** Recursively ensures the item at the given index is less than its children. */
_heapifyDownFromIndex(index) {
const { _heapArray, _getValue } = this;
const leftIndex = left(index);
const rightIndex = right(index);
let smallestIndex = index;
const size = _heapArray.length;
if (leftIndex < size &&
_getValue(_heapArray[leftIndex]) < _getValue(_heapArray[index]))
smallestIndex = leftIndex;
if (rightIndex < size &&
_getValue(_heapArray[rightIndex]) < _getValue(_heapArray[smallestIndex]))
smallestIndex = rightIndex;
if (smallestIndex !== index) {
this._swapIndexes(index, smallestIndex);
this._heapifyDownFromIndex(smallestIndex);
}
}
}
exports.MinHeap = MinHeap;
function aStar({ start, goal, estimateFromNodeToGoal, neighborsAdjacentToNode, actualCostToMove: costToMove, }) {
var _a;
const cameFromMap = new Map();
const cheapestActualCostFrom = new Map();
cheapestActualCostFrom.set(start, 0);
const cheapestEstimatedCostToGoalFrom = new Map();
cheapestEstimatedCostToGoalFrom.set(start, estimateFromNodeToGoal(start));
const openMinHeap = new MinHeap((node) => { var _a; return (_a = cheapestEstimatedCostToGoalFrom.get(node)) !== null && _a !== void 0 ? _a : Infinity; });
openMinHeap.upsert(start);
while (true) {
const current = openMinHeap.removeMinimum();
// no more to explore, failed to find path
if (current === undefined)
return undefined;
// reached goal
if (current === goal)
return reconstructPath(cameFromMap, current);
const cheapestActualCostToCurrent = (_a = cheapestActualCostFrom.get(current)) !== null && _a !== void 0 ? _a : Infinity;
neighborsAdjacentToNode(current).forEach((neighbor) => {
var _a;
const actualCostToNeighbor = cheapestActualCostToCurrent +
costToMove(cameFromMap, current, neighbor);
const cheapestActualCostToNeighbor = (_a = cheapestActualCostFrom.get(neighbor)) !== null && _a !== void 0 ? _a : Infinity;
if (actualCostToNeighbor < cheapestActualCostToNeighbor) {
cameFromMap.set(neighbor, current);
cheapestActualCostFrom.set(neighbor, actualCostToNeighbor);
cheapestEstimatedCostToGoalFrom.set(neighbor, actualCostToNeighbor + estimateFromNodeToGoal(neighbor));
openMinHeap.upsert(neighbor);
}
});
}
}
exports.aStar = aStar;
function reconstructPath(cameFrom, current) {
const total = [current];
while (true) {
const newCurrent = cameFrom.get(current);
if (newCurrent === undefined)
return total.reverse();
else {
total.push(newCurrent);
current = newCurrent;
}
}
}