main.js (6112B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 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 isPositiveInteger = require( '@stdlib/assert/is-positive-integer' ).isPrimitive; 24 var isArrayLike = require( '@stdlib/assert/is-array-like-object' ); 25 var isPositiveZero = require( '@stdlib/math/base/assert/is-positive-zero' ); 26 var isNegativeZero = require( '@stdlib/math/base/assert/is-negative-zero' ); 27 var isnan = require( '@stdlib/math/base/assert/is-nan' ); 28 var PINF = require( '@stdlib/constants/float64/pinf' ); 29 var NINF = require( '@stdlib/constants/float64/ninf' ); 30 var Float64Array = require( '@stdlib/array/float64' ); 31 32 33 // MAIN // 34 35 /** 36 * Returns an accumulator function which incrementally computes moving minimum and maximum values. 37 * 38 * @param {Collection} [out] - output array 39 * @param {PositiveInteger} window - window size 40 * @throws {TypeError} output argument must be array-like 41 * @throws {TypeError} window size must be a positive integer 42 * @returns {Function} accumulator function 43 * 44 * @example 45 * var accumulator = incrmminmax( 3 ); 46 * 47 * var mm = accumulator(); 48 * // returns null 49 * 50 * mm = accumulator( 2.0 ); 51 * // returns [ 2.0, 2.0 ] 52 * 53 * mm = accumulator( -5.0 ); 54 * // returns [ -5.0, 2.0 ] 55 * 56 * mm = accumulator( 3.0 ); 57 * // returns [ -5.0, 3.0 ] 58 * 59 * mm = accumulator( 5.0 ); 60 * // returns [ -5.0, 5.0 ] 61 * 62 * mm = accumulator(); 63 * // returns [ -5.0, 5.0 ] 64 */ 65 function incrmminmax( out, window ) { 66 var minmax; 67 var buf; 68 var min; 69 var max; 70 var W; 71 var N; 72 var i; 73 if ( arguments.length === 1 ) { 74 minmax = [ 0.0, 0.0 ]; 75 W = out; 76 } else { 77 if ( !isArrayLike( out ) ) { 78 throw new TypeError( 'invalid argument. Output argument must be an array-like object. Value: `' + out + '`.' ); 79 } 80 minmax = out; 81 W = window; 82 } 83 if ( !isPositiveInteger( W ) ) { 84 throw new TypeError( 'invalid argument. Window size must be a positive integer. Value: `' + W + '`.' ); 85 } 86 buf = new Float64Array( W ); 87 min = PINF; 88 max = NINF; 89 i = -1; 90 N = 0; 91 92 return accumulator; 93 94 /** 95 * If provided a value, the accumulator function returns updated minimum and maximum values. If not provided a value, the accumulator function returns the current minimum and maximum values. 96 * 97 * @private 98 * @param {number} [x] - input value 99 * @returns {(ArrayLikeObject|null)} output array or null 100 */ 101 function accumulator( x ) { 102 var sgn; 103 var v; 104 var k; 105 if ( arguments.length === 0 ) { 106 if ( N === 0 ) { 107 return null; 108 } 109 return minmax; 110 } 111 // Update the index for managing the circular buffer: 112 i = (i+1) % W; 113 114 // Case: incoming value is NaN... 115 if ( isnan( x ) ) { 116 N = W; // explicitly set to avoid `N < W` branch 117 min = x; 118 max = x; 119 } 120 // Case: initial window... 121 else if ( N < W ) { 122 N += 1; 123 if ( x < min || ( x === min && isNegativeZero( x ) ) ) { 124 min = x; 125 } 126 if ( x > max || ( x === max && isPositiveZero( x ) ) ) { 127 max = x; 128 } 129 } 130 // Case: outgoing value is the current minimum or maximum and the new value is either greater than the minimum or less than the maximum, and, thus, we need to find new accumulated values among the current buffer values... 131 else if ( 132 ( buf[ i ] === min && x > min ) || 133 ( buf[ i ] === max && x < max ) || 134 isnan( buf[ i ] ) 135 ) { 136 min = x; 137 max = x; 138 for ( k = 0; k < W; k++ ) { 139 if ( k !== i ) { 140 v = buf[ k ]; 141 if ( isnan( v ) ) { 142 min = v; 143 max = v; 144 break; // no need to continue searching 145 } 146 if ( v < min || ( v === min && isNegativeZero( v ) ) ) { 147 min = v; 148 } 149 if ( v > max || ( v === max && isPositiveZero( v ) ) ) { 150 max = v; 151 } 152 } 153 } 154 } 155 // Case: incoming value is less than current minimum value... 156 else if ( x < min ) { 157 min = x; 158 } 159 // Case: incoming value is greater than current maximum value... 160 else if ( x > max ) { 161 max = x; 162 } 163 // Case: incoming value is zero, which means we need to be careful and correctly handle signed zeros... 164 else if ( x === 0.0 ) { 165 sgn = isNegativeZero( x ); 166 if ( x === min ) { 167 // Case: outgoing value is the current minimum... 168 if ( 169 buf[ i ] === min && 170 isNegativeZero( buf[ i ] ) && 171 sgn === false 172 ) { 173 // Because the outgoing and incoming are different signs (-,+), we need to search the buffer to see if it contains a negative zero. If so, the minimum value remains negative zero; otherwise, the minimum value is incoming value... 174 min = x; 175 for ( k = 0; k < W; k++ ) { 176 if ( k !== i && isNegativeZero( buf[ k ] ) ) { 177 min = buf[ k ]; 178 break; 179 } 180 } 181 } else if ( sgn ) { 182 // Ensure minimum value has the correct sign: 183 min = x; 184 } 185 } 186 if ( x === max ) { 187 // Case: outgoing value is the current maximum... 188 if ( 189 buf[ i ] === max && 190 isPositiveZero( buf[ i ] ) && 191 sgn 192 ) { 193 // Because the outgoing and incoming are different signs (+,-), we need to search the buffer to see if it contains a positive zero. If so, the maximum value remains positive zero; otherwise, the maximum value is incoming value... 194 max = x; 195 for ( k = 0; k < W; k++ ) { 196 if ( k !== i && isPositiveZero( buf[ k ] ) ) { 197 max = buf[ k ]; 198 break; 199 } 200 } 201 } else if ( sgn === false ) { 202 // Ensure maximum value has the correct sign: 203 max = x; 204 } 205 } 206 } 207 // Case: updating existing window; however, the minimum and maximum values do not change so nothing to do but update our buffer... 208 buf[ i ] = x; 209 210 minmax[ 0 ] = min; 211 minmax[ 1 ] = max; 212 return minmax; 213 } 214 } 215 216 217 // EXPORTS // 218 219 module.exports = incrmminmax;