Skip to content

Commit 423d284

Browse files
authored
Merge pull request #107 from ravann/master
weightFunction to calculate weight of path ...
2 parents e1b0e1e + 3113074 commit 423d284

File tree

7 files changed

+157
-9
lines changed

7 files changed

+157
-9
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,23 @@ console.log(result.nodes); // Prints the array of nodes in the shortest path
196196
console.log(result.weight); // Prints the total weight of the path
197197
```
198198

199+
<a name="shortest-path" href="#shortest-path">#</a> <b>shortestPath</b>(<i>graph</i>, <i>sourceNode</i>, <i>destinationNode</i>, <i>nextWeightFn</i>)
200+
201+
Calculates the weight based on the custom function.
202+
203+
```javascript
204+
import type { NextWeightFnParams } from '../../types.js';
205+
function multiplyWeightFunction(wp: NextWeightFnParams): number {
206+
if (wp.currentPathWeight === undefined) {
207+
return wp.edgeWeight;
208+
}
209+
return wp.edgeWeight * wp.currentPathWeight;
210+
}
211+
var result = shortestPath(graph, 'a', 'c', multiplyWeightFunction);
212+
console.log(result.nodes); // Prints the array of nodes in the shortest path
213+
console.log(result.weight); // Prints the total weight of the path
214+
```
215+
199216
<p align="center">
200217
<a href="https://datavis.tech/">
201218
<img src="https://cloud.githubusercontent.com/assets/68416/15298394/a7a0a66a-1bbc-11e6-9636-367bed9165fc.png">

src/algorithms/shortestPath/getPath.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
import type { EdgeWeight, NoInfer } from '../../types.js';
22
import type { TraversingTracks } from './types.js';
3+
import type { NextWeightFnParams } from '../../types.js';
34

45
import { Graph } from '../../Graph.js';
56

7+
/**
8+
* Computes edge weight as the sum of all the edges in the path.
9+
*/
10+
export function addWeightFunction( wp: NextWeightFnParams): number {
11+
if (wp.currentPathWeight === undefined) {
12+
return wp.edgeWeight;
13+
}
14+
return wp.edgeWeight + wp.currentPathWeight;
15+
}
16+
617
/**
718
* Assembles the shortest path by traversing the
819
* predecessor subgraph from destination to source.
@@ -12,22 +23,30 @@ export function getPath<Node, LinkProps>(
1223
tracks: TraversingTracks<NoInfer<Node>>,
1324
source: NoInfer<Node>,
1425
destination: NoInfer<Node>,
26+
nextWeightFn: (params: NextWeightFnParams) => number = addWeightFunction
1527
): {
1628
nodes: [Node, Node, ...Node[]];
17-
weight: number;
29+
weight: number | undefined;
1830
} {
1931
const { p } = tracks;
2032
const nodeList: Node[] & { weight?: EdgeWeight } = [];
2133

22-
let totalWeight = 0;
34+
let totalWeight : EdgeWeight | undefined = undefined;
2335
let node = destination;
2436

37+
let hop = 1;
2538
while (p.has(node)) {
2639
const currentNode = p.get(node)!;
2740

2841
nodeList.push(node);
29-
totalWeight += graph.getEdgeWeight(currentNode, node);
42+
const edgeWeight = graph.getEdgeWeight(currentNode, node)
43+
totalWeight = nextWeightFn({
44+
edgeWeight, currentPathWeight: totalWeight,
45+
hop: hop, graph: graph, path: tracks,
46+
previousNode: node, currentNode: currentNode
47+
});
3048
node = currentNode;
49+
hop++;
3150
}
3251

3352
if (node !== source) {

src/algorithms/shortestPath/shortestPath.spec.ts

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { describe, expect, it } from 'vitest';
1+
import { describe, expect, it, vi } from 'vitest';
22
import { Graph } from '../../Graph.js';
33
import { serializeGraph } from '../../utils/serializeGraph.js';
44
import { shortestPath } from './shortestPath.js';
55
import { shortestPaths } from './shortestPaths.js';
6+
import { addWeightFunction } from './getPath.js';
7+
import { NextWeightFnParams } from '../../types.js';
68

79
describe("Dijkstra's Shortest Path Algorithm", function () {
810
it('Should compute shortest path on a single edge.', function () {
@@ -104,3 +106,98 @@ describe("Dijkstra's Shortest Path Algorithm", function () {
104106
expect(postSerializedGraph.links).toContainEqual({ source: 'f', target: 'c' });
105107
});
106108
});
109+
110+
describe('addWeightFunction', () => {
111+
it('should return edgeWeight if currentPathWeight is undefined', () => {
112+
const graph = new Graph();
113+
const params = {
114+
edgeWeight: 5, currentPathWeight: undefined, hop: 1,
115+
graph: graph, path: { d: new Map(), p: new Map(), q: new Set() },
116+
previousNode: 'a', currentNode: 'b'
117+
};
118+
expect(addWeightFunction(params)).toBe(5);
119+
});
120+
121+
it('should return the sum of edgeWeight and currentPathWeight', () => {
122+
const graph = new Graph()
123+
const params = { edgeWeight: 5, currentPathWeight: 10, hop: 1,
124+
graph: graph, path: { d: new Map(), p: new Map(), q: new Set() },
125+
previousNode: 'a', currentNode: 'b'
126+
};
127+
expect(addWeightFunction(params)).toBe(15);
128+
});
129+
});
130+
131+
describe('shortestPath with custom weight functions', () => {
132+
it('should compute shortest path with default weight function (sum of weights)', () => {
133+
const graph = new Graph().addEdge('a', 'b', 1).addEdge('b', 'c', 2);
134+
expect(shortestPath(graph, 'a', 'c')).toEqual({
135+
nodes: ['a', 'b', 'c'],
136+
weight: 3,
137+
});
138+
});
139+
140+
it('should compute shortest path with a custom weight function', () => {
141+
const customWeightFn = ({ edgeWeight, currentPathWeight, hop }: NextWeightFnParams) => {
142+
if (currentPathWeight === undefined) {
143+
return edgeWeight;
144+
}
145+
return currentPathWeight + edgeWeight ** hop;
146+
};
147+
148+
const graph = new Graph().addEdge('a', 'b', 2).addEdge('b', 'c', 3);
149+
expect(shortestPath(graph, 'a', 'c', customWeightFn)).toEqual({
150+
nodes: ['a', 'b', 'c'],
151+
weight: 7,
152+
});
153+
});
154+
155+
it('should pass correct parameters to custom weight function for a path with 3 nodes', () => {
156+
const customWeightFn = vi.fn(({ edgeWeight, currentPathWeight, hop }: NextWeightFnParams) => {
157+
if (currentPathWeight === undefined) {
158+
return edgeWeight;
159+
}
160+
return currentPathWeight + edgeWeight ** hop;
161+
});
162+
163+
const graph = new Graph().addEdge('a', 'b', 1).addEdge('b', 'c', 2);
164+
shortestPath(graph, 'a', 'c', customWeightFn);
165+
166+
expect(customWeightFn).toHaveBeenCalledWith({ edgeWeight: 2, currentPathWeight: undefined, hop: 1,
167+
graph: graph, currentNode: 'b', previousNode: 'c',
168+
path: {
169+
d: new Map([['a', 0], ['b', 1], ['c', 3]]),
170+
p: new Map([['b', 'a'], ['c', 'b']]),
171+
q: new Set(),
172+
},
173+
});
174+
expect(customWeightFn).toHaveBeenCalledWith({ edgeWeight: 1, currentPathWeight: 2, hop: 2,
175+
graph: graph, currentNode: 'a', previousNode: 'b',
176+
path: {
177+
d: new Map([['a', 0], ['b', 1], ['c', 3]]),
178+
p: new Map([['b', 'a'], ['c', 'b']]),
179+
q: new Set(),
180+
}
181+
});
182+
});
183+
184+
it('should compute shortest path with a custom weight function in a graph with multiple paths', () => {
185+
const customWeightFn = ({ edgeWeight, currentPathWeight }: NextWeightFnParams) => {
186+
if (currentPathWeight === undefined) {
187+
return edgeWeight;
188+
}
189+
return edgeWeight + currentPathWeight;
190+
};
191+
192+
const graph = new Graph()
193+
.addEdge('a', 'b', 1)
194+
.addEdge('b', 'c', 2)
195+
.addEdge('a', 'd', 1)
196+
.addEdge('d', 'c', 1);
197+
198+
expect(shortestPath(graph, 'a', 'c', customWeightFn)).toEqual({
199+
nodes: ['a', 'd', 'c'],
200+
weight: 2,
201+
});
202+
});
203+
});

src/algorithms/shortestPath/shortestPath.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Graph } from '../../Graph.js';
22
import { NoInfer } from '../../types.js';
33
import { dijkstra } from './dijkstra.js';
4-
import { getPath } from './getPath.js';
4+
import { getPath, addWeightFunction } from './getPath.js';
55
import { TraversingTracks } from './types.js';
6+
import type { NextWeightFnParams } from '../../types.js';
67

78
/**
89
* Dijkstra's Shortest Path Algorithm.
@@ -13,9 +14,10 @@ export function shortestPath<Node, LinkProps>(
1314
graph: Graph<Node, LinkProps>,
1415
source: NoInfer<Node>,
1516
destination: NoInfer<Node>,
17+
nextWeightFn: (params: NextWeightFnParams) => number = addWeightFunction
1618
): {
1719
nodes: [Node, Node, ...Node[]];
18-
weight: number;
20+
weight: number | undefined;
1921
} {
2022
const tracks: TraversingTracks<Node> = {
2123
d: new Map(),
@@ -25,5 +27,5 @@ export function shortestPath<Node, LinkProps>(
2527

2628
dijkstra(graph, tracks, source, destination);
2729

28-
return getPath(graph, tracks, source, destination);
30+
return getPath(graph, tracks, source, destination, nextWeightFn);
2931
}

src/algorithms/shortestPath/shortestPaths.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function shortestPaths<Node, LinkProps>(
4141

4242
try {
4343
path = shortestPath(graph, source, destination);
44-
if (!path.weight || pathWeight < path.weight) break;
44+
if (!path.weight || !pathWeight || pathWeight < path.weight) break;
4545
paths.push(path);
4646
} catch (e) {
4747
break;

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type { Edge, Serialized, SerializedInput, EdgeWeight } from './types.js';
1+
export type { Edge, Serialized, SerializedInput, EdgeWeight, NextWeightFnParams } from './types.js';
22

33
export { Graph } from './Graph.js';
44
export { CycleError } from './CycleError.js';

src/types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { TraversingTracks } from './algorithms/shortestPath/types.js';
2+
import { Graph } from './Graph.js';
3+
14
export type EdgeWeight = number;
25

36
export type Edge<NodeIdentity = unknown, Props = unknown> = {
@@ -18,3 +21,13 @@ export type SerializedInput<Node = unknown, LinkProps = unknown> = {
1821
};
1922

2023
export type NoInfer<T> = [T][T extends any ? 0 : never];
24+
25+
export type NextWeightFnParams<Node = unknown, LinkProps = unknown> = {
26+
edgeWeight: EdgeWeight;
27+
currentPathWeight: EdgeWeight | undefined;
28+
hop: number;
29+
graph: Graph<Node, LinkProps>;
30+
path: TraversingTracks<NoInfer<Node>>;
31+
previousNode: NoInfer<Node>;
32+
currentNode: NoInfer<Node>;
33+
};

0 commit comments

Comments
 (0)