main.js (6230B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2020 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 'use strict'; 20 21 // MODULES // 22 23 var setReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' ); 24 var isFunction = require( '@stdlib/assert/is-function' ); 25 var isIteratorLike = require( '@stdlib/assert/is-iterator-like' ); 26 var isNumber = require( '@stdlib/assert/is-number' ).isPrimitive; 27 var hasOwnProp = require( '@stdlib/assert/has-own-property' ); 28 var iteratorSymbol = require( '@stdlib/symbol/iterator' ); 29 var validate = require( './validate.js' ); 30 31 32 // MAIN // 33 34 /** 35 * Returns an iterator which invokes a binary function accepting numeric arguments for each iterated value. 36 * 37 * ## Notes 38 * 39 * - When invoked, the input function is provided two arguments: 40 * 41 * - `x`: iterated value from first input iterator 42 * - `y`: iterated value from second input iterator 43 * 44 * - If provided a numeric value as an iterator argument, the value is broadcast as an **infinite** iterator which **always** returns the provided value. 45 * 46 * - If an iterated value is non-numeric (including `NaN`), the returned iterator returns `NaN`. If non-numeric iterated values are possible, you are advised to provide an iterator which type checks and handles non-numeric values accordingly. 47 * 48 * - The length of the returned iterator is equal to the length of the shortest provided iterator. In other words, the returned iterator ends once **one** of the provided iterators ends. 49 * 50 * - If an environment supports `Symbol.iterator` and all provided iterators are iterable, the returned iterator is iterable. 51 * 52 * @param {Iterator} iter0 - first input iterator 53 * @param {Iterator} iter1 - second input iterator 54 * @param {Function} fcn - function to invoke 55 * @param {Options} [options] - options 56 * @param {*} [options.invalid=NaN] - return value when an input iterator yields a non-numeric value 57 * @throws {TypeError} first argument must be an iterator protocol-compliant object 58 * @throws {TypeError} second argument must be an iterator protocol-compliant object 59 * @throws {TypeError} third argument must be a function 60 * @throws {TypeError} options argument must be an object 61 * @throws {TypeError} must provide valid options 62 * @returns {Iterator} iterator 63 * 64 * @example 65 * var randu = require( '@stdlib/random/iter/randu' ); 66 * var copysign = require( '@stdlib/math/base/special/copysign' ); 67 * 68 * var iter = iterMap2( randu(), randu(), copysign ); 69 * 70 * var r = iter.next().value; 71 * // returns <number> 72 * 73 * r = iter.next().value; 74 * // returns <number> 75 * 76 * r = iter.next().value; 77 * // returns <number> 78 * 79 * // ... 80 */ 81 function iterMap2( iter0, iter1, fcn, options ) { 82 var iterators; 83 var values; 84 var types; 85 var niter; 86 var iter; 87 var opts; 88 var FLG; 89 var err; 90 var i; 91 92 niter = 2; 93 values = [ 0.0, 0.0 ]; 94 95 iterators = []; 96 types = []; 97 for ( i = 0; i < niter; i++ ) { 98 iterators.push( arguments[ i ] ); 99 if ( isIteratorLike( arguments[ i ] ) ) { 100 types.push( 1 ); 101 } else if ( isNumber( arguments[ i ] ) ) { 102 types.push( 0 ); 103 } else { 104 throw new TypeError( 'invalid argument. Must provide an iterator protocol-compliant object or a number. Argument: `' + i + '`. Value: `' + arguments[ i ] + '`.' ); 105 } 106 } 107 if ( !isFunction( fcn ) ) { 108 throw new TypeError( 'invalid argument. Third argument must be a function. Value: `' + fcn + '`.' ); 109 } 110 opts = { 111 'invalid': NaN 112 }; 113 if ( arguments.length > 3 ) { 114 err = validate( opts, options ); 115 if ( err ) { 116 throw err; 117 } 118 } 119 // Create an iterator protocol-compliant object: 120 iter = {}; 121 setReadOnly( iter, 'next', next ); 122 setReadOnly( iter, 'return', end ); 123 124 // If an environment supports `Symbol.iterator` and all provided iterators are iterable, make the iterator iterable: 125 if ( iteratorSymbol ) { 126 for ( i = 0; i < niter; i++ ) { 127 if ( types[ i ] && !isFunction( iterators[ i ][ iteratorSymbol ] ) ) { // eslint-disable-line max-len 128 FLG = true; 129 break; 130 } 131 } 132 if ( !FLG ) { 133 setReadOnly( iter, iteratorSymbol, factory ); 134 } 135 } 136 FLG = 0; 137 return iter; 138 139 /** 140 * Returns an iterator protocol-compliant object containing the next iterated value. 141 * 142 * @private 143 * @returns {Object} iterator protocol-compliant object 144 */ 145 function next() { 146 var err; 147 var v; 148 var i; 149 if ( FLG ) { 150 return { 151 'done': true 152 }; 153 } 154 FLG = 0; 155 err = 0; 156 for ( i = 0; i < niter; i++ ) { 157 if ( types[ i ] ) { 158 v = iterators[ i ].next(); 159 if ( v.done ) { 160 FLG += 1; 161 if ( hasOwnProp( v, 'value' ) ) { 162 if ( typeof v.value === 'number' ) { 163 values[ i ] = v.value; 164 } else { 165 err = 1; 166 } 167 continue; 168 } 169 return { 170 'done': true 171 }; 172 } 173 if ( typeof v.value === 'number' ) { 174 values[ i ] = v.value; 175 } else { 176 err = 1; 177 } 178 } else { 179 values[ i ] = iterators[ i ]; 180 } 181 } 182 if ( err ) { 183 return { 184 'value': opts.invalid, 185 'done': false 186 }; 187 } 188 return { 189 'value': fcn( values[ 0 ], values[ 1 ] ), 190 'done': false 191 }; 192 } 193 194 /** 195 * Finishes an iterator. 196 * 197 * @private 198 * @param {*} [value] - value to return 199 * @returns {Object} iterator protocol-compliant object 200 */ 201 function end( value ) { 202 FLG = 1; 203 if ( arguments.length ) { 204 return { 205 'value': value, 206 'done': true 207 }; 208 } 209 return { 210 'done': true 211 }; 212 } 213 214 /** 215 * Returns a new iterator. 216 * 217 * @private 218 * @returns {Iterator} iterator 219 */ 220 function factory() { 221 var args; 222 var i; 223 224 args = []; 225 for ( i = 0; i < niter; i++ ) { 226 if ( types[ i ] ) { 227 args.push( iterators[ i ][ iteratorSymbol ]() ); 228 } else { 229 args.push( iterators[ i ] ); 230 } 231 } 232 args.push( fcn, opts ); 233 return iterMap2.apply( null, args ); 234 } 235 } 236 237 238 // EXPORTS // 239 240 module.exports = iterMap2;