Skip to content

Commit

Permalink
Migrate to TS
Browse files Browse the repository at this point in the history
  • Loading branch information
remvst committed Mar 6, 2021
1 parent 580a9ac commit 9c00625
Show file tree
Hide file tree
Showing 8 changed files with 2,291 additions and 123 deletions.
25 changes: 25 additions & 0 deletions lib/path-finder.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
declare type Options<StateType> = {
'hash': (state: StateType) => string;
'neighbors': (state: StateType) => StateType[];
'heuristic': (state: StateType, target: StateType) => number;
'isTarget': (state: StateType, target: StateType) => boolean;
'distance': (stateA: StateType, stateB: StateType) => number;
};
declare type Result<StateType> = {
'found': boolean;
'expandedOrder': StateType[];
'steps': StateType[];
'iterations': number;
};
export default class PathFinder<StateType> {
private readonly hash;
private readonly neighbors;
private readonly heuristic;
private readonly isTarget;
private readonly distance;
constructor(options: Options<StateType>);
findPath(sources: StateType[], target: StateType, maxIterations?: number): Result<StateType>;
private closestExpandable;
private expand;
}
export {};
104 changes: 24 additions & 80 deletions src/path-finder.js → lib/path-finder.js
Original file line number Diff line number Diff line change
@@ -1,167 +1,111 @@
'use strict';

Object.defineProperty(exports, "__esModule", { value: true });
class PathNodeSet {

constructor() {
this.map = {};
this.map = new Map();
this.list = [];
}

add(key, node) {
if (this.has(key)) {
return;
}

this.list.push(node);
this.map[key] = node;
this.map.set(key, node);
}

fetch(key) {
return this.map[key] || null;
return this.map.get(key) || null;
}

has(key) {
return !!this.fetch(key);
return this.map.has(key);
}

remove(key) {
const node = this.fetch(key);
if (!node) {
return;
}

const index = this.list.indexOf(node);
if (index >= 0) {
this.list.splice(index, 1);
}

delete this.map[key];
this.map.delete(key);
}

}

class PathNode {

constructor(node, parent = null, distance = 0) {
this.node = node;
this.parent = parent;
this.distance = distance;
}

}

class PathFinder {

constructor(options) {
this.funcs = {};
this.funcs.hash = options.hash;
this.funcs.neighbors = options.neighbors;
this.funcs.heuristic = options.heuristic;
this.funcs.isTarget = options.isTarget;
this.funcs.distance = options.distance;
this.hash = options.hash;
this.neighbors = options.neighbors;
this.heuristic = options.heuristic;
this.isTarget = options.isTarget;
this.distance = options.distance;
}

hashFor(node) {
return this.funcs.hash(node);
}

neighborsFor(node) {
return this.funcs.neighbors(node);
}

heuristicFor(node, target) {
return this.funcs.heuristic(node, target);
}

isTarget(node, target) {
return this.funcs.isTarget(node, target);
}

distanceBetween(nodeA, nodeB) {
return this.funcs.distance(nodeA, nodeB);
}

findPath(sources, target, maxIterations = 100) {
const expandable = new PathNodeSet();
const expanded = new PathNodeSet();

// Add the initial node
sources.forEach(source => {
expandable.add(this.hashFor(source), new PathNode(source));
expandable.add(this.hash(source), new PathNode(source));
});

let finalPathNode;

let finalPathNode = null;
const expandedOrder = [];

let iteration;
for (iteration = 0 ; iteration < maxIterations ; iteration++) {
for (iteration = 0; iteration < maxIterations; iteration++) {
const pathElement = this.closestExpandable(expandable, target);
if (!pathElement) {
break;
}

if (this.isTarget(pathElement.node, target)) {
finalPathNode = pathElement;
break;
}

this.expand(pathElement, expandable, expanded);
expandedOrder.push(pathElement.node);
}

const steps = [];
let pathNode = finalPathNode;
while (pathNode && finalPathNode) {
steps.unshift(pathNode.node);
pathNode = pathNode.parent;
}

return {
'found': !!finalPathNode,
'expandedOrder': expandedOrder,
'steps': steps,
'iterations': iteration
};
}

closestExpandable(expandableSet, target) {
let closest = null;
let closestHeuristic;

let closestHeuristic = Number.MAX_SAFE_INTEGER;
expandableSet.list.forEach(pathElement => {
const heuristic = this.heuristicFor(pathElement.node, target) + pathElement.distance;
const heuristic = this.heuristic(pathElement.node, target) + pathElement.distance;
if (!closest || heuristic < closestHeuristic) {
closest = pathElement;
closestHeuristic = heuristic;
}
});

return closest;
}

expand(pathElement, expandableSet, expandedSet) {
// Mask as expanded
const nodeHash = this.hashFor(pathElement.node);

const nodeHash = this.hash(pathElement.node);
expandedSet.add(nodeHash, pathElement);
expandableSet.remove(nodeHash);

// Actually expand
const neighbors = this.neighborsFor(pathElement.node);
neighbors.forEach(neighborNode => {
const neighborHash = this.hashFor(neighborNode);
const neighborDistance = pathElement.distance + this.distanceBetween(pathElement.node, neighborNode);

for (const neighborNode of this.neighbors(pathElement.node)) {
const neighborHash = this.hash(neighborNode);
const neighborDistance = pathElement.distance + this.distance(pathElement.node, neighborNode);
const alreadyExpanded = expandedSet.fetch(neighborHash);
if (alreadyExpanded) {
return;
if (!alreadyExpanded) {
expandableSet.add(neighborHash, new PathNode(neighborNode, pathElement, neighborDistance));
}

expandableSet.add(neighborHash, new PathNode(neighborNode, pathElement, neighborDistance));
});
}
}

}

module.exports = PathFinder;
exports.default = PathFinder;

0 comments on commit 9c00625

Please sign in to comment.