main.js (28230B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2021 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 /* eslint-disable no-restricted-syntax, no-invalid-this */ 20 21 'use strict'; 22 23 // MODULES // 24 25 var isNonNegativeInteger = require( '@stdlib/assert/is-nonnegative-integer' ).isPrimitive; 26 var isArrayLikeObject = require( '@stdlib/assert/is-array-like-object' ); 27 var isCollection = require( '@stdlib/assert/is-collection' ); 28 var isFunction = require( '@stdlib/assert/is-function' ); 29 var isObject = require( '@stdlib/assert/is-object' ); 30 var hasIteratorSymbolSupport = require( '@stdlib/assert/has-iterator-symbol-support' ); 31 var ITERATOR_SYMBOL = require( '@stdlib/symbol/iterator' ); 32 var setReadOnly = require( './../../define-nonenumerable-read-only-property' ); 33 var setReadOnlyAccessor = require( './../../define-nonenumerable-read-only-accessor' ); 34 var Int32Array = require( '@stdlib/array/int32' ); 35 var Int8Array = require( '@stdlib/array/int8' ); 36 var ceil = require( '@stdlib/math/base/special/ceil' ); 37 var floor = require( '@stdlib/math/base/special/floor' ); 38 var grev = require( '@stdlib/blas/ext/base/grev' ); 39 var fromIteratorAdjList = require( './from_adjacency_list_iterator.js' ); 40 var fromIteratorAdjListMap = require( './from_adjacency_list_iterator_map.js' ); 41 var fromIteratorEdges = require( './from_edges_iterator.js' ); 42 var fromIteratorEdgesMap = require( './from_edges_iterator_map.js' ); 43 var setBit = require( './set_bit.js' ); 44 var clearBit = require( './clear_bit.js' ); 45 var isSet = require( './is_set.js' ); 46 var bitValue = require( './bit_value.js' ); 47 48 49 // VARIABLES // 50 51 var HAS_ITERATOR_SYMBOL = hasIteratorSymbolSupport(); 52 var NBITS = Int32Array.BYTES_PER_ELEMENT * 8; // 8 bits per byte 53 54 55 // MAIN // 56 57 /** 58 * Compact adjacency matrix constructor. 59 * 60 * @constructor 61 * @param {NonNegativeInteger} N - number of vertices 62 * @throws {TypeError} must provide a nonnegative integer 63 * @returns {CompactAdjacencyMatrix} adjacency matrix instance 64 * 65 * @example 66 * var adj = new CompactAdjacencyMatrix( 4 ); 67 * // returns <CompactAdjacencyMatrix> 68 * 69 * adj.addEdge( 0, 1 ); 70 * adj.addEdge( 0, 2 ); 71 * adj.addEdge( 1, 2 ); 72 * adj.addEdge( 2, 3 ); 73 */ 74 function CompactAdjacencyMatrix( N ) { 75 if ( !( this instanceof CompactAdjacencyMatrix ) ) { 76 return new CompactAdjacencyMatrix( N ); 77 } 78 if ( !isNonNegativeInteger( N ) ) { 79 throw new TypeError( 'invalid argument. Must provide a nonnegative integer. Value: `' + N + '`.' ); 80 } 81 this._N = N; // number of vertices 82 this._M = 0; // number of edges 83 this._buffer = new Int32Array( ceil( N*N/NBITS ) ); // square matrix 84 return this; 85 } 86 87 /** 88 * Creates a compact adjacency matrix from an adjacency list. 89 * 90 * @name fromAdjacencyList 91 * @memberof CompactAdjacencyMatrix 92 * @type {Function} 93 * @param {(ArrayLikeObject|Iterable)} list - adjacency list 94 * @param {Function} [clbk] - callback to invoke for each list element 95 * @param {*} [thisArg] - context 96 * @throws {TypeError} `this` context must be a constructor 97 * @throws {TypeError} `this` must be a compact adjacency matrix 98 * @throws {TypeError} first argument must be an array-like object or an iterable 99 * @throws {TypeError} second argument must be a function 100 * @throws {TypeError} each element of a provided adjacency list must be an array-like object 101 * @throws {TypeError} an iterator must return an array-like object containing vertices 102 * @throws {TypeError} when provided an iterator, a callback must return an array-like object containing vertices 103 * @returns {CompactAdjacencyMatrix} adjacency matrix instance 104 * 105 * @example 106 * var list = [ [ 1, 2 ], [ 2 ], [ 3 ], [] ]; 107 * 108 * var adj = CompactAdjacencyMatrix.fromAdjacencyList( list ); 109 * // returns <CompactAdjacencyMatrix> 110 * 111 * var bool = adj.hasEdge( 0, 1 ); 112 * // returns true 113 * 114 * bool = adj.hasEdge( 0, 2 ); 115 * // returns true 116 * 117 * bool = adj.hasEdge( 1, 2 ); 118 * // returns true 119 * 120 * bool = adj.hasEdge( 2, 3 ); 121 * // returns true 122 */ 123 setReadOnly( CompactAdjacencyMatrix, 'fromAdjacencyList', function fromAdjacencyList( list ) { 124 var thisArg; 125 var nargs; 126 var edges; 127 var clbk; 128 var adj; 129 var tmp; 130 var len; 131 var N; 132 var i; 133 var j; 134 if ( !isFunction( this ) ) { 135 throw new TypeError( 'invalid invocation. `this` context must be a constructor.' ); 136 } 137 if ( this !== CompactAdjacencyMatrix ) { 138 throw new TypeError( 'invalid invocation. `this` is not a compact adjacency matrix.' ); 139 } 140 nargs = arguments.length; 141 if ( nargs > 1 ) { 142 clbk = arguments[ 1 ]; 143 if ( !isFunction( clbk ) ) { 144 throw new TypeError( 'invalid argument. Second argument must be a function. Value: `'+clbk+'`.' ); 145 } 146 if ( nargs > 2 ) { 147 thisArg = arguments[ 2 ]; 148 } 149 } 150 if ( isArrayLikeObject( list ) ) { 151 N = list.length; 152 adj = new this( N ); 153 if ( clbk ) { 154 for ( i = 0; i < N; i++ ) { 155 edges = clbk.call( thisArg, list[ i ], i ); 156 if ( !isCollection( edges ) ) { 157 throw new TypeError( 'invalid argument. Callback must return an array-like object. Value: `' + edges + '`.' ); 158 } 159 for ( j = 0; j < edges.length; j++ ) { 160 adj.addEdge( i, edges[ j ] ); 161 } 162 } 163 return adj; 164 } 165 for ( i = 0; i < N; i++ ) { 166 edges = list[ i ]; 167 if ( !isCollection( edges ) ) { 168 throw new TypeError( 'invalid argument. Each element of the adjacency list must be an array-like object. Value: `' + list + '`.' ); 169 } 170 for ( j = 0; j < edges.length; j++ ) { 171 adj.addEdge( i, edges[ j ] ); 172 } 173 } 174 return adj; 175 } 176 if ( isObject( list ) && HAS_ITERATOR_SYMBOL && isFunction( list[ ITERATOR_SYMBOL ] ) ) { // eslint-disable-line max-len 177 tmp = list[ ITERATOR_SYMBOL ](); 178 if ( !isFunction( tmp.next ) ) { 179 throw new TypeError( 'invalid argument. First argument must be an array-like object or an iterable.' ); 180 } 181 if ( clbk ) { 182 tmp = fromIteratorAdjListMap( tmp, clbk, thisArg ); 183 } else { 184 tmp = fromIteratorAdjList( tmp ); 185 } 186 if ( tmp instanceof Error ) { 187 throw tmp; 188 } 189 len = tmp.length; 190 adj = new this( len ); 191 for ( i = 0; i < len; i++ ) { 192 edges = tmp[ i ]; 193 for ( j = 0; j < edges.length; j++ ) { 194 adj.addEdge( i, edges[ j ] ); 195 } 196 } 197 return adj; 198 } 199 throw new TypeError( 'invalid argument. First argument must be an array-like object or an iterable. Value: `' + list + '`.' ); 200 }); 201 202 /** 203 * Creates a compact adjacency matrix from a list of edges. 204 * 205 * @name fromEdges 206 * @memberof CompactAdjacencyMatrix 207 * @type {Function} 208 * @param {NonNegativeInteger} N - number of vertices 209 * @param {(ArrayLikeObject|Iterable)} edges - list of edges 210 * @param {Function} [clbk] - callback to invoke for each list element 211 * @param {*} [thisArg] - context 212 * @throws {TypeError} `this` context must be a constructor 213 * @throws {TypeError} `this` must be a compact adjacency matrix 214 * @throws {TypeError} first argument must be a nonnegative integer 215 * @throws {TypeError} second argument must be an array-like object 216 * @throws {TypeError} third argument must be a function 217 * @throws {TypeError} each element of a provided list of edges must be a two-element array-like object containing vertices 218 * @throws {TypeError} an iterator must return a two-element array-like object containing vertices 219 * @throws {TypeError} when provided an iterator, a callback must return a two-element array-like object containing vertices 220 * @returns {CompactAdjacencyMatrix} adjacency matrix instance 221 * 222 * @example 223 * var edges = [ [ 0, 1 ], [ 0, 2 ], [ 1, 2 ], [ 2, 3 ] ]; 224 * 225 * var adj = CompactAdjacencyMatrix.fromEdges( 4, edges ); 226 * // returns <CompactAdjacencyMatrix> 227 * 228 * var bool = adj.hasEdge( 0, 1 ); 229 * // returns true 230 * 231 * bool = adj.hasEdge( 0, 2 ); 232 * // returns true 233 * 234 * bool = adj.hasEdge( 1, 2 ); 235 * // returns true 236 * 237 * bool = adj.hasEdge( 2, 3 ); 238 * // returns true 239 */ 240 setReadOnly( CompactAdjacencyMatrix, 'fromEdges', function fromEdges( N, edges ) { 241 var thisArg; 242 var nargs; 243 var clbk; 244 var edge; 245 var adj; 246 var tmp; 247 var len; 248 var i; 249 if ( !isFunction( this ) ) { 250 throw new TypeError( 'invalid invocation. `this` context must be a constructor.' ); 251 } 252 if ( this !== CompactAdjacencyMatrix ) { 253 throw new TypeError( 'invalid invocation. `this` is not a compact adjacency matrix.' ); 254 } 255 nargs = arguments.length; 256 if ( nargs > 2 ) { 257 clbk = arguments[ 2 ]; 258 if ( !isFunction( clbk ) ) { 259 throw new TypeError( 'invalid argument. Third argument must be a function. Value: `'+clbk+'`.' ); 260 } 261 if ( nargs > 3 ) { 262 thisArg = arguments[ 3 ]; 263 } 264 } 265 if ( !isNonNegativeInteger( N ) ) { 266 throw new TypeError( 'invalid argument. First argument must be a nonnegative integer. Value: `' + N + '`.' ); 267 } 268 if ( isArrayLikeObject( edges ) ) { 269 if ( clbk ) { 270 adj = new this( N ); 271 for ( i = 0; i < edges.length; i++ ) { 272 edge = clbk.call( thisArg, edges[ i ], i ); 273 if ( !isArrayLikeObject( edge ) ) { 274 throw new TypeError( 'invalid argument. Callback must return an array-like object. Value: `' + edge + '`.' ); 275 } 276 adj.addEdge( edge[ 0 ], edge[ 1 ] ); 277 } 278 return adj; 279 } 280 adj = new this( N ); 281 for ( i = 0; i < edges.length; i++ ) { 282 edge = edges[ i ]; 283 if ( !isArrayLikeObject( edge ) ) { 284 throw new TypeError( 'invalid argument. Each element of the edge list must be an array-like object. Value: `' + edge + '`.' ); 285 } 286 adj.addEdge( edge[ 0 ], edge[ 1 ] ); 287 } 288 return adj; 289 } 290 291 if ( isObject( edges ) && HAS_ITERATOR_SYMBOL && isFunction( edges[ ITERATOR_SYMBOL ] ) ) { // eslint-disable-line max-len 292 tmp = edges[ ITERATOR_SYMBOL ](); 293 if ( !isFunction( tmp.next ) ) { 294 throw new TypeError( 'invalid argument. First argument must be an array-like object or an iterable.' ); 295 } 296 if ( clbk ) { 297 tmp = fromIteratorEdgesMap( tmp, clbk, thisArg ); 298 } else { 299 tmp = fromIteratorEdges( tmp ); 300 } 301 if ( tmp instanceof Error ) { 302 throw tmp; 303 } 304 len = tmp.length; 305 adj = new this( len/2 ); 306 for ( i = 0; i < len; i += 2 ) { 307 adj.addEdge( tmp[ i ], tmp[ i+1 ] ); 308 } 309 return adj; 310 } 311 throw new TypeError( 'invalid argument. Second argument must be an array-like object or an iterable. Value: `' + edges + '`.' ); 312 }); 313 314 /** 315 * Returns indices ("bucket" and bit offset) for an `(i,j)` vertex pair. 316 * 317 * @private 318 * @name _loc 319 * @memberof CompactAdjacencyMatrix.prototype 320 * @type {Function} 321 * @param {NonNegativeInteger} i - starting vertex 322 * @param {NonNegativeInteger} j - ending vertex 323 * @param {Array} out - output array 324 * @throws {TypeError} first argument must be a nonnegative integer 325 * @throws {TypeError} second argument must be a nonnegative integer 326 * @throws {RangeError} first argument must not exceed matrix dimensions 327 * @throws {RangeError} second argument must not exceed matrix dimensions 328 * @returns {Array} output array 329 */ 330 setReadOnly( CompactAdjacencyMatrix.prototype, '_loc', function loc( i, j, out ) { 331 var bucket; 332 var bit; 333 var idx; 334 335 // Compute a strided index for the desired bit: 336 idx = ( i*this._N ) + j; 337 338 // Compute the index of the buffer element (bucket) containing the bit: 339 bucket = floor( idx / NBITS ); 340 341 // Compute the bit offset: 342 bit = idx - ( bucket*NBITS ); 343 344 // Set the output values: 345 out[ 0 ] = bucket; 346 out[ 1 ] = bit; 347 348 return out; 349 }); 350 351 /** 352 * Adds a directed edge between two vertices. 353 * 354 * @name addEdge 355 * @memberof CompactAdjacencyMatrix.prototype 356 * @type {Function} 357 * @param {NonNegativeInteger} i - starting vertex 358 * @param {NonNegativeInteger} j - ending vertex 359 * @throws {TypeError} first argument must be a nonnegative integer 360 * @throws {TypeError} second argument must be a nonnegative integer 361 * @throws {RangeError} first argument must not exceed matrix dimensions 362 * @throws {RangeError} second argument must not exceed matrix dimensions 363 * @returns {CompactAdjacencyMatrix} adjacency matrix instance 364 * 365 * @example 366 * var adj = new CompactAdjacencyMatrix( 4 ); 367 * // returns <CompactAdjacencyMatrix> 368 * 369 * adj.addEdge( 0, 1 ); 370 * adj.addEdge( 0, 2 ); 371 * adj.addEdge( 1, 2 ); 372 * adj.addEdge( 2, 3 ); 373 */ 374 setReadOnly( CompactAdjacencyMatrix.prototype, 'addEdge', function addEdge( i, j ) { 375 var idx; 376 if ( !isNonNegativeInteger( i ) ) { 377 throw new TypeError( 'invalid argument. First argument must be a nonnegative integer. Value: `' + i + '`.' ); 378 } 379 if ( !isNonNegativeInteger( j ) ) { 380 throw new TypeError( 'invalid argument. Second argument must be a nonnegative integer. Value: `' + j + '`.' ); 381 } 382 if ( i >= this._N ) { 383 throw new RangeError( 'invalid argument. First argument exceeds matrix dimensions. Value: `' + i + '`.' ); 384 } 385 if ( j >= this._N ) { 386 throw new RangeError( 'invalid argument. Second argument exceeds matrix dimensions. Value: `' + j + '`.' ); 387 } 388 // Resolve the `(i,j)` pair: 389 idx = this._loc( i, j, [ 0, 0 ] ); 390 391 // Set the bit for the edge: 392 if ( isSet( this._buffer[ idx[0] ], idx[1] ) === false ) { 393 this._buffer[ idx[0] ] = setBit( this._buffer[ idx[0] ], idx[1] ); 394 this._M += 1; 395 } 396 return this; 397 }); 398 399 /** 400 * Returns the list of all edges. 401 * 402 * @name edges 403 * @memberof CompactAdjacencyMatrix.prototype 404 * @type {Array} 405 * 406 * @example 407 * var adj = new CompactAdjacencyMatrix( 4 ); 408 * // returns <CompactAdjacencyMatrix> 409 * 410 * adj.addEdge( 0, 1 ); 411 * adj.addEdge( 0, 2 ); 412 * adj.addEdge( 1, 2 ); 413 * adj.addEdge( 2, 3 ); 414 * 415 * var edges = adj.edges; 416 * // returns [ [ 0, 1 ], [ 0, 2 ], [ 1, 2 ], [ 2, 3 ] ] 417 */ 418 setReadOnlyAccessor( CompactAdjacencyMatrix.prototype, 'edges', function edges() { 419 var edges; 420 var idx; 421 var i; 422 var j; 423 424 edges = []; 425 idx = [ 0, 0 ]; 426 for ( i = 0; i < this._N; i++ ) { 427 for ( j = 0; j < this._N; j++ ) { 428 // Resolve the `(i,j)` pair: 429 idx = this._loc( i, j, idx ); 430 431 // Check for an edge: 432 if ( isSet( this._buffer[ idx[0] ], idx[1] ) ) { 433 edges.push( [ i, j ] ); 434 } 435 } 436 } 437 return edges; 438 }); 439 440 /** 441 * Checks whether a directed edge exists between two vertices. 442 * 443 * @name hasEdge 444 * @memberof CompactAdjacencyMatrix.prototype 445 * @type {Function} 446 * @param {NonNegativeInteger} i - starting vertex 447 * @param {NonNegativeInteger} j - ending vertex 448 * @throws {TypeError} first argument must be a nonnegative integer 449 * @throws {TypeError} second argument must be a nonnegative integer 450 * @throws {RangeError} first argument must not exceed matrix dimensions 451 * @throws {RangeError} second argument must not exceed matrix dimensions 452 * @returns {boolean} boolean indicating if an edge exists 453 * 454 * @example 455 * var adj = new CompactAdjacencyMatrix( 4 ); 456 * // returns <CompactAdjacencyMatrix> 457 * 458 * adj.addEdge( 0, 1 ); 459 * adj.addEdge( 0, 2 ); 460 * adj.addEdge( 1, 2 ); 461 * adj.addEdge( 2, 3 ); 462 * 463 * // ... 464 * 465 * var bool = adj.hasEdge( 0, 1 ); 466 * // returns true 467 * 468 * bool = adj.hasEdge( 0, 2 ); 469 * // returns true 470 * 471 * bool = adj.hasEdge( 1, 2 ); 472 * // returns true 473 * 474 * bool = adj.hasEdge( 2, 3 ); 475 * // returns true 476 * 477 * bool = adj.hasEdge( 1, 3 ); 478 * // returns false 479 */ 480 setReadOnly( CompactAdjacencyMatrix.prototype, 'hasEdge', function hasEdge( i, j ) { 481 var idx; 482 if ( !isNonNegativeInteger( i ) ) { 483 throw new TypeError( 'invalid argument. First argument must be a nonnegative integer. Value: `' + i + '`.' ); 484 } 485 if ( !isNonNegativeInteger( j ) ) { 486 throw new TypeError( 'invalid argument. Second argument must be a nonnegative integer. Value: `' + j + '`.' ); 487 } 488 if ( i >= this._N ) { 489 throw new RangeError( 'invalid argument. First argument exceeds matrix dimensions. Value: `' + i + '`.' ); 490 } 491 if ( j >= this._N ) { 492 throw new RangeError( 'invalid argument. Second argument exceeds matrix dimensions. Value: `' + j + '`.' ); 493 } 494 // Resolve the `(i,j)` pair: 495 idx = this._loc( i, j, [ 0, 0 ] ); 496 497 // Check for an edge: 498 return isSet( this._buffer[ idx[0] ], idx[1] ); 499 }); 500 501 /** 502 * Returns the indegree of a vertex (i.e., number of edges ending at a vertex). 503 * 504 * @name inDegree 505 * @memberof CompactAdjacencyMatrix.prototype 506 * @type {Function} 507 * @param {NonNegativeInteger} j - vertex 508 * @throws {TypeError} must provide a nonnegative integer 509 * @throws {RangeError} must not exceed matrix dimensions 510 * @returns {NonNegativeInteger} indegree 511 * 512 * @example 513 * var adj = new CompactAdjacencyMatrix( 4 ); 514 * // returns <CompactAdjacencyMatrix> 515 * 516 * adj.addEdge( 0, 1 ); 517 * adj.addEdge( 0, 2 ); 518 * adj.addEdge( 1, 2 ); 519 * adj.addEdge( 2, 3 ); 520 * 521 * var d = adj.inDegree( 2 ); 522 * // returns 2 523 * 524 * d = adj.inDegree( 3 ); 525 * // returns 1 526 */ 527 setReadOnly( CompactAdjacencyMatrix.prototype, 'inDegree', function inDegree( j ) { 528 var deg; 529 var idx; 530 var i; 531 if ( !isNonNegativeInteger( j ) ) { 532 throw new TypeError( 'invalid argument. Must provide a nonnegative integer. Value: `' + j + '`.' ); 533 } 534 if ( j >= this._N ) { 535 throw new RangeError( 'invalid argument. Input argument cannot exceed matrix dimensions. Value: `' + j + '`.' ); 536 } 537 // Iterate over the rows and add up the number of edges... 538 deg = 0; 539 idx = [ 0, 0 ]; 540 for ( i = 0; i < this._N; i++ ) { 541 // Resolve the `(i,j)` pair: 542 idx = this._loc( i, j, idx ); 543 544 // Check for an edge: 545 deg += bitValue( this._buffer[ idx[0] ], idx[1] ); 546 } 547 return deg; 548 }); 549 550 /** 551 * Returns a list of vertices having edges ending at a specified vertex. 552 * 553 * @name inEdges 554 * @memberof CompactAdjacencyMatrix.prototype 555 * @type {Function} 556 * @param {NonNegativeInteger} j - vertex 557 * @throws {TypeError} must provide a nonnegative integer 558 * @throws {RangeError} must not exceed matrix dimensions 559 * @returns {Array} list of vertices 560 * 561 * @example 562 * var adj = new CompactAdjacencyMatrix( 4 ); 563 * // returns <CompactAdjacencyMatrix> 564 * 565 * adj.addEdge( 0, 1 ); 566 * adj.addEdge( 0, 2 ); 567 * adj.addEdge( 1, 2 ); 568 * adj.addEdge( 2, 3 ); 569 * 570 * var e = adj.inEdges( 2 ); 571 * // returns [ 0, 1 ] 572 * 573 * e = adj.inEdges( 3 ); 574 * // returns [ 2 ] 575 */ 576 setReadOnly( CompactAdjacencyMatrix.prototype, 'inEdges', function inEdges( j ) { 577 var edges; 578 var idx; 579 var i; 580 if ( !isNonNegativeInteger( j ) ) { 581 throw new TypeError( 'invalid argument. Must provide a nonnegative integer. Value: `' + j + '`.' ); 582 } 583 if ( j >= this._N ) { 584 throw new RangeError( 'invalid argument. Input argument cannot exceed matrix dimensions. Value: `' + j + '`.' ); 585 } 586 // Iterate over the rows and retrieve edges... 587 edges = []; 588 idx = [ 0, 0 ]; 589 for ( i = 0; i < this._N; i++ ) { 590 // Resolve the `(i,j)` pair: 591 idx = this._loc( i, j, idx ); 592 593 // Check for an edge: 594 if ( isSet( this._buffer[ idx[0] ], idx[1] ) ) { 595 edges.push( i ); 596 } 597 } 598 return edges; 599 }); 600 601 /** 602 * Returns the total number of edges. 603 * 604 * @name nedges 605 * @memberof CompactAdjacencyMatrix.prototype 606 * @readonly 607 * @type {NonNegativeInteger} 608 * 609 * @example 610 * var adj = new CompactAdjacencyMatrix( 4 ); 611 * // returns <CompactAdjacencyMatrix> 612 * 613 * // ... 614 * 615 * adj.addEdge( 0, 1 ); 616 * adj.addEdge( 0, 2 ); 617 * adj.addEdge( 1, 2 ); 618 * 619 * // ... 620 * 621 * var M = adj.nedges; 622 * // returns 3 623 */ 624 setReadOnlyAccessor( CompactAdjacencyMatrix.prototype, 'nedges', function nedges() { 625 return this._M; 626 }); 627 628 /** 629 * Returns the number of vertices. 630 * 631 * @name nvertices 632 * @memberof CompactAdjacencyMatrix.prototype 633 * @readonly 634 * @type {NonNegativeInteger} 635 * 636 * @example 637 * var adj = new CompactAdjacencyMatrix( 4 ); 638 * // returns <CompactAdjacencyMatrix> 639 * 640 * // ... 641 * 642 * var N = adj.nvertices; 643 * // returns 4 644 */ 645 setReadOnlyAccessor( CompactAdjacencyMatrix.prototype, 'nvertices', function nvertices() { 646 return this._N; 647 }); 648 649 /** 650 * Returns the outdegree of a vertex (i.e., number of edges starting from a vertex). 651 * 652 * @name outDegree 653 * @memberof CompactAdjacencyMatrix.prototype 654 * @type {Function} 655 * @param {NonNegativeInteger} i - vertex 656 * @throws {TypeError} must provide a nonnegative integer 657 * @throws {RangeError} must not exceed matrix dimensions 658 * @returns {NonNegativeInteger} outdegree 659 * 660 * @example 661 * var adj = new CompactAdjacencyMatrix( 4 ); 662 * // returns <CompactAdjacencyMatrix> 663 * 664 * adj.addEdge( 0, 1 ); 665 * adj.addEdge( 0, 2 ); 666 * adj.addEdge( 1, 2 ); 667 * adj.addEdge( 2, 3 ); 668 * 669 * var d = adj.outDegree( 2 ); 670 * // returns 1 671 * 672 * d = adj.outDegree( 0 ); 673 * // returns 2 674 */ 675 setReadOnly( CompactAdjacencyMatrix.prototype, 'outDegree', function outDegree( i ) { 676 var deg; 677 var idx; 678 var j; 679 if ( !isNonNegativeInteger( i ) ) { 680 throw new TypeError( 'invalid argument. Must provide a nonnegative integer. Value: `' + i + '`.' ); 681 } 682 if ( i >= this._N ) { 683 throw new RangeError( 'invalid argument. Input argument cannot exceed matrix dimensions. Value: `' + i + '`.' ); 684 } 685 // Iterate over the columns and add up the number of edges... 686 deg = 0; 687 idx = [ 0, 0 ]; 688 for ( j = 0; j < this._N; j++ ) { 689 // Resolve the `(i,j)` pair: 690 idx = this._loc( i, j, idx ); 691 692 // Check for an edge: 693 deg += bitValue( this._buffer[ idx[0] ], idx[1] ); 694 } 695 return deg; 696 }); 697 698 /** 699 * Returns a list of vertices having edges starting at a specified vertex. 700 * 701 * @name outEdges 702 * @memberof CompactAdjacencyMatrix.prototype 703 * @type {Function} 704 * @param {NonNegativeInteger} i - vertex 705 * @throws {TypeError} must provide a nonnegative integer 706 * @throws {RangeError} must not exceed matrix dimensions 707 * @returns {Array} list of vertices 708 * 709 * @example 710 * var adj = new CompactAdjacencyMatrix( 4 ); 711 * // returns <CompactAdjacencyMatrix> 712 * 713 * adj.addEdge( 0, 1 ); 714 * adj.addEdge( 0, 2 ); 715 * adj.addEdge( 1, 2 ); 716 * adj.addEdge( 2, 3 ); 717 * 718 * var e = adj.outEdges( 2 ); 719 * // returns [ 3 ] 720 * 721 * e = adj.outEdges( 0 ); 722 * // returns [ 1, 2 ] 723 */ 724 setReadOnly( CompactAdjacencyMatrix.prototype, 'outEdges', function outEdges( i ) { 725 var edges; 726 var idx; 727 var j; 728 if ( !isNonNegativeInteger( i ) ) { 729 throw new TypeError( 'invalid argument. Must provide a nonnegative integer. Value: `' + i + '`.' ); 730 } 731 if ( i >= this._N ) { 732 throw new RangeError( 'invalid argument. Input argument cannot exceed matrix dimensions. Value: `' + i + '`.' ); 733 } 734 // Iterate over the rows and retrieve edges... 735 edges = []; 736 idx = [ 0, 0 ]; 737 for ( j = 0; j < this._N; j++ ) { 738 // Resolve the `(i,j)` pair: 739 idx = this._loc( i, j, idx ); 740 741 // Check for an edge: 742 if ( isSet( this._buffer[ idx[0] ], idx[1] ) ) { 743 edges.push( j ); 744 } 745 } 746 return edges; 747 }); 748 749 /** 750 * Removes a directed edge between two vertices. 751 * 752 * @name removeEdge 753 * @memberof CompactAdjacencyMatrix.prototype 754 * @type {Function} 755 * @param {NonNegativeInteger} i - starting vertex 756 * @param {NonNegativeInteger} j - ending vertex 757 * @throws {TypeError} first argument must be a nonnegative integer 758 * @throws {TypeError} second argument must be a nonnegative integer 759 * @throws {RangeError} first argument must not exceed matrix dimensions 760 * @throws {RangeError} second argument must not exceed matrix dimensions 761 * @returns {CompactAdjacencyMatrix} adjacency matrix instance 762 * 763 * @example 764 * var adj = new CompactAdjacencyMatrix( 4 ); 765 * // returns <CompactAdjacencyMatrix> 766 * 767 * adj.addEdge( 0, 1 ); 768 * adj.addEdge( 0, 2 ); 769 * adj.addEdge( 1, 2 ); 770 * adj.addEdge( 2, 3 ); 771 * 772 * // ... 773 * 774 * adj.removeEdge( 0, 1 ); 775 * adj.removeEdge( 0, 2 ); 776 * adj.removeEdge( 1, 2 ); 777 * adj.removeEdge( 2, 3 ); 778 */ 779 setReadOnly( CompactAdjacencyMatrix.prototype, 'removeEdge', function removeEdge( i, j ) { 780 var idx; 781 if ( !isNonNegativeInteger( i ) ) { 782 throw new TypeError( 'invalid argument. First argument must be a nonnegative integer. Value: `' + i + '`.' ); 783 } 784 if ( !isNonNegativeInteger( j ) ) { 785 throw new TypeError( 'invalid argument. Second argument must be a nonnegative integer. Value: `' + j + '`.' ); 786 } 787 if ( i >= this._N ) { 788 throw new RangeError( 'invalid argument. First argument exceeds matrix dimensions. Value: `' + i + '`.' ); 789 } 790 if ( j >= this._N ) { 791 throw new RangeError( 'invalid argument. Second argument exceeds matrix dimensions. Value: `' + j + '`.' ); 792 } 793 // Resolve the `(i,j)` pair: 794 idx = this._loc( i, j, [ 0, 0 ] ); 795 796 // Clear the bit for the edge: 797 if ( isSet( this._buffer[ idx[0] ], idx[1] ) ) { 798 this._buffer[ idx[0] ] = clearBit( this._buffer[ idx[0] ], idx[1] ); 799 this._M -= 1; 800 } 801 return this; 802 }); 803 804 /** 805 * Returns an adjacency list representation. 806 * 807 * @name toAdjacencyList 808 * @memberof CompactAdjacencyMatrix.prototype 809 * @type {Function} 810 * @returns {Array} adjacency list representation 811 * 812 * @example 813 * var adj = new CompactAdjacencyMatrix( 4 ); 814 * // returns <CompactAdjacencyMatrix> 815 * 816 * adj.addEdge( 0, 1 ); 817 * adj.addEdge( 0, 2 ); 818 * adj.addEdge( 1, 2 ); 819 * adj.addEdge( 2, 3 ); 820 * 821 * var list = adj.toAdjacencyList(); 822 * // returns [ [ 1, 2 ], [ 2 ], [ 3 ], [] ] 823 */ 824 setReadOnly( CompactAdjacencyMatrix.prototype, 'toAdjacencyList', function toAdjacencyList() { 825 var list; 826 var idx; 827 var tmp; 828 var i; 829 var j; 830 831 list = []; 832 idx = [ 0, 0 ]; 833 for ( i = 0; i < this._N; i++ ) { 834 tmp = []; 835 for ( j = 0; j < this._N; j++ ) { 836 // Resolve the `(i,j)` pair: 837 idx = this._loc( i, j, idx ); 838 839 // Check for an edge: 840 if ( isSet( this._buffer[ idx[0] ], idx[1] ) ) { 841 tmp.push( j ); 842 } 843 } 844 list.push( tmp ); 845 } 846 return list; 847 }); 848 849 /** 850 * Returns a topological ordering of the directed graph. 851 * 852 * ## Notes 853 * 854 * - The function returns a two-element array. 855 * - If the function is able to compute a topological ordering, the first array element is the topological ordering and the second element is `null`. 856 * - If a topological ordering cannot be achieved (e.g., due to the graph not being a directed acyclic graph (DAG)), the first array element is `null` and the second element is the first encountered cycle. 857 * 858 * @name toposort 859 * @memberof CompactAdjacencyMatrix.prototype 860 * @type {Function} 861 * @returns {Array} topological ordering 862 * 863 * @example 864 * var adj = new CompactAdjacencyMatrix( 4 ); 865 * // returns <CompactAdjacencyMatrix> 866 * 867 * adj.addEdge( 1, 0 ); 868 * adj.addEdge( 1, 2 ); 869 * adj.addEdge( 0, 2 ); 870 * adj.addEdge( 2, 3 ); 871 * 872 * var results = adj.toposort(); 873 * // returns <Array> 874 * 875 * var order = results[ 0 ]; 876 * // returns [ 1, 0, 2, 3 ] 877 * 878 * var cycle = results[ 1 ]; 879 * // returns null 880 */ 881 setReadOnly( CompactAdjacencyMatrix.prototype, 'toposort', function toposort() { 882 var marks; 883 var self; 884 var out; 885 var idx; 886 var err; 887 var N; 888 var s; 889 var i; 890 891 self = this; 892 N = this._N; 893 894 // Initialize an empty list that will contain the sorted vertices: 895 out = []; 896 897 // If the graph is empty, nothing to sort... 898 if ( this._N === 0 ) { 899 return [ out, null ]; 900 } 901 // Initialize an array for keeping track of whether a vertex has been "visited": 902 marks = new Int8Array( N ); 903 904 // Initialize a stack for keeping track of cycles: 905 s = []; 906 907 // Process vertices using depth-first-search... 908 idx = [ 0, 0 ]; 909 for ( i = 0; i < N; i++ ) { 910 if ( marks[ i ] === 0 ) { 911 err = visit( i ); 912 if ( err !== 0 ) { 913 // Found a cycle... 914 s.push( i ); 915 return [ null, s ]; 916 } 917 } 918 } 919 // Reverse the output array as the leaves were added first, followed the by the roots, via depth-first-search: 920 grev( out.length, out, 1 ); 921 922 return [ out, null ]; 923 924 /** 925 * Visits a graph vertex and follows edges until finding a leaf vertex (if one exists). 926 * 927 * ## Notes 928 * 929 * - If the function is able to successfully perform a depth-first-search, the functions returns `0`; otherwise, the function returns `-1` in the event of a cycle. 930 * 931 * @private 932 * @param {NonNegativeInteger} i - vertex 933 * @returns {integer} error code 934 */ 935 function visit( i ) { 936 var err; 937 var j; 938 939 // Check if we've already processed/visited this vertex... 940 if ( marks[ i ] === 2 ) { 941 return 0; 942 } 943 // Check if we've seen this vertex before and the vertex is still being processed... 944 if ( marks[ i ] === 1 ) { 945 // We've found a cycle... 946 return -1; 947 } 948 // Mark the current vertex as currently being processed: 949 marks[ i ] = 1; 950 951 // Follow all edges from the current vertex... 952 for ( j = 0; j < N; j++ ) { 953 idx = self._loc( i, j, idx ); // eslint-disable-line no-underscore-dangle 954 if ( isSet( self._buffer[ idx[0] ], idx[1] ) ) { // eslint-disable-line no-underscore-dangle 955 err = visit( j ); 956 if ( err !== 0 ) { 957 // This vertex is part of a cycle, so add to cycle stack... 958 s.push( j ); 959 return err; 960 } 961 } 962 } 963 // Mark the current vertex as processed: 964 marks[ i ] = 2; 965 966 // Add to the output array now that all subsequent vertices (relative to this vertex) in the graph have already been added to the output array: 967 out.push( i ); 968 969 return 0; 970 } 971 }); 972 973 974 // EXPORTS // 975 976 module.exports = CompactAdjacencyMatrix;