977 lines
28 KiB
JavaScript
977 lines
28 KiB
JavaScript
/**
|
|
* @license Apache-2.0
|
|
*
|
|
* Copyright (c) 2021 The Stdlib Authors.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/* eslint-disable no-restricted-syntax, no-invalid-this */
|
|
|
|
'use strict';
|
|
|
|
// MODULES //
|
|
|
|
var isNonNegativeInteger = require( '@stdlib/assert/is-nonnegative-integer' ).isPrimitive;
|
|
var isArrayLikeObject = require( '@stdlib/assert/is-array-like-object' );
|
|
var isCollection = require( '@stdlib/assert/is-collection' );
|
|
var isFunction = require( '@stdlib/assert/is-function' );
|
|
var isObject = require( '@stdlib/assert/is-object' );
|
|
var hasIteratorSymbolSupport = require( '@stdlib/assert/has-iterator-symbol-support' );
|
|
var ITERATOR_SYMBOL = require( '@stdlib/symbol/iterator' );
|
|
var setReadOnly = require( './../../define-nonenumerable-read-only-property' );
|
|
var setReadOnlyAccessor = require( './../../define-nonenumerable-read-only-accessor' );
|
|
var Int32Array = require( '@stdlib/array/int32' );
|
|
var Int8Array = require( '@stdlib/array/int8' );
|
|
var ceil = require( '@stdlib/math/base/special/ceil' );
|
|
var floor = require( '@stdlib/math/base/special/floor' );
|
|
var grev = require( '@stdlib/blas/ext/base/grev' );
|
|
var fromIteratorAdjList = require( './from_adjacency_list_iterator.js' );
|
|
var fromIteratorAdjListMap = require( './from_adjacency_list_iterator_map.js' );
|
|
var fromIteratorEdges = require( './from_edges_iterator.js' );
|
|
var fromIteratorEdgesMap = require( './from_edges_iterator_map.js' );
|
|
var setBit = require( './set_bit.js' );
|
|
var clearBit = require( './clear_bit.js' );
|
|
var isSet = require( './is_set.js' );
|
|
var bitValue = require( './bit_value.js' );
|
|
|
|
|
|
// VARIABLES //
|
|
|
|
var HAS_ITERATOR_SYMBOL = hasIteratorSymbolSupport();
|
|
var NBITS = Int32Array.BYTES_PER_ELEMENT * 8; // 8 bits per byte
|
|
|
|
|
|
// MAIN //
|
|
|
|
/**
|
|
* Compact adjacency matrix constructor.
|
|
*
|
|
* @constructor
|
|
* @param {NonNegativeInteger} N - number of vertices
|
|
* @throws {TypeError} must provide a nonnegative integer
|
|
* @returns {CompactAdjacencyMatrix} adjacency matrix instance
|
|
*
|
|
* @example
|
|
* var adj = new CompactAdjacencyMatrix( 4 );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* adj.addEdge( 0, 1 );
|
|
* adj.addEdge( 0, 2 );
|
|
* adj.addEdge( 1, 2 );
|
|
* adj.addEdge( 2, 3 );
|
|
*/
|
|
function CompactAdjacencyMatrix( N ) {
|
|
if ( !( this instanceof CompactAdjacencyMatrix ) ) {
|
|
return new CompactAdjacencyMatrix( N );
|
|
}
|
|
if ( !isNonNegativeInteger( N ) ) {
|
|
throw new TypeError( 'invalid argument. Must provide a nonnegative integer. Value: `' + N + '`.' );
|
|
}
|
|
this._N = N; // number of vertices
|
|
this._M = 0; // number of edges
|
|
this._buffer = new Int32Array( ceil( N*N/NBITS ) ); // square matrix
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Creates a compact adjacency matrix from an adjacency list.
|
|
*
|
|
* @name fromAdjacencyList
|
|
* @memberof CompactAdjacencyMatrix
|
|
* @type {Function}
|
|
* @param {(ArrayLikeObject|Iterable)} list - adjacency list
|
|
* @param {Function} [clbk] - callback to invoke for each list element
|
|
* @param {*} [thisArg] - context
|
|
* @throws {TypeError} `this` context must be a constructor
|
|
* @throws {TypeError} `this` must be a compact adjacency matrix
|
|
* @throws {TypeError} first argument must be an array-like object or an iterable
|
|
* @throws {TypeError} second argument must be a function
|
|
* @throws {TypeError} each element of a provided adjacency list must be an array-like object
|
|
* @throws {TypeError} an iterator must return an array-like object containing vertices
|
|
* @throws {TypeError} when provided an iterator, a callback must return an array-like object containing vertices
|
|
* @returns {CompactAdjacencyMatrix} adjacency matrix instance
|
|
*
|
|
* @example
|
|
* var list = [ [ 1, 2 ], [ 2 ], [ 3 ], [] ];
|
|
*
|
|
* var adj = CompactAdjacencyMatrix.fromAdjacencyList( list );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* var bool = adj.hasEdge( 0, 1 );
|
|
* // returns true
|
|
*
|
|
* bool = adj.hasEdge( 0, 2 );
|
|
* // returns true
|
|
*
|
|
* bool = adj.hasEdge( 1, 2 );
|
|
* // returns true
|
|
*
|
|
* bool = adj.hasEdge( 2, 3 );
|
|
* // returns true
|
|
*/
|
|
setReadOnly( CompactAdjacencyMatrix, 'fromAdjacencyList', function fromAdjacencyList( list ) {
|
|
var thisArg;
|
|
var nargs;
|
|
var edges;
|
|
var clbk;
|
|
var adj;
|
|
var tmp;
|
|
var len;
|
|
var N;
|
|
var i;
|
|
var j;
|
|
if ( !isFunction( this ) ) {
|
|
throw new TypeError( 'invalid invocation. `this` context must be a constructor.' );
|
|
}
|
|
if ( this !== CompactAdjacencyMatrix ) {
|
|
throw new TypeError( 'invalid invocation. `this` is not a compact adjacency matrix.' );
|
|
}
|
|
nargs = arguments.length;
|
|
if ( nargs > 1 ) {
|
|
clbk = arguments[ 1 ];
|
|
if ( !isFunction( clbk ) ) {
|
|
throw new TypeError( 'invalid argument. Second argument must be a function. Value: `'+clbk+'`.' );
|
|
}
|
|
if ( nargs > 2 ) {
|
|
thisArg = arguments[ 2 ];
|
|
}
|
|
}
|
|
if ( isArrayLikeObject( list ) ) {
|
|
N = list.length;
|
|
adj = new this( N );
|
|
if ( clbk ) {
|
|
for ( i = 0; i < N; i++ ) {
|
|
edges = clbk.call( thisArg, list[ i ], i );
|
|
if ( !isCollection( edges ) ) {
|
|
throw new TypeError( 'invalid argument. Callback must return an array-like object. Value: `' + edges + '`.' );
|
|
}
|
|
for ( j = 0; j < edges.length; j++ ) {
|
|
adj.addEdge( i, edges[ j ] );
|
|
}
|
|
}
|
|
return adj;
|
|
}
|
|
for ( i = 0; i < N; i++ ) {
|
|
edges = list[ i ];
|
|
if ( !isCollection( edges ) ) {
|
|
throw new TypeError( 'invalid argument. Each element of the adjacency list must be an array-like object. Value: `' + list + '`.' );
|
|
}
|
|
for ( j = 0; j < edges.length; j++ ) {
|
|
adj.addEdge( i, edges[ j ] );
|
|
}
|
|
}
|
|
return adj;
|
|
}
|
|
if ( isObject( list ) && HAS_ITERATOR_SYMBOL && isFunction( list[ ITERATOR_SYMBOL ] ) ) { // eslint-disable-line max-len
|
|
tmp = list[ ITERATOR_SYMBOL ]();
|
|
if ( !isFunction( tmp.next ) ) {
|
|
throw new TypeError( 'invalid argument. First argument must be an array-like object or an iterable.' );
|
|
}
|
|
if ( clbk ) {
|
|
tmp = fromIteratorAdjListMap( tmp, clbk, thisArg );
|
|
} else {
|
|
tmp = fromIteratorAdjList( tmp );
|
|
}
|
|
if ( tmp instanceof Error ) {
|
|
throw tmp;
|
|
}
|
|
len = tmp.length;
|
|
adj = new this( len );
|
|
for ( i = 0; i < len; i++ ) {
|
|
edges = tmp[ i ];
|
|
for ( j = 0; j < edges.length; j++ ) {
|
|
adj.addEdge( i, edges[ j ] );
|
|
}
|
|
}
|
|
return adj;
|
|
}
|
|
throw new TypeError( 'invalid argument. First argument must be an array-like object or an iterable. Value: `' + list + '`.' );
|
|
});
|
|
|
|
/**
|
|
* Creates a compact adjacency matrix from a list of edges.
|
|
*
|
|
* @name fromEdges
|
|
* @memberof CompactAdjacencyMatrix
|
|
* @type {Function}
|
|
* @param {NonNegativeInteger} N - number of vertices
|
|
* @param {(ArrayLikeObject|Iterable)} edges - list of edges
|
|
* @param {Function} [clbk] - callback to invoke for each list element
|
|
* @param {*} [thisArg] - context
|
|
* @throws {TypeError} `this` context must be a constructor
|
|
* @throws {TypeError} `this` must be a compact adjacency matrix
|
|
* @throws {TypeError} first argument must be a nonnegative integer
|
|
* @throws {TypeError} second argument must be an array-like object
|
|
* @throws {TypeError} third argument must be a function
|
|
* @throws {TypeError} each element of a provided list of edges must be a two-element array-like object containing vertices
|
|
* @throws {TypeError} an iterator must return a two-element array-like object containing vertices
|
|
* @throws {TypeError} when provided an iterator, a callback must return a two-element array-like object containing vertices
|
|
* @returns {CompactAdjacencyMatrix} adjacency matrix instance
|
|
*
|
|
* @example
|
|
* var edges = [ [ 0, 1 ], [ 0, 2 ], [ 1, 2 ], [ 2, 3 ] ];
|
|
*
|
|
* var adj = CompactAdjacencyMatrix.fromEdges( 4, edges );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* var bool = adj.hasEdge( 0, 1 );
|
|
* // returns true
|
|
*
|
|
* bool = adj.hasEdge( 0, 2 );
|
|
* // returns true
|
|
*
|
|
* bool = adj.hasEdge( 1, 2 );
|
|
* // returns true
|
|
*
|
|
* bool = adj.hasEdge( 2, 3 );
|
|
* // returns true
|
|
*/
|
|
setReadOnly( CompactAdjacencyMatrix, 'fromEdges', function fromEdges( N, edges ) {
|
|
var thisArg;
|
|
var nargs;
|
|
var clbk;
|
|
var edge;
|
|
var adj;
|
|
var tmp;
|
|
var len;
|
|
var i;
|
|
if ( !isFunction( this ) ) {
|
|
throw new TypeError( 'invalid invocation. `this` context must be a constructor.' );
|
|
}
|
|
if ( this !== CompactAdjacencyMatrix ) {
|
|
throw new TypeError( 'invalid invocation. `this` is not a compact adjacency matrix.' );
|
|
}
|
|
nargs = arguments.length;
|
|
if ( nargs > 2 ) {
|
|
clbk = arguments[ 2 ];
|
|
if ( !isFunction( clbk ) ) {
|
|
throw new TypeError( 'invalid argument. Third argument must be a function. Value: `'+clbk+'`.' );
|
|
}
|
|
if ( nargs > 3 ) {
|
|
thisArg = arguments[ 3 ];
|
|
}
|
|
}
|
|
if ( !isNonNegativeInteger( N ) ) {
|
|
throw new TypeError( 'invalid argument. First argument must be a nonnegative integer. Value: `' + N + '`.' );
|
|
}
|
|
if ( isArrayLikeObject( edges ) ) {
|
|
if ( clbk ) {
|
|
adj = new this( N );
|
|
for ( i = 0; i < edges.length; i++ ) {
|
|
edge = clbk.call( thisArg, edges[ i ], i );
|
|
if ( !isArrayLikeObject( edge ) ) {
|
|
throw new TypeError( 'invalid argument. Callback must return an array-like object. Value: `' + edge + '`.' );
|
|
}
|
|
adj.addEdge( edge[ 0 ], edge[ 1 ] );
|
|
}
|
|
return adj;
|
|
}
|
|
adj = new this( N );
|
|
for ( i = 0; i < edges.length; i++ ) {
|
|
edge = edges[ i ];
|
|
if ( !isArrayLikeObject( edge ) ) {
|
|
throw new TypeError( 'invalid argument. Each element of the edge list must be an array-like object. Value: `' + edge + '`.' );
|
|
}
|
|
adj.addEdge( edge[ 0 ], edge[ 1 ] );
|
|
}
|
|
return adj;
|
|
}
|
|
|
|
if ( isObject( edges ) && HAS_ITERATOR_SYMBOL && isFunction( edges[ ITERATOR_SYMBOL ] ) ) { // eslint-disable-line max-len
|
|
tmp = edges[ ITERATOR_SYMBOL ]();
|
|
if ( !isFunction( tmp.next ) ) {
|
|
throw new TypeError( 'invalid argument. First argument must be an array-like object or an iterable.' );
|
|
}
|
|
if ( clbk ) {
|
|
tmp = fromIteratorEdgesMap( tmp, clbk, thisArg );
|
|
} else {
|
|
tmp = fromIteratorEdges( tmp );
|
|
}
|
|
if ( tmp instanceof Error ) {
|
|
throw tmp;
|
|
}
|
|
len = tmp.length;
|
|
adj = new this( len/2 );
|
|
for ( i = 0; i < len; i += 2 ) {
|
|
adj.addEdge( tmp[ i ], tmp[ i+1 ] );
|
|
}
|
|
return adj;
|
|
}
|
|
throw new TypeError( 'invalid argument. Second argument must be an array-like object or an iterable. Value: `' + edges + '`.' );
|
|
});
|
|
|
|
/**
|
|
* Returns indices ("bucket" and bit offset) for an `(i,j)` vertex pair.
|
|
*
|
|
* @private
|
|
* @name _loc
|
|
* @memberof CompactAdjacencyMatrix.prototype
|
|
* @type {Function}
|
|
* @param {NonNegativeInteger} i - starting vertex
|
|
* @param {NonNegativeInteger} j - ending vertex
|
|
* @param {Array} out - output array
|
|
* @throws {TypeError} first argument must be a nonnegative integer
|
|
* @throws {TypeError} second argument must be a nonnegative integer
|
|
* @throws {RangeError} first argument must not exceed matrix dimensions
|
|
* @throws {RangeError} second argument must not exceed matrix dimensions
|
|
* @returns {Array} output array
|
|
*/
|
|
setReadOnly( CompactAdjacencyMatrix.prototype, '_loc', function loc( i, j, out ) {
|
|
var bucket;
|
|
var bit;
|
|
var idx;
|
|
|
|
// Compute a strided index for the desired bit:
|
|
idx = ( i*this._N ) + j;
|
|
|
|
// Compute the index of the buffer element (bucket) containing the bit:
|
|
bucket = floor( idx / NBITS );
|
|
|
|
// Compute the bit offset:
|
|
bit = idx - ( bucket*NBITS );
|
|
|
|
// Set the output values:
|
|
out[ 0 ] = bucket;
|
|
out[ 1 ] = bit;
|
|
|
|
return out;
|
|
});
|
|
|
|
/**
|
|
* Adds a directed edge between two vertices.
|
|
*
|
|
* @name addEdge
|
|
* @memberof CompactAdjacencyMatrix.prototype
|
|
* @type {Function}
|
|
* @param {NonNegativeInteger} i - starting vertex
|
|
* @param {NonNegativeInteger} j - ending vertex
|
|
* @throws {TypeError} first argument must be a nonnegative integer
|
|
* @throws {TypeError} second argument must be a nonnegative integer
|
|
* @throws {RangeError} first argument must not exceed matrix dimensions
|
|
* @throws {RangeError} second argument must not exceed matrix dimensions
|
|
* @returns {CompactAdjacencyMatrix} adjacency matrix instance
|
|
*
|
|
* @example
|
|
* var adj = new CompactAdjacencyMatrix( 4 );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* adj.addEdge( 0, 1 );
|
|
* adj.addEdge( 0, 2 );
|
|
* adj.addEdge( 1, 2 );
|
|
* adj.addEdge( 2, 3 );
|
|
*/
|
|
setReadOnly( CompactAdjacencyMatrix.prototype, 'addEdge', function addEdge( i, j ) {
|
|
var idx;
|
|
if ( !isNonNegativeInteger( i ) ) {
|
|
throw new TypeError( 'invalid argument. First argument must be a nonnegative integer. Value: `' + i + '`.' );
|
|
}
|
|
if ( !isNonNegativeInteger( j ) ) {
|
|
throw new TypeError( 'invalid argument. Second argument must be a nonnegative integer. Value: `' + j + '`.' );
|
|
}
|
|
if ( i >= this._N ) {
|
|
throw new RangeError( 'invalid argument. First argument exceeds matrix dimensions. Value: `' + i + '`.' );
|
|
}
|
|
if ( j >= this._N ) {
|
|
throw new RangeError( 'invalid argument. Second argument exceeds matrix dimensions. Value: `' + j + '`.' );
|
|
}
|
|
// Resolve the `(i,j)` pair:
|
|
idx = this._loc( i, j, [ 0, 0 ] );
|
|
|
|
// Set the bit for the edge:
|
|
if ( isSet( this._buffer[ idx[0] ], idx[1] ) === false ) {
|
|
this._buffer[ idx[0] ] = setBit( this._buffer[ idx[0] ], idx[1] );
|
|
this._M += 1;
|
|
}
|
|
return this;
|
|
});
|
|
|
|
/**
|
|
* Returns the list of all edges.
|
|
*
|
|
* @name edges
|
|
* @memberof CompactAdjacencyMatrix.prototype
|
|
* @type {Array}
|
|
*
|
|
* @example
|
|
* var adj = new CompactAdjacencyMatrix( 4 );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* adj.addEdge( 0, 1 );
|
|
* adj.addEdge( 0, 2 );
|
|
* adj.addEdge( 1, 2 );
|
|
* adj.addEdge( 2, 3 );
|
|
*
|
|
* var edges = adj.edges;
|
|
* // returns [ [ 0, 1 ], [ 0, 2 ], [ 1, 2 ], [ 2, 3 ] ]
|
|
*/
|
|
setReadOnlyAccessor( CompactAdjacencyMatrix.prototype, 'edges', function edges() {
|
|
var edges;
|
|
var idx;
|
|
var i;
|
|
var j;
|
|
|
|
edges = [];
|
|
idx = [ 0, 0 ];
|
|
for ( i = 0; i < this._N; i++ ) {
|
|
for ( j = 0; j < this._N; j++ ) {
|
|
// Resolve the `(i,j)` pair:
|
|
idx = this._loc( i, j, idx );
|
|
|
|
// Check for an edge:
|
|
if ( isSet( this._buffer[ idx[0] ], idx[1] ) ) {
|
|
edges.push( [ i, j ] );
|
|
}
|
|
}
|
|
}
|
|
return edges;
|
|
});
|
|
|
|
/**
|
|
* Checks whether a directed edge exists between two vertices.
|
|
*
|
|
* @name hasEdge
|
|
* @memberof CompactAdjacencyMatrix.prototype
|
|
* @type {Function}
|
|
* @param {NonNegativeInteger} i - starting vertex
|
|
* @param {NonNegativeInteger} j - ending vertex
|
|
* @throws {TypeError} first argument must be a nonnegative integer
|
|
* @throws {TypeError} second argument must be a nonnegative integer
|
|
* @throws {RangeError} first argument must not exceed matrix dimensions
|
|
* @throws {RangeError} second argument must not exceed matrix dimensions
|
|
* @returns {boolean} boolean indicating if an edge exists
|
|
*
|
|
* @example
|
|
* var adj = new CompactAdjacencyMatrix( 4 );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* adj.addEdge( 0, 1 );
|
|
* adj.addEdge( 0, 2 );
|
|
* adj.addEdge( 1, 2 );
|
|
* adj.addEdge( 2, 3 );
|
|
*
|
|
* // ...
|
|
*
|
|
* var bool = adj.hasEdge( 0, 1 );
|
|
* // returns true
|
|
*
|
|
* bool = adj.hasEdge( 0, 2 );
|
|
* // returns true
|
|
*
|
|
* bool = adj.hasEdge( 1, 2 );
|
|
* // returns true
|
|
*
|
|
* bool = adj.hasEdge( 2, 3 );
|
|
* // returns true
|
|
*
|
|
* bool = adj.hasEdge( 1, 3 );
|
|
* // returns false
|
|
*/
|
|
setReadOnly( CompactAdjacencyMatrix.prototype, 'hasEdge', function hasEdge( i, j ) {
|
|
var idx;
|
|
if ( !isNonNegativeInteger( i ) ) {
|
|
throw new TypeError( 'invalid argument. First argument must be a nonnegative integer. Value: `' + i + '`.' );
|
|
}
|
|
if ( !isNonNegativeInteger( j ) ) {
|
|
throw new TypeError( 'invalid argument. Second argument must be a nonnegative integer. Value: `' + j + '`.' );
|
|
}
|
|
if ( i >= this._N ) {
|
|
throw new RangeError( 'invalid argument. First argument exceeds matrix dimensions. Value: `' + i + '`.' );
|
|
}
|
|
if ( j >= this._N ) {
|
|
throw new RangeError( 'invalid argument. Second argument exceeds matrix dimensions. Value: `' + j + '`.' );
|
|
}
|
|
// Resolve the `(i,j)` pair:
|
|
idx = this._loc( i, j, [ 0, 0 ] );
|
|
|
|
// Check for an edge:
|
|
return isSet( this._buffer[ idx[0] ], idx[1] );
|
|
});
|
|
|
|
/**
|
|
* Returns the indegree of a vertex (i.e., number of edges ending at a vertex).
|
|
*
|
|
* @name inDegree
|
|
* @memberof CompactAdjacencyMatrix.prototype
|
|
* @type {Function}
|
|
* @param {NonNegativeInteger} j - vertex
|
|
* @throws {TypeError} must provide a nonnegative integer
|
|
* @throws {RangeError} must not exceed matrix dimensions
|
|
* @returns {NonNegativeInteger} indegree
|
|
*
|
|
* @example
|
|
* var adj = new CompactAdjacencyMatrix( 4 );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* adj.addEdge( 0, 1 );
|
|
* adj.addEdge( 0, 2 );
|
|
* adj.addEdge( 1, 2 );
|
|
* adj.addEdge( 2, 3 );
|
|
*
|
|
* var d = adj.inDegree( 2 );
|
|
* // returns 2
|
|
*
|
|
* d = adj.inDegree( 3 );
|
|
* // returns 1
|
|
*/
|
|
setReadOnly( CompactAdjacencyMatrix.prototype, 'inDegree', function inDegree( j ) {
|
|
var deg;
|
|
var idx;
|
|
var i;
|
|
if ( !isNonNegativeInteger( j ) ) {
|
|
throw new TypeError( 'invalid argument. Must provide a nonnegative integer. Value: `' + j + '`.' );
|
|
}
|
|
if ( j >= this._N ) {
|
|
throw new RangeError( 'invalid argument. Input argument cannot exceed matrix dimensions. Value: `' + j + '`.' );
|
|
}
|
|
// Iterate over the rows and add up the number of edges...
|
|
deg = 0;
|
|
idx = [ 0, 0 ];
|
|
for ( i = 0; i < this._N; i++ ) {
|
|
// Resolve the `(i,j)` pair:
|
|
idx = this._loc( i, j, idx );
|
|
|
|
// Check for an edge:
|
|
deg += bitValue( this._buffer[ idx[0] ], idx[1] );
|
|
}
|
|
return deg;
|
|
});
|
|
|
|
/**
|
|
* Returns a list of vertices having edges ending at a specified vertex.
|
|
*
|
|
* @name inEdges
|
|
* @memberof CompactAdjacencyMatrix.prototype
|
|
* @type {Function}
|
|
* @param {NonNegativeInteger} j - vertex
|
|
* @throws {TypeError} must provide a nonnegative integer
|
|
* @throws {RangeError} must not exceed matrix dimensions
|
|
* @returns {Array} list of vertices
|
|
*
|
|
* @example
|
|
* var adj = new CompactAdjacencyMatrix( 4 );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* adj.addEdge( 0, 1 );
|
|
* adj.addEdge( 0, 2 );
|
|
* adj.addEdge( 1, 2 );
|
|
* adj.addEdge( 2, 3 );
|
|
*
|
|
* var e = adj.inEdges( 2 );
|
|
* // returns [ 0, 1 ]
|
|
*
|
|
* e = adj.inEdges( 3 );
|
|
* // returns [ 2 ]
|
|
*/
|
|
setReadOnly( CompactAdjacencyMatrix.prototype, 'inEdges', function inEdges( j ) {
|
|
var edges;
|
|
var idx;
|
|
var i;
|
|
if ( !isNonNegativeInteger( j ) ) {
|
|
throw new TypeError( 'invalid argument. Must provide a nonnegative integer. Value: `' + j + '`.' );
|
|
}
|
|
if ( j >= this._N ) {
|
|
throw new RangeError( 'invalid argument. Input argument cannot exceed matrix dimensions. Value: `' + j + '`.' );
|
|
}
|
|
// Iterate over the rows and retrieve edges...
|
|
edges = [];
|
|
idx = [ 0, 0 ];
|
|
for ( i = 0; i < this._N; i++ ) {
|
|
// Resolve the `(i,j)` pair:
|
|
idx = this._loc( i, j, idx );
|
|
|
|
// Check for an edge:
|
|
if ( isSet( this._buffer[ idx[0] ], idx[1] ) ) {
|
|
edges.push( i );
|
|
}
|
|
}
|
|
return edges;
|
|
});
|
|
|
|
/**
|
|
* Returns the total number of edges.
|
|
*
|
|
* @name nedges
|
|
* @memberof CompactAdjacencyMatrix.prototype
|
|
* @readonly
|
|
* @type {NonNegativeInteger}
|
|
*
|
|
* @example
|
|
* var adj = new CompactAdjacencyMatrix( 4 );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* // ...
|
|
*
|
|
* adj.addEdge( 0, 1 );
|
|
* adj.addEdge( 0, 2 );
|
|
* adj.addEdge( 1, 2 );
|
|
*
|
|
* // ...
|
|
*
|
|
* var M = adj.nedges;
|
|
* // returns 3
|
|
*/
|
|
setReadOnlyAccessor( CompactAdjacencyMatrix.prototype, 'nedges', function nedges() {
|
|
return this._M;
|
|
});
|
|
|
|
/**
|
|
* Returns the number of vertices.
|
|
*
|
|
* @name nvertices
|
|
* @memberof CompactAdjacencyMatrix.prototype
|
|
* @readonly
|
|
* @type {NonNegativeInteger}
|
|
*
|
|
* @example
|
|
* var adj = new CompactAdjacencyMatrix( 4 );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* // ...
|
|
*
|
|
* var N = adj.nvertices;
|
|
* // returns 4
|
|
*/
|
|
setReadOnlyAccessor( CompactAdjacencyMatrix.prototype, 'nvertices', function nvertices() {
|
|
return this._N;
|
|
});
|
|
|
|
/**
|
|
* Returns the outdegree of a vertex (i.e., number of edges starting from a vertex).
|
|
*
|
|
* @name outDegree
|
|
* @memberof CompactAdjacencyMatrix.prototype
|
|
* @type {Function}
|
|
* @param {NonNegativeInteger} i - vertex
|
|
* @throws {TypeError} must provide a nonnegative integer
|
|
* @throws {RangeError} must not exceed matrix dimensions
|
|
* @returns {NonNegativeInteger} outdegree
|
|
*
|
|
* @example
|
|
* var adj = new CompactAdjacencyMatrix( 4 );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* adj.addEdge( 0, 1 );
|
|
* adj.addEdge( 0, 2 );
|
|
* adj.addEdge( 1, 2 );
|
|
* adj.addEdge( 2, 3 );
|
|
*
|
|
* var d = adj.outDegree( 2 );
|
|
* // returns 1
|
|
*
|
|
* d = adj.outDegree( 0 );
|
|
* // returns 2
|
|
*/
|
|
setReadOnly( CompactAdjacencyMatrix.prototype, 'outDegree', function outDegree( i ) {
|
|
var deg;
|
|
var idx;
|
|
var j;
|
|
if ( !isNonNegativeInteger( i ) ) {
|
|
throw new TypeError( 'invalid argument. Must provide a nonnegative integer. Value: `' + i + '`.' );
|
|
}
|
|
if ( i >= this._N ) {
|
|
throw new RangeError( 'invalid argument. Input argument cannot exceed matrix dimensions. Value: `' + i + '`.' );
|
|
}
|
|
// Iterate over the columns and add up the number of edges...
|
|
deg = 0;
|
|
idx = [ 0, 0 ];
|
|
for ( j = 0; j < this._N; j++ ) {
|
|
// Resolve the `(i,j)` pair:
|
|
idx = this._loc( i, j, idx );
|
|
|
|
// Check for an edge:
|
|
deg += bitValue( this._buffer[ idx[0] ], idx[1] );
|
|
}
|
|
return deg;
|
|
});
|
|
|
|
/**
|
|
* Returns a list of vertices having edges starting at a specified vertex.
|
|
*
|
|
* @name outEdges
|
|
* @memberof CompactAdjacencyMatrix.prototype
|
|
* @type {Function}
|
|
* @param {NonNegativeInteger} i - vertex
|
|
* @throws {TypeError} must provide a nonnegative integer
|
|
* @throws {RangeError} must not exceed matrix dimensions
|
|
* @returns {Array} list of vertices
|
|
*
|
|
* @example
|
|
* var adj = new CompactAdjacencyMatrix( 4 );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* adj.addEdge( 0, 1 );
|
|
* adj.addEdge( 0, 2 );
|
|
* adj.addEdge( 1, 2 );
|
|
* adj.addEdge( 2, 3 );
|
|
*
|
|
* var e = adj.outEdges( 2 );
|
|
* // returns [ 3 ]
|
|
*
|
|
* e = adj.outEdges( 0 );
|
|
* // returns [ 1, 2 ]
|
|
*/
|
|
setReadOnly( CompactAdjacencyMatrix.prototype, 'outEdges', function outEdges( i ) {
|
|
var edges;
|
|
var idx;
|
|
var j;
|
|
if ( !isNonNegativeInteger( i ) ) {
|
|
throw new TypeError( 'invalid argument. Must provide a nonnegative integer. Value: `' + i + '`.' );
|
|
}
|
|
if ( i >= this._N ) {
|
|
throw new RangeError( 'invalid argument. Input argument cannot exceed matrix dimensions. Value: `' + i + '`.' );
|
|
}
|
|
// Iterate over the rows and retrieve edges...
|
|
edges = [];
|
|
idx = [ 0, 0 ];
|
|
for ( j = 0; j < this._N; j++ ) {
|
|
// Resolve the `(i,j)` pair:
|
|
idx = this._loc( i, j, idx );
|
|
|
|
// Check for an edge:
|
|
if ( isSet( this._buffer[ idx[0] ], idx[1] ) ) {
|
|
edges.push( j );
|
|
}
|
|
}
|
|
return edges;
|
|
});
|
|
|
|
/**
|
|
* Removes a directed edge between two vertices.
|
|
*
|
|
* @name removeEdge
|
|
* @memberof CompactAdjacencyMatrix.prototype
|
|
* @type {Function}
|
|
* @param {NonNegativeInteger} i - starting vertex
|
|
* @param {NonNegativeInteger} j - ending vertex
|
|
* @throws {TypeError} first argument must be a nonnegative integer
|
|
* @throws {TypeError} second argument must be a nonnegative integer
|
|
* @throws {RangeError} first argument must not exceed matrix dimensions
|
|
* @throws {RangeError} second argument must not exceed matrix dimensions
|
|
* @returns {CompactAdjacencyMatrix} adjacency matrix instance
|
|
*
|
|
* @example
|
|
* var adj = new CompactAdjacencyMatrix( 4 );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* adj.addEdge( 0, 1 );
|
|
* adj.addEdge( 0, 2 );
|
|
* adj.addEdge( 1, 2 );
|
|
* adj.addEdge( 2, 3 );
|
|
*
|
|
* // ...
|
|
*
|
|
* adj.removeEdge( 0, 1 );
|
|
* adj.removeEdge( 0, 2 );
|
|
* adj.removeEdge( 1, 2 );
|
|
* adj.removeEdge( 2, 3 );
|
|
*/
|
|
setReadOnly( CompactAdjacencyMatrix.prototype, 'removeEdge', function removeEdge( i, j ) {
|
|
var idx;
|
|
if ( !isNonNegativeInteger( i ) ) {
|
|
throw new TypeError( 'invalid argument. First argument must be a nonnegative integer. Value: `' + i + '`.' );
|
|
}
|
|
if ( !isNonNegativeInteger( j ) ) {
|
|
throw new TypeError( 'invalid argument. Second argument must be a nonnegative integer. Value: `' + j + '`.' );
|
|
}
|
|
if ( i >= this._N ) {
|
|
throw new RangeError( 'invalid argument. First argument exceeds matrix dimensions. Value: `' + i + '`.' );
|
|
}
|
|
if ( j >= this._N ) {
|
|
throw new RangeError( 'invalid argument. Second argument exceeds matrix dimensions. Value: `' + j + '`.' );
|
|
}
|
|
// Resolve the `(i,j)` pair:
|
|
idx = this._loc( i, j, [ 0, 0 ] );
|
|
|
|
// Clear the bit for the edge:
|
|
if ( isSet( this._buffer[ idx[0] ], idx[1] ) ) {
|
|
this._buffer[ idx[0] ] = clearBit( this._buffer[ idx[0] ], idx[1] );
|
|
this._M -= 1;
|
|
}
|
|
return this;
|
|
});
|
|
|
|
/**
|
|
* Returns an adjacency list representation.
|
|
*
|
|
* @name toAdjacencyList
|
|
* @memberof CompactAdjacencyMatrix.prototype
|
|
* @type {Function}
|
|
* @returns {Array} adjacency list representation
|
|
*
|
|
* @example
|
|
* var adj = new CompactAdjacencyMatrix( 4 );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* adj.addEdge( 0, 1 );
|
|
* adj.addEdge( 0, 2 );
|
|
* adj.addEdge( 1, 2 );
|
|
* adj.addEdge( 2, 3 );
|
|
*
|
|
* var list = adj.toAdjacencyList();
|
|
* // returns [ [ 1, 2 ], [ 2 ], [ 3 ], [] ]
|
|
*/
|
|
setReadOnly( CompactAdjacencyMatrix.prototype, 'toAdjacencyList', function toAdjacencyList() {
|
|
var list;
|
|
var idx;
|
|
var tmp;
|
|
var i;
|
|
var j;
|
|
|
|
list = [];
|
|
idx = [ 0, 0 ];
|
|
for ( i = 0; i < this._N; i++ ) {
|
|
tmp = [];
|
|
for ( j = 0; j < this._N; j++ ) {
|
|
// Resolve the `(i,j)` pair:
|
|
idx = this._loc( i, j, idx );
|
|
|
|
// Check for an edge:
|
|
if ( isSet( this._buffer[ idx[0] ], idx[1] ) ) {
|
|
tmp.push( j );
|
|
}
|
|
}
|
|
list.push( tmp );
|
|
}
|
|
return list;
|
|
});
|
|
|
|
/**
|
|
* Returns a topological ordering of the directed graph.
|
|
*
|
|
* ## Notes
|
|
*
|
|
* - The function returns a two-element array.
|
|
* - If the function is able to compute a topological ordering, the first array element is the topological ordering and the second element is `null`.
|
|
* - 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.
|
|
*
|
|
* @name toposort
|
|
* @memberof CompactAdjacencyMatrix.prototype
|
|
* @type {Function}
|
|
* @returns {Array} topological ordering
|
|
*
|
|
* @example
|
|
* var adj = new CompactAdjacencyMatrix( 4 );
|
|
* // returns <CompactAdjacencyMatrix>
|
|
*
|
|
* adj.addEdge( 1, 0 );
|
|
* adj.addEdge( 1, 2 );
|
|
* adj.addEdge( 0, 2 );
|
|
* adj.addEdge( 2, 3 );
|
|
*
|
|
* var results = adj.toposort();
|
|
* // returns <Array>
|
|
*
|
|
* var order = results[ 0 ];
|
|
* // returns [ 1, 0, 2, 3 ]
|
|
*
|
|
* var cycle = results[ 1 ];
|
|
* // returns null
|
|
*/
|
|
setReadOnly( CompactAdjacencyMatrix.prototype, 'toposort', function toposort() {
|
|
var marks;
|
|
var self;
|
|
var out;
|
|
var idx;
|
|
var err;
|
|
var N;
|
|
var s;
|
|
var i;
|
|
|
|
self = this;
|
|
N = this._N;
|
|
|
|
// Initialize an empty list that will contain the sorted vertices:
|
|
out = [];
|
|
|
|
// If the graph is empty, nothing to sort...
|
|
if ( this._N === 0 ) {
|
|
return [ out, null ];
|
|
}
|
|
// Initialize an array for keeping track of whether a vertex has been "visited":
|
|
marks = new Int8Array( N );
|
|
|
|
// Initialize a stack for keeping track of cycles:
|
|
s = [];
|
|
|
|
// Process vertices using depth-first-search...
|
|
idx = [ 0, 0 ];
|
|
for ( i = 0; i < N; i++ ) {
|
|
if ( marks[ i ] === 0 ) {
|
|
err = visit( i );
|
|
if ( err !== 0 ) {
|
|
// Found a cycle...
|
|
s.push( i );
|
|
return [ null, s ];
|
|
}
|
|
}
|
|
}
|
|
// Reverse the output array as the leaves were added first, followed the by the roots, via depth-first-search:
|
|
grev( out.length, out, 1 );
|
|
|
|
return [ out, null ];
|
|
|
|
/**
|
|
* Visits a graph vertex and follows edges until finding a leaf vertex (if one exists).
|
|
*
|
|
* ## Notes
|
|
*
|
|
* - 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.
|
|
*
|
|
* @private
|
|
* @param {NonNegativeInteger} i - vertex
|
|
* @returns {integer} error code
|
|
*/
|
|
function visit( i ) {
|
|
var err;
|
|
var j;
|
|
|
|
// Check if we've already processed/visited this vertex...
|
|
if ( marks[ i ] === 2 ) {
|
|
return 0;
|
|
}
|
|
// Check if we've seen this vertex before and the vertex is still being processed...
|
|
if ( marks[ i ] === 1 ) {
|
|
// We've found a cycle...
|
|
return -1;
|
|
}
|
|
// Mark the current vertex as currently being processed:
|
|
marks[ i ] = 1;
|
|
|
|
// Follow all edges from the current vertex...
|
|
for ( j = 0; j < N; j++ ) {
|
|
idx = self._loc( i, j, idx ); // eslint-disable-line no-underscore-dangle
|
|
if ( isSet( self._buffer[ idx[0] ], idx[1] ) ) { // eslint-disable-line no-underscore-dangle
|
|
err = visit( j );
|
|
if ( err !== 0 ) {
|
|
// This vertex is part of a cycle, so add to cycle stack...
|
|
s.push( j );
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
// Mark the current vertex as processed:
|
|
marks[ i ] = 2;
|
|
|
|
// 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:
|
|
out.push( i );
|
|
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
|
|
// EXPORTS //
|
|
|
|
module.exports = CompactAdjacencyMatrix;
|