@@ -16,6 +16,16 @@ interface IPath {
1616 readonly weight : number ;
1717}
1818
19+ interface NodeBag {
20+ readonly nodes : number ;
21+ }
22+
23+ function checkWeight ( weight : number ) : void {
24+ if ( weight < 0 ) {
25+ throw new RangeError ( "weight must be >= 0" ) ;
26+ }
27+ }
28+
1929/**
2030 * Implementation of Dijkstra's Shortest Path algorithm. This quickly finds the shortest path between a single point
2131 * and every other point it it connected to.
@@ -31,7 +41,11 @@ interface IPath {
3141 * as our priority queue. All map-likes have been eliminated, but there are still object references. So this is
3242 * not as fast as possible, but it should be plenty fast and not too heavy on memory.
3343 */
34- export class DijkstraShortestPathSolver {
44+ export class DijkstraShortestPathSolver implements NodeBag {
45+ private readonly startNodeChecker = new NodeIndexChecker ( this , "startNode" ) ;
46+ private readonly fromNodeChecker = new NodeIndexChecker ( this , "fromNode" ) ;
47+ private readonly toNodeChecker = new NodeIndexChecker ( this , "toNode" ) ;
48+
3549 private constructor (
3650 protected readonly adjacencyList : IEdge [ ] [ ] ,
3751 ) {
@@ -70,7 +84,7 @@ export class DijkstraShortestPathSolver {
7084 /**
7185 * The number of nodes in the graph. Nodes are numbered from `0` to `n-1`.
7286 */
73- protected get nodes ( ) : number {
87+ get nodes ( ) : number {
7488 return this . adjacencyList . length ;
7589 }
7690
@@ -98,21 +112,9 @@ export class DijkstraShortestPathSolver {
98112 * @param weight Weight of the edge. Must be greater than 0.
99113 */
100114 addEdge ( fromNode : number , toNode : number , weight : number ) : void {
101- if ( weight < 0 ) {
102- throw new RangeError ( "weight must be >= 0" ) ;
103- }
104-
105- if ( fromNode < 0 || fromNode >= this . nodes ) {
106- throw new RangeError (
107- `fromNode must be in range 0..${ this . nodes - 1 } : ${ fromNode } ` ,
108- ) ;
109- }
110-
111- if ( toNode < 0 || toNode >= this . nodes ) {
112- throw new RangeError (
113- `toNode must be in range 0..${ this . nodes - 1 } : ${ toNode } ` ,
114- ) ;
115- }
115+ checkWeight ( weight ) ;
116+ this . fromNodeChecker . check ( fromNode ) ;
117+ this . toNodeChecker . check ( toNode ) ;
116118
117119 this . adjacencyList [ fromNode ] . push ( { toNode, weight } ) ;
118120 }
@@ -124,41 +126,20 @@ export class DijkstraShortestPathSolver {
124126 * @param weight Weight of the edge. Must be greater than 0.
125127 */
126128 addBidirEdge ( fromNode : number , toNode : number , weight : number ) : void {
127- if ( weight < 0 ) {
128- throw new RangeError ( "weight must be >= 0" ) ;
129- }
130-
131- if ( fromNode < 0 || fromNode >= this . nodes ) {
132- throw new RangeError (
133- `fromNode must be in range 0..${ this . nodes - 1 } : ${ fromNode } ` ,
134- ) ;
135- }
136-
137- if ( toNode < 0 || toNode >= this . nodes ) {
138- throw new RangeError (
139- `toNode must be in range 0..${ this . nodes - 1 } : ${ toNode } ` ,
140- ) ;
141- }
129+ checkWeight ( weight ) ;
130+ this . fromNodeChecker . check ( fromNode ) ;
131+ this . toNodeChecker . check ( toNode ) ;
142132
143133 this . adjacencyList [ fromNode ] . push ( { toNode, weight } ) ;
144134 this . adjacencyList [ toNode ] . push ( { toNode : fromNode , weight } ) ;
145135 }
146136
147- // TODO: Not ready for general consumption.
148- // setEdges(node: number, edges: IEdge[]): void {
149- // this.adjacencyList[node] = edges;
150- // }
151-
152137 /**
153138 * Calculate shortest paths for all nodes for the given start node.
154139 * @param startNode The start node.
155140 */
156141 calculateFor ( startNode : number ) : ShortestPaths {
157- if ( startNode < 0 || startNode >= this . nodes ) {
158- throw new RangeError (
159- `startNode must be in range 0..${ this . nodes - 1 } : ${ startNode } ` ,
160- ) ;
161- }
142+ this . startNodeChecker . check ( startNode ) ;
162143
163144 const weights : number [ ] = new Array ( this . nodes ) . fill ( Infinity ) ;
164145 weights [ startNode ] = 0 ;
@@ -193,7 +174,9 @@ export class DijkstraShortestPathSolver {
193174/**
194175 * Shortest paths result.
195176 */
196- export class ShortestPaths {
177+ export class ShortestPaths implements NodeBag {
178+ private readonly endNodeChecker = new NodeIndexChecker ( this , "endNode" ) ;
179+
197180 constructor (
198181 public readonly nodes : number ,
199182 public readonly startNode : number ,
@@ -207,11 +190,7 @@ export class ShortestPaths {
207190 * @throws {@link Error } No path found.
208191 */
209192 shortestPathTo ( endNode : number ) : number [ ] {
210- if ( endNode < 0 || endNode >= this . nodes ) {
211- throw new RangeError (
212- `end-node must be in range 0 to ${ this . nodes - 1 } : ${ endNode } ` ,
213- ) ;
214- }
193+ this . endNodeChecker . check ( endNode ) ;
215194
216195 const path = [ endNode ] ;
217196 let lastStep = endNode ;
@@ -232,12 +211,35 @@ export class ShortestPaths {
232211 * @param endNode The end node.
233212 */
234213 weightOfPathTo ( endNode : number ) : number {
235- if ( endNode < 0 || endNode >= this . nodes ) {
214+ this . endNodeChecker . check ( endNode ) ;
215+
216+ return this . weights [ endNode ] ;
217+ }
218+ }
219+
220+ /**
221+ * Simple range check for various node index inputs.
222+ */
223+ class NodeIndexChecker implements NodeBag {
224+ constructor (
225+ private readonly nodeBag : NodeBag ,
226+ private readonly label : string ,
227+ ) {
228+ }
229+
230+ get nodes ( ) : number {
231+ return this . nodeBag . nodes ;
232+ }
233+
234+ check ( index : number ) : void {
235+ if ( ! Number . isInteger ( index ) ) {
236+ throw new RangeError ( `${ this . label } must be an integer: ${ index } ` ) ;
237+ }
238+
239+ if ( index < 0 || index >= this . nodes ) {
236240 throw new RangeError (
237- `end-node must be in range 0 to ${ this . nodes - 1 } : ${ endNode } ` ,
241+ `${ this . label } must be in range 0.. ${ this . nodes - 1 } : ${ index } ` ,
238242 ) ;
239243 }
240-
241- return this . weights [ endNode ] ;
242244 }
243245}
0 commit comments