Skip to content

Commit

Permalink
refactor: use the common astar implementation for flooding
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexAegis committed Dec 20, 2023
1 parent ca54ef6 commit 2875275
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 126 deletions.
114 changes: 6 additions & 108 deletions solutions/typescript/libs/lib/src/model/graph/graph.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,121 +168,19 @@ export class Graph<
Omit<EdgeCollectionOptions<T, Dir, N>, 'pathConstructor' | 'allNodes'>,
): PathFindingResult<N> {
return dijkstra<T, Dir, N>({
...options,
allNodes: this.nodes,
start: options.start,
end: options.end,
currentPathWeighter: options.currentPathWeighter,
edgeFilter: options.edgeFilter,
edgeGenerator: options.edgeGenerator,
});
}

/**
*
*/
public floodDiskstra(start: N | undefined): Map<N, number> {
if (!start) {
return new Map();
}
const q = new Set<N>(this.nodes.values());

const dist = new Map<N, number>();
const prev = new Map<N, N>();
dist.set(start, 0);

while (q.size > 0) {
// refactor this to a prio queue
const umin = [...q.values()].reduce(
(acc, b) => {
const u = dist.get(b) ?? Number.POSITIVE_INFINITY;
if (!acc.node || u < acc.dist) {
acc.node = b;
acc.dist = dist.get(b) ?? Number.POSITIVE_INFINITY;
}
return acc;
},
{ node: undefined as N | undefined, dist: Number.POSITIVE_INFINITY },
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const u = umin.node!;

q.delete(u);

for (const neighbour of u) {
const alt = umin.dist + (neighbour.weight ?? 1);
if (alt < (dist.get(neighbour.to) ?? Number.POSITIVE_INFINITY)) {
dist.set(neighbour.to, alt);
prev.set(neighbour.to, u);
}
}
}

return dist;
}

/**
* A gutted out aStar, not trying to find a path, but calculating a distanceMap
* to all reachable node.
* to all reachable nodes.
*/
public flood(options: GraphTraversalOptions<T, Dir, N>): Map<N, number> {
if (!options.start) {
return new Map();
}
const openSet = new Set<N>([options.start]); // q?
const cameFrom = new Map<N, N>(); // prev!
const gScore = new Map<N, number>(); // weightMap! Infinity
const dMap = new Map<N, number>(); // distanceMap Infinity

const h = options?.heuristic ?? (() => 1);

gScore.set(options.start, 0);
dMap.set(options.start, 0);

const fScore = new Map<N, number>(); // Infinity
fScore.set(options.start, h(options.start, []));

while (openSet.size > 0) {
const umin = [...openSet.values()].reduce(
(acc, b) => {
const u = fScore.get(b) ?? Number.POSITIVE_INFINITY;
if (!acc.node || u < acc.dist) {
acc.node = b;
acc.dist = fScore.get(b) ?? Number.POSITIVE_INFINITY;
}
return acc;
},
{ node: undefined as N | undefined, dist: Number.POSITIVE_INFINITY },
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const current = umin.node!;

openSet.delete(current);

for (const neighbour of options?.edgeGenerator?.(this.nodes, current, []) ?? current) {
const tentativegScore =
(gScore.get(current) ?? Number.POSITIVE_INFINITY) +
(options?.currentPathWeighter
? options.currentPathWeighter(
neighbour.from,
neighbour.to,
neighbour.direction,
[],
)
: neighbour.weight ?? 1);
const tentativeDistance = (dMap.get(current) ?? 0) + 1;
if (tentativegScore < (gScore.get(neighbour.to) ?? Number.POSITIVE_INFINITY)) {
cameFrom.set(neighbour.to, current);
gScore.set(neighbour.to, tentativegScore);
fScore.set(neighbour.to, tentativegScore);
dMap.set(neighbour.to, tentativeDistance);
if (!openSet.has(neighbour.to)) {
openSet.add(neighbour.to);
}
}
}
}

return dMap;
public flood(
options: Omit<GraphTraversalOptions<T, Dir, N>, 'end' | 'heuristic'>,
): Map<N, number> {
return aStar({ ...options, allNodes: this.nodes, end: undefined }).distances;
}

/**
Expand Down
28 changes: 19 additions & 9 deletions solutions/typescript/libs/lib/src/pathfinding/astar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,19 @@ import {
type PathFindingResult,
} from './dijkstra.js';

/**
* Finds the shortest path between two nodes in a graph. The nodes must be aware
* of their own neighbours.
*
* When an end is not defined the algorithm will travel though the reachable
* parts of the graph from the start and calculate the distance to
* each nodes from the start.
*/
export const aStar = <T extends ToString, Dir extends ToString, N extends BasicGraphNode<T, Dir>>(
options: GraphTraversalOptions<T, Dir, N> &
Omit<EdgeCollectionOptions<T, Dir, N>, 'pathConstructor'>,
): PathFindingResult<N> => {
if (!options.start || !options.end) {
if (!options.start) {
return { path: [], distances: new Map() };
}

Expand All @@ -28,7 +36,7 @@ export const aStar = <T extends ToString, Dir extends ToString, N extends BasicG
const pathLengthMap = new Map<N, number>(); // How many nodes there are to reach the end

gScore.set(options.start, 0);
fScore.set(options.start, h(options.start, []));
fScore.set(options.start, options.end ? h(options.start, []) : 1);
pathLengthMap.set(options.start, 0);
// This is used to support weights of 0
const orderOfDiscovery = [options.start];
Expand All @@ -51,7 +59,6 @@ export const aStar = <T extends ToString, Dir extends ToString, N extends BasicG
return aScore - bScore;
}
});

const pathConstructor: PathConstructor<N> = (to) => constructPath<N>(options.start, to, prev);

const isFinished = isNotNullish(options.end)
Expand All @@ -66,7 +73,6 @@ export const aStar = <T extends ToString, Dir extends ToString, N extends BasicG
while (pq.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const current = pq.pop()!; // u, closest yet

orderOfDiscovery.removeItem(current);

if (isFinished(current)) {
Expand All @@ -90,12 +96,16 @@ export const aStar = <T extends ToString, Dir extends ToString, N extends BasicG
const tentativegScore = uDist + weight;
const currentScore = gScore.get(neighbour.to) ?? Number.POSITIVE_INFINITY;
if (tentativegScore < currentScore) {
const currentPath = constructPath<N>(options.start, current, prev);

prev.set(neighbour.to, current);
gScore.set(neighbour.to, tentativegScore);
fScore.set(neighbour.to, tentativegScore + h(neighbour.to, currentPath));
pathLengthMap.set(neighbour.to, (pathLengthMap.get(neighbour.to) ?? 0) + 1);
fScore.set(
neighbour.to,
tentativegScore +
(options.end
? h(neighbour.to, constructPath<N>(options.start, current, prev))
: 1),
);
pathLengthMap.set(neighbour.to, (pathLengthMap.get(current) ?? 0) + 1);

if (!pq.updateItem(neighbour.to)) {
pq.push(neighbour.to);
Expand All @@ -106,7 +116,7 @@ export const aStar = <T extends ToString, Dir extends ToString, N extends BasicG
}

return {
path: constructPath<N>(options.start, goal, prev),
path: goal ? constructPath<N>(options.start, goal, prev) : [],
distances: pathLengthMap,
};
};
13 changes: 4 additions & 9 deletions solutions/typescript/libs/lib/src/pathfinding/dijkstra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,8 @@ export const dijkstra = <
}
}

return target
? {
distances: pathLengthMap,
path: constructPath(options.start, target, prev),
}
: {
distances: pathLengthMap,
path: [],
};
return {
distances: pathLengthMap,
path: target ? constructPath(options.start, target, prev) : [],
};
};

0 comments on commit 2875275

Please sign in to comment.