find.js (5372B)
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 isFunction = require( '@stdlib/assert/is-function' ); 24 var isInteger = require( '@stdlib/assert/is-integer' ).isPrimitive; 25 var isObject = require( '@stdlib/assert/is-plain-object' ); 26 var isString = require( '@stdlib/assert/is-string' ).isPrimitive; 27 var isCollection = require( '@stdlib/assert/is-collection' ); 28 var hasOwnProp = require( '@stdlib/assert/has-own-property' ); 29 30 31 // MAIN // 32 33 /** 34 * Finds elements in an array-like object that satisfy a test condition. 35 * 36 * @param {(Collection|string)} arr - object from which elements will be tested 37 * @param {Options} [options] - function options 38 * @param {integer} [options.k=arr.length] - limits the number of returned elements 39 * @param {string} [options.returns='indices'] - if `values`, values are returned; if `indices`, indices are returned; if `*`, both indices and values are returned 40 * @param {Function} clbk - function invoked for each array element. If the return value is truthy, the value is considered to have satisfied the test condition. 41 * @throws {TypeError} first argument must be an array-like object 42 * @throws {TypeError} options argument must be an object 43 * @throws {TypeError} last argument must be a function 44 * @throws {TypeError} `options.k` must be an integer 45 * @throws {TypeError} `options.returns` must be a string equal to `values`, `indices` or `*` 46 * @returns {Array} array of indices, element values, or arrays of index-value pairs 47 * 48 * @example 49 * var data = [ 30, 20, 50, 60, 10 ]; 50 * var vals = find( data, condition ); 51 * // returns [ 0, 2, 3 ] 52 * 53 * function condition( val ) { 54 * return val > 20; 55 * } 56 * 57 * @example 58 * var data = [ 30, 20, 50, 60, 10 ]; 59 * var opts = { 60 * 'k': 2, 61 * 'returns': 'values' 62 * }; 63 * var vals = find( data, opts, condition ); 64 * // returns [ 30, 50 ] 65 * 66 * function condition( val ) { 67 * return val > 20; 68 * } 69 * 70 * @example 71 * var data = [ 30, 20, 50, 60, 10 ]; 72 * var vals = find( data, condition ); 73 * // returns [] 74 * 75 * function condition( val ) { 76 * return val > 1000; 77 * } 78 * 79 * @example 80 * var data = [ 30, 20, 50, 60, 10 ]; 81 * var opts = { 82 * 'k': -2, 83 * 'returns': 'values' 84 * }; 85 * var vals = find( data, opts, condition ); 86 * // returns [ 60, 50 ] 87 * 88 * function condition( val ) { 89 * return val > 20; 90 * } 91 * 92 * @example 93 * var data = [ 30, 20, 50, 60, 10 ]; 94 * var opts = { 95 * 'k': -2, 96 * 'returns': '*' 97 * }; 98 * var vals = find( data, opts, condition ); 99 * // returns [ [3, 60], [2, 50] ] 100 * 101 * function condition( val ) { 102 * return val > 20; 103 * } 104 */ 105 function find( arr, options, clbk ) { // eslint-disable-line stdlib/no-redeclare 106 var returns; 107 var count; 108 var mode; 109 var opts; 110 var len; 111 var out; 112 var ret; 113 var cb; 114 var i; 115 var k; 116 var v; 117 118 mode = 0; 119 returns = [ 'values', 'indices', '*' ]; 120 121 if ( !isCollection( arr ) && !isString( arr ) ) { 122 throw new TypeError( 'invalid argument. Must provide an array-like object. Value: `' + arr + '`' ); 123 } 124 len = arr.length; 125 if ( arguments.length < 3 ) { 126 opts = {}; 127 cb = options; 128 } else { 129 opts = options; 130 cb = clbk; 131 } 132 if ( !isFunction( cb ) ) { 133 throw new TypeError( 'invalid argument. Callback argument must be a function. Value: `' + cb + '`' ); 134 } 135 if ( !isObject( opts ) ) { 136 throw new TypeError( 'invalid argument. Options must be an object. Value: `' + opts + '`' ); 137 } 138 if ( hasOwnProp( opts, 'k' ) ) { 139 k = opts.k; 140 if ( !isInteger( k ) ) { 141 throw new TypeError( 'invalid argument. `k` must be an integer. Value: `' + k + '`' ); 142 } 143 } else { 144 k = len; 145 } 146 if ( hasOwnProp( opts, 'returns' ) ) { 147 ret = opts.returns; 148 if ( !isString( ret ) || returns.indexOf( ret ) === -1 ) { 149 throw new TypeError( 'invalid argument. `returns` option must be a string and have one of the following values: `values`, `indices`, `all`. Value: `' + ret + '`' ); 150 } 151 if ( ret === 'values' ) { 152 mode = 1; 153 } else if ( ret === '*' ) { 154 mode = 2; 155 } 156 } 157 out = []; 158 count = 0; 159 160 if ( k === 0 ) { 161 return out; 162 } 163 if ( k > 0 ) { 164 // Search moving from begin-to-end [0,1,...]: 165 for ( i = 0; i < len; i++ ) { 166 v = arr[ i ]; 167 if ( cb( v, i, arr ) ) { // eslint-disable-line callback-return 168 if ( mode === 2 ) { 169 out.push( [ i, v ] ); 170 } else if ( mode === 1 ) { 171 out.push( v ); 172 } else { 173 out.push( i ); 174 } 175 count += 1; 176 if ( count === k ) { 177 break; 178 } 179 } 180 } 181 return out; 182 } 183 // Search moving from end-to-begin [...,2,1,0]: 184 k = -k; 185 for ( i = len-1; i >= 0; i-- ) { 186 v = arr[ i ]; 187 if ( cb( v, i, arr ) ) { // eslint-disable-line callback-return 188 if ( mode === 2 ) { 189 out.push( [ i, v ] ); 190 } else if ( mode === 1 ) { 191 out.push( v ); 192 } else { 193 out.push( i ); 194 } 195 count += 1; 196 if ( count === k ) { 197 break; 198 } 199 } 200 } 201 return out; 202 } 203 204 205 // EXPORTS // 206 207 module.exports = find;