@stdlib/utils
Version:
Standard utilities.
1,408 lines (1,336 loc) • 41.5 kB
JavaScript
/* eslint-disable max-len, max-lines */
/**
* @license Apache-2.0
*
* Copyright (c) 2018 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.
*/
'use strict';
// MODULES //
var isStringArray = require( '@stdlib/assert/is-string-array' ).primitives;
var isString = require( '@stdlib/assert/is-string' ).isPrimitive;
var isArrayBuffer = require( '@stdlib/assert/is-arraybuffer' );
var isFunction = require( '@stdlib/assert/is-function' );
var isInteger = require( '@stdlib/assert/is-integer' ).isPrimitive;
var isObject = require( '@stdlib/assert/is-object' );
var isCollection = require( '@stdlib/assert/is-collection' );
var hasOwnProp = require( '@stdlib/assert/has-own-property' );
var hasIteratorSymbolSupport = require( '@stdlib/assert/has-iterator-symbol-support' );
var propertiesIn = require( './../../properties-in' );
var typedarray = require( '@stdlib/array/typed' );
var Int8Array = require( '@stdlib/array/int8' );
var getDtype = require( '@stdlib/array/dtype' );
var defineProperty = require( './../../define-property' );
var setNonEnumerableProperty = require( './../../define-nonenumerable-property' );
var setNonEnumerableReadOnlyAccessor = require( './../../define-nonenumerable-read-only-accessor' ); // eslint-disable-line id-length
var setNonEnumerableReadWriteAccessor = require( './../../define-nonenumerable-read-write-accessor' ); // eslint-disable-line id-length
var floor = require( '@stdlib/math/base/special/floor' );
var ITERATOR_SYMBOL = require( '@stdlib/symbol/iterator' );
var format = require( '@stdlib/string/format' );
var contains = require( './contains.js' );
var hasDistinctElements = require( './has_distinct_elements.js' );
var validate = require( './validate.js' );
var ascending = require( './ascending.js' );
var fromIterator = require( './from_iterator.js' );
var fromIteratorMap = require( './from_iterator_map.js' );
// VARIABLES //
var RESERVED_PROPS = propertiesIn( new Int8Array( 0 ) );
var HAS_ITERATOR_SYMBOL = hasIteratorSymbolSupport();
// MAIN //
/**
* Returns a named typed tuple factory.
*
* @param {StringArray} names - field (property) names
* @param {Options} [options] - options
* @param {string} [options.dtype="float64"] - default data type
* @param {string} [options.name="tuple"] - tuple name
* @throws {TypeError} must provide an array of strings
* @throws {TypeError} must provide distinct field names
* @throws {Error} cannot provide a reserved field (property) name
* @throws {TypeError} must provide valid options
* @throws {Error} must provide a recognized data type
* @returns {Function} factory function
*
* @example
* var point = factory( [ 'x', 'y' ] );
*
* var p = point( [ 1.0, -1.0 ] );
*
* var x = p[ 0 ];
* // returns 1.0
*
* x = p.x;
* // returns 1.0
*
* var y = p[ 1 ];
* // returns -1.0
*
* y = p.y;
* // returns -1.0
*/
function factory( names, options ) { // eslint-disable-line max-lines-per-function, stdlib/jsdoc-require-throws-tags
var nfields;
var fields;
var opts;
var err;
var i;
if ( !isStringArray( names ) ) {
throw new TypeError( format( 'invalid argument. Must provide an array of strings. Value: `%s`.', names ) );
}
if ( !hasDistinctElements( names ) ) {
throw new TypeError( format( 'invalid argument. Field names must be distinct. Value: `%s`.', names ) );
}
fields = names.slice();
nfields = fields.length;
for ( i = 0; i < nfields; i++ ) {
if ( contains( RESERVED_PROPS, fields[ i ] ) ) {
throw new Error( format( 'invalid argument. Provided field name is reserved. Name: `%s`.', fields[ i ] ) );
}
}
opts = {
'dtype': 'float64',
'name': 'tuple'
};
if ( arguments.length > 1 ) {
err = validate( opts, options );
if ( err ) {
throw err;
}
}
/**
* Returns a named typed tuple.
*
* @private
* @param {(TypedArray|ArrayLikeObject|ArrayBuffer|Iterable)} [arg] - a typed array, array-like object, buffer, or an iterable
* @param {NonNegativeInteger} [byteOffset=0] - byte offset
* @param {string} [dtype] - data type
* @throws {TypeError} must provide a recognized data type
* @throws {RangeError} arguments must be compatible with tuple length
* @returns {TypedArray} named typed tuple
*/
function namedtypedtuple() { // eslint-disable-line max-lines-per-function
var indices;
var dtype;
var nargs;
var tuple;
var i;
nargs = arguments.length;
if ( nargs <= 0 ) {
tuple = typedarray( nfields, opts.dtype );
} else if ( nargs === 1 ) {
if ( isString( arguments[ 0 ] ) ) {
// Arguments: [ dtype ]
tuple = typedarray( nfields, arguments[ 0 ] );
} else if ( isArrayBuffer( arguments[ 0 ] ) ) {
// Arguments: [ ArrayBuffer ]
tuple = typedarray( arguments[ 0 ], 0, nfields, opts.dtype );
} else {
// Arguments: [ TypedArray|ArrayLikeObject|Iterable ]
tuple = typedarray( arguments[ 0 ], opts.dtype );
}
} else if ( nargs === 2 ) {
if ( isArrayBuffer( arguments[ 0 ] ) ) {
if ( isString( arguments[ 1 ] ) ) {
// Arguments: [ ArrayBuffer, dtype ]
tuple = typedarray( arguments[ 0 ], 0, nfields, arguments[ 1 ] );
} else {
// Arguments: [ ArrayBuffer, byteOffset ]
tuple = typedarray( arguments[ 0 ], arguments[ 1 ], nfields, opts.dtype );
}
} else {
// Arguments: [ TypedArray|ArrayLikeObject|Iterable, dtype ]
tuple = typedarray( arguments[ 0 ], arguments[ 1 ] );
}
} else {
// Arguments: [ ArrayBuffer, byteOffset, dtype ]
tuple = typedarray( arguments[ 0 ], arguments[ 1 ], nfields, arguments[ 2 ] );
}
if ( tuple.length !== nfields ) {
throw new RangeError( format( 'invalid arguments. Arguments are incompatible with the number of tuple fields. Number of fields: `%u`. Number of data elements: `%u`.', nfields, tuple.length ) );
}
dtype = getDtype( tuple );
indices = []; // indirect index look-up table
for ( i = 0; i < nfields; i++ ) {
indices.push( i );
setNonEnumerableReadWriteAccessor( tuple, fields[ i ], getter( i ), setter( i ) );
}
setNonEnumerableProperty( tuple, 'name', opts.name );
setNonEnumerableReadOnlyAccessor( tuple, 'fields', getFields );
setNonEnumerableReadOnlyAccessor( tuple, 'orderedFields', orderedFields );
// Note: keep in alphabetical order
setNonEnumerableProperty( tuple, 'entries', entries );
setNonEnumerableProperty( tuple, 'every', every );
setNonEnumerableProperty( tuple, 'fieldOf', fieldOf );
setNonEnumerableProperty( tuple, 'filter', filter );
setNonEnumerableProperty( tuple, 'find', find );
setNonEnumerableProperty( tuple, 'findIndex', findIndex );
setNonEnumerableProperty( tuple, 'findField', findField );
setNonEnumerableProperty( tuple, 'forEach', forEach );
setNonEnumerableProperty( tuple, 'ind2key', ind2key );
setNonEnumerableProperty( tuple, 'key2ind', key2ind );
setNonEnumerableProperty( tuple, 'keys', keys );
setNonEnumerableProperty( tuple, 'lastFieldOf', lastFieldOf );
setNonEnumerableProperty( tuple, 'map', map );
setNonEnumerableProperty( tuple, 'reduce', reduce );
setNonEnumerableProperty( tuple, 'reduceRight', reduceRight );
setNonEnumerableProperty( tuple, 'reverse', reverse );
setNonEnumerableProperty( tuple, 'slice', slice );
setNonEnumerableProperty( tuple, 'some', some );
setNonEnumerableProperty( tuple, 'sort', sort );
setNonEnumerableProperty( tuple, 'subtuple', subtuple );
setNonEnumerableProperty( tuple, 'toJSON', toJSON );
setNonEnumerableProperty( tuple, 'toString', toString );
return tuple;
/**
* Returns an accessor to retrieve a tuple value.
*
* @private
* @param {NonNegativeInteger} i - tuple index
* @returns {Function} accessor
*/
function getter( i ) {
return get;
/**
* Returns a tuple value.
*
* @private
* @returns {number} tuple value
*/
function get() {
return tuple[ indices[ i ] ];
}
}
/**
* Returns an accessor to set a tuple value.
*
* @private
* @param {NonNegativeInteger} i - tuple index
* @returns {Function} accessor
*/
function setter( i ) {
return set;
/**
* Sets a tuple value.
*
* @private
* @param {number} v - value to set
*/
function set( v ) {
tuple[ indices[ i ] ] = v;
}
}
/**
* Returns the list of tuple fields.
*
* @private
* @memberof tuple
* @returns {StringArray} tuple fields
*/
function getFields() {
return fields.slice();
}
/**
* Returns the list of tuple fields in index order.
*
* @private
* @memberof tuple
* @returns {StringArray} tuple fields
*/
function orderedFields() {
var out;
var i;
out = fields.slice();
for ( i = 0; i < nfields; i++ ) {
out[ i ] = fields[ indices[i] ];
}
return out;
}
// Note: keep functions which follow in alphabetical order
/**
* Returns an iterator for iterating over tuple key-value pairs.
*
* @private
* @memberof tuple
* @throws {TypeError} `this` must be the host tuple
* @returns {Iterator} iterator
*/
function entries() {
var self;
var iter;
var FLG;
var i;
self = this; // eslint-disable-line no-invalid-this
if ( self !== tuple ) {
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
// Initialize the iteration index:
i = -1;
// Create an iterator protocol-compliant object:
iter = {};
defineProperty( iter, 'next', {
'configurable': false,
'enumerable': false,
'writable': false,
'value': next
});
defineProperty( iter, 'return', {
'configurable': false,
'enumerable': false,
'writable': false,
'value': end
});
if ( HAS_ITERATOR_SYMBOL ) {
defineProperty( iter, ITERATOR_SYMBOL, {
'configurable': false,
'enumerable': false,
'writable': false,
'value': factory
});
}
return iter;
/**
* Returns an iterator protocol-compliant object containing the next iterated value.
*
* @private
* @returns {Object} iterator protocol-compliant object
*/
function next() {
i += 1;
if ( FLG || i >= nfields ) {
return {
'done': true
};
}
return {
'value': [ i, fields[ indices[ i ] ], tuple[ i ] ],
'done': false
};
}
/**
* Finishes an iterator.
*
* @private
* @param {*} [value] - value to return
* @returns {Object} iterator protocol-compliant object
*/
function end( value ) {
FLG = true;
if ( arguments.length ) {
return {
'value': value,
'done': true
};
}
return {
'done': true
};
}
/**
* Returns a new iterator.
*
* @private
* @returns {Iterator} iterator
*/
function factory() {
return self.entries();
}
}
/**
* Tests whether all tuple elements pass a test implemented by a predicate function.
*
* @private
* @memberof tuple
* @param {Function} predicate - predicate function
* @param {*} [thisArg] - execution context
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be a function
* @returns {boolean} boolean indicating if all elements pass
*/
function every( predicate, thisArg ) {
var bool;
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( !isFunction( predicate ) ) {
throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', predicate ) );
}
for ( i = 0; i < nfields; i++ ) {
bool = predicate.call( thisArg, tuple[ i ], i, fields[ indices[i] ], tuple );
if ( !bool ) {
return false;
}
}
return true;
}
/**
* Returns the field of the first tuple element strictly equal to a search element.
*
* ## Notes
*
* - The function does not distinguish between signed and unsigned zero.
* - If unable to locate a search element, the function returns `undefined`.
*
* @private
* @memberof tuple
* @param {*} searchElement - search element
* @param {integer} [fromIndex=0] - tuple index from which to begin searching
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} second argument must be an integer
* @returns {(string|void)} tuple field name or `undefined`
*/
function fieldOf( searchElement ) {
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( arguments.length > 1 ) {
i = arguments[ 0 ];
if ( !isInteger( i ) ) {
throw new TypeError( format( 'invalid argument. Second argument must be an integer. Value: `%s`.', i ) );
}
if ( i >= nfields ) {
return;
}
if ( i < 0 ) {
i = nfields + i;
if ( i < 0 ) {
i = 0;
}
}
} else {
i = 0;
}
for ( ; i < nfields; i++ ) {
if ( tuple[ i ] === searchElement ) {
return fields[ indices[ i ] ];
}
}
}
/**
* Creates a new tuple which includes those elements for which a predicate function returns a truthy value.
*
* ## Notes
*
* - The returned tuple has the same data type as the host tuple.
* - If a predicate function does not return a truthy value for any tuple element, the function returns `null`.
*
* @private
* @memberof tuple
* @param {Function} predicate - filter (predicate) function
* @param {*} [thisArg] - execution context
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be a function
* @returns {(TypedArray|null)} new tuple
*/
function filter( predicate, thisArg ) {
var bool;
var tmp;
var f;
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( !isFunction( predicate ) ) {
throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', predicate ) );
}
tmp = [];
f = [];
for ( i = 0; i < nfields; i++ ) {
bool = predicate.call( thisArg, tuple[ i ], i, fields[ indices[i] ], tuple );
if ( bool ) {
f.push( fields[ indices[i] ] );
tmp.push( tuple[ i ] );
}
}
if ( f.length === nfields ) {
return namedtypedtuple( tmp, dtype );
}
if ( f.length ) {
return factory( f, opts )( tmp );
}
return null;
}
/**
* Returns the first tuple element for which a provided predicate function returns a truthy value.
*
* @private
* @memberof tuple
* @param {Function} predicate - predicate function
* @param {*} [thisArg] - execution context
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be a function
* @returns {(number|void)} tuple element
*/
function find( predicate, thisArg ) {
var bool;
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( !isFunction( predicate ) ) {
throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', predicate ) );
}
for ( i = 0; i < nfields; i++ ) {
bool = predicate.call( thisArg, tuple[ i ], i, fields[ indices[i] ], tuple );
if ( bool ) {
return tuple[ i ];
}
}
}
/**
* Returns the field of the first tuple element for which a provided predicate function returns a truthy value.
*
* ## Notes
*
* - If the predicate function never returns a truthy value, the function returns `undefined`.
*
* @private
* @memberof tuple
* @param {Function} predicate - predicate function
* @param {*} [thisArg] - execution context
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be a function
* @returns {(string|void)} tuple field name or `undefined`
*/
function findField( predicate, thisArg ) {
var bool;
var f;
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( !isFunction( predicate ) ) {
throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', predicate ) );
}
for ( i = 0; i < nfields; i++ ) {
f = fields[ indices[ i ] ];
bool = predicate.call( thisArg, tuple[ i ], i, f, tuple );
if ( bool ) {
return f;
}
}
}
/**
* Returns the index of the first tuple element for which a provided predicate function returns a truthy value.
*
* ## Notes
*
* - If the predicate function never returns a truthy value, the function returns `-1`.
*
* @private
* @memberof tuple
* @param {Function} predicate - predicate function
* @param {*} [thisArg] - execution context
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be a function
* @returns {integer} tuple index or `-1`
*/
function findIndex( predicate, thisArg ) {
var bool;
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( !isFunction( predicate ) ) {
throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', predicate ) );
}
for ( i = 0; i < nfields; i++ ) {
bool = predicate.call( thisArg, tuple[ i ], i, fields[ indices[i] ], tuple );
if ( bool ) {
return i;
}
}
return -1;
}
/**
* Invokes a callback for each tuple element.
*
* @private
* @memberof tuple
* @param {Function} fcn - function to invoke
* @param {*} [thisArg] - execution context
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be a function
*/
function forEach( fcn, thisArg ) {
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( !isFunction( fcn ) ) {
throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', fcn ) );
}
for ( i = 0; i < nfields; i++ ) {
fcn.call( thisArg, tuple[ i ], i, fields[ indices[i] ], tuple );
}
}
/**
* Converts a tuple index to a field name.
*
* ## Notes
*
* - If provided an out-of-bounds index, the function returns `undefined`.
* - If provided a negative tuple index, the function resolves the index relative to the last tuple element.
*
* @private
* @memberof tuple
* @param {integer} ind - tuple index
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} must provide an integer
* @returns {(string|void)} field name or undefined
*/
function ind2key( ind ) {
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( !isInteger( ind ) ) {
throw new TypeError( format( 'invalid argument. Must provide an integer. Value: `%s`.', ind ) );
}
if ( ind < 0 ) {
ind = nfields + ind;
}
if ( ind < 0 || ind >= nfields ) {
return;
}
return fields[ indices[ ind ] ];
}
/**
* Converts a field name to a tuple index.
*
* ## Notes
*
* - If provided an unknown field name, the function returns `-1`.
*
* @private
* @memberof tuple
* @param {string} key - field name
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be a string
* @returns {integer} tuple index
*/
function key2ind( key ) {
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( !isString( key ) ) {
throw new TypeError( format( 'invalid argument. First argument must be a string. Value: `%s`.', key ) );
}
for ( i = 0; i < nfields; i++ ) {
if ( fields[ indices[i] ] === key ) {
return i;
}
}
return -1;
}
/**
* Returns an iterator for iterating over tuple keys.
*
* @private
* @memberof tuple
* @throws {TypeError} `this` must be the host tuple
* @returns {Iterator} iterator
*/
function keys() {
var self;
var iter;
var FLG;
var i;
self = this; // eslint-disable-line no-invalid-this
if ( self !== tuple ) {
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
// Initialize the iteration index:
i = -1;
// Create an iterator protocol-compliant object:
iter = {};
defineProperty( iter, 'next', {
'configurable': false,
'enumerable': false,
'writable': false,
'value': next
});
defineProperty( iter, 'return', {
'configurable': false,
'enumerable': false,
'writable': false,
'value': end
});
if ( HAS_ITERATOR_SYMBOL ) {
defineProperty( iter, ITERATOR_SYMBOL, {
'configurable': false,
'enumerable': false,
'writable': false,
'value': factory
});
}
return iter;
/**
* Returns an iterator protocol-compliant object containing the next iterated value.
*
* @private
* @returns {Object} iterator protocol-compliant object
*/
function next() {
i += 1;
if ( FLG || i >= nfields ) {
return {
'done': true
};
}
return {
'value': [ i, fields[ indices[ i ] ] ],
'done': false
};
}
/**
* Finishes an iterator.
*
* @private
* @param {*} [value] - value to return
* @returns {Object} iterator protocol-compliant object
*/
function end( value ) {
FLG = true;
if ( arguments.length ) {
return {
'value': value,
'done': true
};
}
return {
'done': true
};
}
/**
* Returns a new iterator.
*
* @private
* @returns {Iterator} iterator
*/
function factory() {
return self.keys();
}
}
/**
* Returns the field of the last tuple element strictly equal to a search element, iterating from right to left.
*
* ## Notes
*
* - The function does not distinguish between signed and unsigned zero.
* - If unable to locate a search element, the function returns `undefined`.
*
* @private
* @memberof tuple
* @param {*} searchElement - search element
* @param {integer} [fromIndex=-1] - tuple index from which to begin searching
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} second argument must be an integer
* @returns {(string|void)} tuple field name or `undefined`
*/
function lastFieldOf( searchElement ) {
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( arguments.length > 1 ) {
i = arguments[ 1 ];
if ( !isInteger( i ) ) {
throw new TypeError( format( 'invalid argument. Second argument must be an integer. Value: `%s`.', i ) );
}
if ( i >= nfields ) {
i = nfields - 1;
} else if ( i < 0 ) {
i = nfields + i;
if ( i < 0 ) {
return;
}
}
} else {
i = nfields - 1;
}
for ( ; i >= 0; i-- ) {
if ( tuple[ i ] === searchElement ) {
return fields[ indices[ i ] ];
}
}
}
/**
* Maps each tuple element to an element in a new tuple.
*
* ## Notes
*
* - The returned tuple has the same data type as the host tuple.
*
* @private
* @memberof tuple
* @param {Function} fcn - map function
* @param {*} [thisArg] - execution context
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be a function
* @returns {TypedArray} new tuple
*/
function map( fcn, thisArg ) {
var out;
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( !isFunction( fcn ) ) {
throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', fcn ) );
}
out = namedtypedtuple( dtype );
for ( i = 0; i < nfields; i++ ) {
out[ i ] = fcn.call( thisArg, tuple[ i ], i, fields[ indices[i] ], tuple );
}
return out;
}
/**
* Applies a function against an accumulator and each element in a tuple and returns the accumulated result.
*
* @private
* @memberof tuple
* @param {Function} fcn - reduction function
* @param {*} [initial] - initial value
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be a function
* @returns {*} accumulated result
*/
function reduce( fcn ) {
var acc;
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( !isFunction( fcn ) ) {
throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', fcn ) );
}
if ( arguments.length > 1 ) {
acc = arguments[ 1 ];
i = 0;
} else {
acc = tuple[ 0 ];
i = 1;
}
for ( ; i < nfields; i++ ) {
acc = fcn( acc, tuple[ i ], i, fields[ indices[i] ], tuple );
}
return acc;
}
/**
* Applies a function against an accumulator and each element in a tuple and returns the accumulated result, iterating from right to left.
*
* @private
* @memberof tuple
* @param {Function} fcn - reduction function
* @param {*} [initial] - initial value
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be a function
* @returns {*} accumulated result
*/
function reduceRight( fcn ) {
var acc;
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( !isFunction( fcn ) ) {
throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', fcn ) );
}
if ( arguments.length > 1 ) {
acc = arguments[ 1 ];
i = nfields - 1;
} else {
acc = tuple[ nfields-1 ];
i = nfields - 2;
}
for ( ; i >= 0; i-- ) {
acc = fcn( acc, tuple[ i ], i, fields[ indices[i] ], tuple );
}
return acc;
}
/**
* Reverses a tuple **in-place**.
*
* @private
* @memberof tuple
* @throws {TypeError} `this` must be the host tuple
* @returns {TypedArray} reversed tuple
*/
function reverse() {
var tmp;
var i;
var j;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
for ( i = 0; i < floor( nfields/2 ); i++ ) {
j = nfields - i - 1;
tmp = tuple[ i ];
tuple[ i ] = tuple[ j ];
tuple[ j ] = tmp;
}
// Because the indices are bounded [0,nfields), we can use simple arithmetic to "reverse" index values in-place...
for ( i = 0; i < nfields; i++ ) {
indices[ i ] = nfields - indices[ i ] - 1;
}
return tuple;
}
/**
* Copies elements to a new tuple with the same underlying data type as the host tuple.
*
* ## Notes
*
* - If the function is unable to resolve indices to a non-empty tuple subsequence, the function returns `null`.
*
* @private
* @memberof tuple
* @param {integer} [begin=0] - start element index (inclusive)
* @param {integer} [end=tuple.length] - end element index (exclusive)
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be an integer
* @throws {TypeError} second argument must be an integer
* @returns {(TypedArray|null)} new tuple
*/
function slice( begin, end ) {
var tmp;
var f;
var i;
var j;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( arguments.length === 0 ) {
return namedtypedtuple( tuple, dtype );
}
i = begin;
if ( !isInteger( i ) ) {
throw new TypeError( format( 'invalid argument. First argument must be an integer. Value: `%s`.', begin ) );
}
if ( i < 0 ) {
i = nfields + i;
if ( i < 0 ) {
i = 0;
}
}
if ( arguments.length === 1 ) {
j = nfields;
} else {
j = end;
if ( !isInteger( j ) ) {
throw new TypeError( format( 'invalid argument. Second argument must be an integer. Value: `%s`.', end ) );
}
if ( j < 0 ) {
j = nfields + j;
if ( j < 0 ) {
j = 0;
}
} else if ( j > nfields ) {
j = nfields;
}
}
if ( i >= j ) {
return null;
}
f = [];
tmp = [];
for ( ; i < j; i++ ) {
f.push( fields[ indices[i] ] );
tmp.push( tuple[ i ] );
}
return factory( f, opts )( tmp, dtype );
}
/**
* Tests whether at least one tuple element passes a test implemented by a predicate function.
*
* @private
* @memberof tuple
* @param {Function} predicate - predicate function
* @param {*} [thisArg] - execution context
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be a function
* @returns {boolean} boolean indicating if at least one element passes
*/
function some( predicate, thisArg ) {
var bool;
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( !isFunction( predicate ) ) {
throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', predicate ) );
}
for ( i = 0; i < nfields; i++ ) {
bool = predicate.call( thisArg, tuple[ i ], i, fields[ indices[i] ], tuple );
if ( bool ) {
return true;
}
}
return false;
}
/**
* Sorts a tuple in-place.
*
* ## Notes
*
* - The comparison function is provided two tuple elements, `a` and `b`, per invocation, and its return value determines the sort order as follows:
*
* - If the comparison function returns a value **less** than zero, then the function sorts `a` to an index lower than `b` (i.e., `a` should come **before** `b`).
* - If the comparison function returns a value **greater** than zero, then the function sorts `a` to an index higher than `b` (i.e., `b` should come **before** `a`).
* - If the comparison function returns **zero**, then the relative order of `a` and `b` _should_ remain unchanged.
*
* - Invoking this method does **not** affect tuple field assignments.
*
* @private
* @memberof tuple
* @param {Function} [compareFunction] - function which specifies the sort order
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be a function
* @returns {TypedArray} sorted tuple
*/
function sort( compareFunction ) {
var clbk;
var tmp;
var i;
var j;
var k;
var v;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( arguments.length ) {
if ( !isFunction( compareFunction ) ) {
throw new TypeError( format( 'invalid argument. First argument must be a function. Value: `%s`.', compareFunction ) );
}
clbk = compareFunction;
} else {
clbk = ascending;
}
indices.sort( wrapper );
// Create a temporary indices array which we'll reorder as we rearrange the tuple elements:
tmp = indices.slice();
// Rearrange tuple elements according to the rearranged indices (note: every "move" moves a tuple element to its desired position with runtime complexity O(N))...
for ( i = 0; i < nfields; i++ ) {
// Check if we need to move a tuple element:
if ( tmp[ i ] !== i ) {
v = tuple[ i ];
j = i;
k = tmp[ j ];
// Follow "cycles", stopping once we are back at index `i`...
while ( k !== i ) {
tuple[ j ] = tuple[ k ];
tmp[ j ] = j;
j = k;
k = tmp[ j ];
}
tuple[ j ] = v;
tmp[ j ] = j;
}
}
return tuple;
/**
* Wraps a comparison function to allow sorting the internal indices array rather than the tuple directly.
*
* @private
* @param {NonNegativeInteger} ia - first index
* @param {NonNegativeInteger} ib - second index
* @returns {*} value specifying the sort order
*/
function wrapper( ia, ib ) {
var a = tuple[ indices[ ia ] ];
var b = tuple[ indices[ ib ] ];
return clbk( a, b );
}
}
/**
* Creates a new tuple over the same underlying `ArrayBuffer` and with the same underlying data type as the host tuple.
*
* ## Notes
*
* - If the function is unable to resolve indices to a non-empty tuple subsequence, the function returns `null`.
*
* @private
* @memberof tuple
* @param {integer} [begin=0] - start element index (inclusive)
* @param {integer} [end=tuple.length] - end element index (exclusive)
* @throws {TypeError} `this` must be the host tuple
* @throws {TypeError} first argument must be an integer
* @throws {TypeError} second argument must be an integer
* @returns {(TypedArray|null)} new tuple
*/
function subtuple( begin, end ) {
var f;
var i;
var j;
var k;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
if ( arguments.length === 0 ) {
return namedtypedtuple( tuple.buffer, tuple.byteOffset, dtype );
}
i = begin;
if ( !isInteger( i ) ) {
throw new TypeError( format( 'invalid argument. First argument must be an integer. Value: `%s`.', begin ) );
}
if ( i < 0 ) {
i = nfields + i;
if ( i < 0 ) {
i = 0;
}
}
if ( arguments.length === 1 ) {
j = nfields;
} else {
j = end;
if ( !isInteger( j ) ) {
throw new TypeError( format( 'invalid argument. Second argument must be an integer. Value: `%s`.', end ) );
}
if ( j < 0 ) {
j = nfields + j;
if ( j < 0 ) {
j = 0;
}
} else if ( j > nfields ) {
j = nfields;
}
}
if ( i >= j ) {
return null;
}
f = [];
for ( k = i; k < j; k++ ) {
f.push( fields[ indices[k] ] );
}
return factory( f, opts )( tuple.buffer, tuple.byteOffset+(i*tuple.BYTES_PER_ELEMENT), dtype );
}
/**
* Serializes a tuple as JSON.
*
* @private
* @memberof tuple
* @throws {TypeError} `this` must be the host tuple
* @returns {JSON} tuple JSON representation
*/
function toJSON() {
var out;
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
out = {};
for ( i = 0; i < nfields; i++ ) {
out[ fields[i] ] = tuple[ indices[i] ];
}
return out;
}
// TODO: consider adding `toLocaleString()` in a manner similar to `toString()` below
/**
* Serializes a tuple as a string.
*
* @private
* @memberof tuple
* @throws {TypeError} `this` must be the host tuple
* @returns {string} tuple string representation
*/
function toString() {
var out;
var i;
if ( this !== tuple ) { // eslint-disable-line no-invalid-this
throw new TypeError( 'invalid invocation. `this` is not host tuple.' );
}
out = opts.name + '(';
for ( i = 0; i < nfields; i++ ) {
out += fields[ i ];
out += '=';
out += tuple[ indices[ i ] ];
if ( i < nfields-1 ) {
out += ', ';
}
}
out += ')';
return out;
}
}
// Note: keep the following methods in alphabetical order...
/**
* Creates a new tuple from an array-like object or an iterable.
*
* @private
* @name from
* @memberof namedtypedtuple
* @type {Function}
* @param {(ArrayLikeObject|Iterable)} src - array-like object or iterable
* @param {Function} [clbk] - callback to invoke for each source element
* @param {*} [thisArg] - callback execution context
* @throws {TypeError} `this` must be the host tuple factory
* @throws {TypeError} first argument must be an array-like object or an iterable
* @throws {RangeError} source must be compatible with tuple length
* @throws {TypeError} second argument must be a function
* @returns {TypedArray} new tuple
*/
defineProperty( namedtypedtuple, 'from', {
'configurable': false,
'enumerable': false,
'writable': false,
'value': function from( src ) { // eslint-disable-line no-restricted-syntax
var thisArg;
var nargs;
var tuple;
var clbk;
var tmp;
var it;
var i;
if ( this !== namedtypedtuple ) {
throw new TypeError( 'invalid invocation. `this` is not the host tuple factory.' );
}
nargs = arguments.length;
if ( nargs > 1 ) {
clbk = arguments[ 1 ];
if ( !isFunction( clbk ) ) {
throw new TypeError( format( 'invalid argument. Second argument must be a function. Value: `%s`.', clbk ) );
}
if ( nargs > 2 ) {
thisArg = arguments[ 2 ];
}
}
if ( isCollection( src ) ) {
if ( src.length !== nfields ) {
throw new RangeError( format( 'invalid argument. Source is incompatible with the number of tuple fields. Number of fields: `%u`. Source length: `%u`.', nfields, src.length ) );
}
tuple = namedtypedtuple( nfields, opts.dtype );
if ( clbk ) {
for ( i = 0; i < nfields; i++ ) {
tuple[ i ] = clbk.call( thisArg, src[ i ], i, fields[ i ] );
}
} else {
for ( i = 0; i < nfields; i++ ) {
tuple[ i ] = src[ i ];
}
}
} else if ( isObject( src ) && HAS_ITERATOR_SYMBOL && isFunction( src[ ITERATOR_SYMBOL ] ) ) {
it = src[ ITERATOR_SYMBOL ]();
if ( !isFunction( it.next ) ) {
throw new TypeError( format( 'invalid argument. First argument must be an array-like object or an iterable. Value: `%s`.', src ) );
}
if ( clbk ) {
tmp = fromIteratorMap( fields, it, clbk, thisArg );
} else {
tmp = fromIterator( it );
}
tuple = namedtypedtuple( tmp, opts.dtype );
} else {
throw new TypeError( format( 'invalid argument. First argument must be an array-like object or an iterable. Value: `%s`.', src ) );
}
return tuple;
}
});
/**
* Creates a new tuple from an object containing tuple fields.
*
* @private
* @name fromObject
* @memberof namedtypedtuple
* @type {Function}
* @param {Object} obj - source object
* @param {Function} [clbk] - callback to invoke for each source object tuple field
* @param {*} [thisArg] - callback execution context
* @throws {TypeError} `this` must be the host tuple factory
* @throws {TypeError} first argument must be an object
* @throws {TypeError} second argument must be a function
* @returns {TypedArray} new tuple
*/
defineProperty( namedtypedtuple, 'fromObject', {
'configurable': false,
'enumerable': false,
'writable': false,
'value': function fromObject( obj ) { // eslint-disable-line no-restricted-syntax
var thisArg;
var nargs;
var tuple;
var clbk;
var f;
var i;
if ( this !== namedtypedtuple ) {
throw new TypeError( 'invalid invocation. `this` is not the host tuple factory.' );
}
if ( obj === null || typeof obj !== 'object' ) {
throw new TypeError( format( 'invalid argument. First argument must be an object. Value: `%s`.', obj ) );
}
nargs = arguments.length;
if ( nargs > 1 ) {
clbk = arguments[ 1 ];
if ( !isFunction( clbk ) ) {
throw new TypeError( format( 'invalid argument. Second argument must be a function. Value: `%s`.', clbk ) );
}
if ( nargs > 2 ) {
thisArg = arguments[ 2 ];
}
}
tuple = namedtypedtuple( nfields, opts.dtype );
if ( clbk ) {
for ( i = 0; i < nfields; i++ ) {
f = fields[ i ];
if ( hasOwnProp( obj, f ) ) {
tuple[ i ] = clbk.call( thisArg, obj[ f ], f );
}
}
} else {
for ( i = 0; i < nfields; i++ ) {
f = fields[ i ];
if ( hasOwnProp( obj, f ) ) {
tuple[ i ] = obj[ f ];
}
}
}
return tuple;
}
});
/**
* Creates a new tuple from a variable number of arguments.
*
* @private
* @name of
* @memberof namedtypedtuple
* @type {Function}
* @param {...number} element - tuple elements
* @throws {TypeError} `this` must be the host tuple factory
* @throws {RangeError} incompatible number of arguments
* @returns {TypedArray} new tuple
*/
defineProperty( namedtypedtuple, 'of', {
'configurable': false,
'enumerable': false,
'writable': false,
'value': function of() { // eslint-disable-line no-restricted-syntax
var args;
var i;
if ( this !== namedtypedtuple ) {
throw new TypeError( 'invalid invocation. `this` is not the host tuple factory.' );
}
if ( arguments.length !== nfields ) {
throw new RangeError( format( 'invalid invocation. Number of arguments is incompatible with the number of tuple fields. Number of fields: `%u`. Number of arguments: `%u`.', nfields, arguments.length ) );
}
args = [];
for ( i = 0; i < arguments.length; i++ ) {
args.push( arguments[ i ] );
}
return namedtypedtuple( args );
}
});
return namedtypedtuple;
}
// EXPORTS //
module.exports = factory;