compute-indexspace
Version:
Generates a linearly spaced index array from a subsequence string.
272 lines (247 loc) • 5.18 kB
JavaScript
/**
*
* COMPUTE: indexspace
*
*
* DESCRIPTION:
* - Generates a linearly spaced index array from a subsequence string.
*
*
* NOTES:
* [1]
*
*
* TODO:
* [1]
*
*
* LICENSE:
* MIT
*
* Copyright (c) 2015. Athan Reines.
*
*
* AUTHOR:
* Athan Reines. kgryte@gmail.com. 2015.
*
*/
;
// MODULES //
var isString = require( 'validate.io-string-primitive' ),
isNonNegativeInteger = require( 'validate.io-nonnegative-integer' );
// VARIABLES //
var re = /^(?:(?:-(?=\d+))?\d*|end(?:-\d+|\/\d+)?):(?:(?:-(?=\d+))?\d*|end(?:-\d+|\/\d+)?)(?:\:(?=-?\d*)(?:-?\d+)?)?$/;
/**
* ^(...)
* => require that the string begin with either a digit (+ or -), an `end` keyword, or a colon
*
* (?:(?:-(?=\d+))?\d*|end(?:-?\d+|/\\d+)?)
* => match but do not capture
* (?:-(?=\d+))?
* => match but do not capture a minus sign but only if followed by one or more digits
* \d*
* => 0 or more digits
* |
* => OR
* end(?:-\d+|/\d+)?
* => the word `end` exactly, which may be followed by either a minus sign and 1 or more digits or a division sign and 1 or more digits
*
* :
* => match a colon exactly
*
* (?:(?:-(?=\d+))?\d*|end(?:-\d+|/\\d+)?)
* => same as above
*
* (?:\:(?=-?\d*)(?:-?\d+)?)?
* => match but do not capture
* \:(?=-?\d*)
* => a colon but only if followed by either a possible minus sign and 0 or more digits
* (?:-?\d+)?
* => match but do not capture a possible minus sign and 1 or more digits
*
* $
* => require that the string end with either a digit, `end` keyword, or a colon.
*
*
* Examples:
* - :
* - ::
* - 4:
* - :4
* - ::-1
* - 3::-1
* - :10:2
* - 1:3:1
* - 9:1:-3
* - 1:-1
* - :-1
* - -5:
* - 1:-5:2
* - -9:10:1
* - -9:-4:2
* - -4:-9:-2
* - 1:end:2
* - :end/2
* - end/2:end:5
*/
var reEnd = /^end/,
reMatch = /(-|\/)(?=\d+)(\d+)?$/;
// INDEXSPACE
/**
* FUNCTION: indexspace( str, len )
* Generates a linearly spaced index array from a subsequence string.
*
* @param {String} str - subsequence string
* @param {Number} len - reference array length
* @returns {Number[]} array of indices
*/
function indexspace( str, len ) {
var x1,
x2,
tmp,
inc,
arr;
if ( !isString( str ) || !re.test( str ) ) {
throw new Error( 'indexspace()::invalid input argument. Invalid subsequence syntax. Please consult documentation. Value: `' + str + '`.' );
}
if ( !isNonNegativeInteger( len ) ) {
throw new TypeError( 'indexspace()::invalid input argument. Reference array length must be a nonnegative integer. Value: `' + len + '`.' );
}
if ( !len ) {
return [];
}
str = str.split( ':' );
x1 = str[ 0 ];
x2 = str[ 1 ];
if ( str.length === 2 ) {
inc = 1;
} else {
inc = parseInt( str[ 2 ], 10 );
}
// Handle zero increment...
if ( inc === 0 ) {
throw new Error( 'indexspace()::invalid syntax. Increment must be an integer not equal to 0. Value: `' + inc + '`.' );
}
// START //
// Handle use of 'end' keyword...
if ( reEnd.test( x1 ) ) {
tmp = x1.match( reMatch );
if ( tmp ) {
if ( tmp[ 1 ] === '-' ) {
x1 = len - 1 - parseInt( tmp[ 2 ], 10 );
if ( x1 < 0 ) {
// WARNING: forgive the user for exceeding the range bounds...
x1 = 0;
}
} else {
x1 = (len-1) / parseInt( tmp[ 2 ], 10 );
x1 = Math.ceil( x1 );
}
} else {
x1 = len - 1;
}
} else {
x1 = parseInt( x1, 10 );
// Handle empty index...
if ( x1 !== x1 ) {
// :-?\d*:-?\d+
if ( inc < 0 ) {
// Max index:
x1 = len - 1;
} else {
// Min index:
x1 = 0;
}
}
// Handle negative index...
else if ( x1 < 0 ) {
x1 = len + x1; // len-x1
if ( x1 < 0 ) {
// WARNING: forgive the user for exceeding index bounds...
x1 = 0;
}
}
// Handle exceeding bounds...
else if ( x1 >= len ) {
return [];
}
}
// END //
// NOTE: here, we determine an inclusive `end` value; i.e., the last acceptable index value.
// Handle use of 'end' keyword...
if ( reEnd.test( x2 ) ) {
tmp = x2.match( reMatch );
if ( tmp ) {
if ( tmp[ 1 ] === '-' ) {
x2 = len - 1 - parseInt( tmp[ 2 ], 10 );
if ( x2 < 0 ) {
// WARNING: forgive the user for exceeding the range bounds...
x2 = 0;
}
} else {
x2 = (len-1) / parseInt( tmp[ 2 ], 10 );
x2 = Math.ceil( x2 ) - 1;
}
} else {
x2 = len - 1;
}
} else {
x2 = parseInt( x2, 10 );
// Handle empty index...
if ( x2 !== x2 ) {
// -?\d*::-?\d+
if ( inc < 0 ) {
// Min index:
x2 = 0;
} else {
// Max index:
x2 = len - 1;
}
}
// Handle negative index...
else if ( x2 < 0 ) {
x2 = len + x2; // len-x2
if ( x2 < 0 ) {
// WARNING: forgive the user for exceeding index bounds...
x2 = 0;
}
if ( inc > 0 ) {
x2 = x2 - 1;
}
}
// Handle positive index...
else {
if ( inc < 0 ) {
x2 = x2 + 1;
}
else if ( x2 >= len ) {
x2 = len - 1;
}
else {
x2 = x2 - 1;
}
}
}
// INDICES //
arr = [];
if ( inc < 0 ) {
if ( x2 > x1 ) {
return arr;
}
while ( x1 >= x2 ) {
arr.push( x1 );
x1 += inc;
}
} else {
if ( x1 > x2 ) {
return arr;
}
while ( x1 <= x2 ) {
arr.push( x1 );
x1 += inc;
}
}
return arr;
} // end FUNCTION indexspace()
// EXPORTS //
module.exports = indexspace;