├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bower.json ├── dist ├── mini-linq.full.min.js ├── mini-linq.min.js ├── mini-linq.with-knockout.min.js └── mini-linq.with-lazy.min.js ├── gruntfile.js ├── package.json ├── src ├── mini-linq.js ├── mini-linq.knockout.js └── mini-linq.lazy.js └── tests ├── bower.json ├── test-core.js ├── test-lazy.js └── tests.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Alexander Kopachov (https://www.linkedin.com/in/akopachov) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mini-linq.js 2 | ## Description 3 | LINQ for JavaScript library, which allows to work with arrays in a more easy way and focus on business logic. 4 | 5 | ## Installation 6 | Download and link manually. Or install it 7 | ``` 8 | bower install mini-linq-js --save 9 | npm install mini-linq-js --save 10 | ``` 11 | 12 | ## Usage 13 | Just link `mini-linq.js` or `mini-linq.min.js` in your html. 14 | ```html 15 | 16 | ``` 17 | You can also attach and use mini-linq with [knockout observableArray](http://knockoutjs.com/documentation/observableArrays.html). Just link `mini-linq.knockout.js`. 18 | Also you may use postponed [lazy-execution for arrays](#lazyArrays) by linking `mini-linq.lazy.js`. 19 | 20 | You also may use it in your Node.JS project by using 21 | ```javascript 22 | require('mini-linq-js'); 23 | ``` 24 | 25 | ## Available methods 26 | * [any](#any) 27 | * [all](#all) 28 | * [where](#where) 29 | * [select](#select) 30 | * [selectMany](#selectMany) 31 | * [count](#count) 32 | * [orderBy](#orderBy) 33 | * [orderByDescending](#orderByDescending) 34 | * [groupBy](#groupBy) 35 | * [distinct](#distinct) 36 | * [firstOrDefault](#firstOrDefault) 37 | * [lastOrDefault](#lastOrDefault) 38 | * [joinWith](#joinWith) 39 | * [groupJoinWith](#groupJoinWith) 40 | * [contains](#contains) 41 | * [aggregate](#aggregate) 42 | * [sum](#sum) 43 | * [min](#min) 44 | * [max](#max) 45 | * [skip](#skip) 46 | * [take](#take) 47 | * [ofType](#ofType) 48 | * [union](#union) 49 | * [except](#except) 50 | 51 | ## Terms 52 | * **Predicate** - function which accepts arguments (value, index, array) and returns: `true` if arguments matches specified business-logic conditions; `false` otherwise; 53 | * **Selector** - function which accepts arguments (value, index, array) and returns some value which should be used instead of original value. 54 | * **Comparator** - function which accepts two arguments and returns `true` if two arguments are equal and `false` otherwise. 55 | * **Order comparator** - function which accepts two arguments and returns: `1` if first argument is greater then second; `-1` if second argument is greater then first; `0` if they are equal. 56 | 57 | [Predicates](#predicate), [selectors](#selector), [comparators](#comparator) can be written in 3 ways: 58 | 59 | 1. usual way: `function(arg) { return arg * 2; }`; 60 | 2. modern way ([by using arrow functions](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions)): `arg => arg * 2`; 61 | 3. modern way with obsolete browsers support: `'arg => arg * 2'`. It's almost the same as in p.2, just wrapped as a string. _mini-linq_ core will parse this string and generated appropriate function. _**Important!** It's not possible to use closure variables using this way._ 62 | 63 | 64 | ## Methods description 65 | 66 | ### .any 67 | ###### Description: 68 | `.any` checks if there is at least one element in array which matches [predicate](#predicate). If called without [predicate](#predicate) then it checks for any element in the array. 69 | ###### Arguments: 70 | `.any` accepts [predicate](#predicate) or nothing. 71 | ###### Returns: 72 | `true` if there is at least one element which matches specified [predicate](#predicate); `false` otherwise. 73 | ###### Example of usage: 74 | ```javascript 75 | [1, 2, 3].any(); // will return true, because predicate is not passed and array is not empty 76 | [1, 2, 3].any(a => a > 2); // will return true because there is at least one element which match predicate a > 2 77 | [1, 2, 3].any(a => a < 0); // will return false. There are no elements which match predicate a < 0 78 | [].any(); // will return false. Array is empty. 79 | ``` 80 | --- 81 | 82 | ### .all 83 | ###### Description: 84 | `.all` checks if all elements in array match [predicate](#predicate). 85 | ###### Arguments: 86 | `.all` accepts [predicate](#predicate). 87 | ###### Returns: 88 | `true` if all elements match specified [predicate](#predicate); `false` otherwise. 89 | ###### Example of usage: 90 | ```javascript 91 | [1, 2, 3].all(a => a > 0); // will return true because all elements matches predicate a > 0 92 | [1, 2, 3].all(a => a > 2); // will return false, because not all elements matches predicate a > 2 93 | ``` 94 | --- 95 | 96 | ### .where 97 | ###### Description: 98 | `.where` selects all elements which match [predicate](#predicate). 99 | ###### Arguments: 100 | `.where` accepts [predicate](#predicate). 101 | ###### Returns: 102 | Array of elements which match [predicate](#predicate). Or empty array if there are no such elements. 103 | ###### Example of usage: 104 | ```javascript 105 | [1, 2, 3, 4].where(a => a > 2); // will return [3, 4] 106 | [1, 2, 3, 4].where(a => a > 5); // will return [] (empty array) 107 | ``` 108 | --- 109 | 110 | ### .select 111 | ###### Description: 112 | `.select` produces new array by applying [selector](#selector) for each element. 113 | ###### Arguments: 114 | `.select` accepts [selector](#selector). 115 | ###### Returns: 116 | Array of elements produced by applying [selector](#selector). Or empty array if there are no elements. 117 | ###### Example of usage: 118 | ```javascript 119 | [1, 2, 3, 4].select(s => s * 10); // will return [10, 20, 30, 40] 120 | [].select(a => a * 10); // will return [] (empty array) 121 | ``` 122 | --- 123 | 124 | ### .selectMany 125 | ###### Description: 126 | `.selectMany` produces new array by applying [selector](#selector) for each element and combining selected arrays together into one single array. 127 | ###### Arguments: 128 | `.selectMany` accepts [selector](#selector). 129 | ###### Returns: 130 | Array of elements produced by applying [selector](#selector). Or empty array if there are no elements. 131 | ###### Example of usage: 132 | ```javascript 133 | var testArray = [ 134 | { x: [1, 2], y: 0 }, 135 | { x: [3, 4], y: 1 }, 136 | { x: [5, 6], y: 2 }]; 137 | testArray.selectMany(sm => sm.x); // will return [1, 2, 3, 4, 5, 6] 138 | testArray.selectMany(sm => sm.y); // will return [] because 'y' fields are not arrays 139 | ``` 140 | --- 141 | 142 | ### .count 143 | ###### Description: 144 | `.count` calculates count of elements which match [predicate](#predicate). If called without [predicate](#predicate) then it will return total count of elements. 145 | ###### Arguments: 146 | `.count` accepts [predicate](#predicate) or nothing. 147 | ###### Returns: 148 | Count of elements which match specified [predicate](#predicate). Or total count of elements if [predicate](#predicate) is not specified. 149 | ###### Example of usage: 150 | ```javascript 151 | [1, 2, 3, 4].count(s => c > 2); // will return 2 152 | [1, 2, 3, 4].count(); // will return 4 153 | ``` 154 | --- 155 | 156 | ### .orderBy 157 | ###### Description: 158 | `.orderBy` order elements in ascending order by using [selector](#selector) and [order comparator](#orderComparator) (if specified). 159 | ###### Arguments: 160 | `.orderBy` accepts [selector](#selector) as first argument and may accept [order comparator](#orderComparator) as a second argument. 161 | ###### Returns: 162 | Array of ordered elements. 163 | ###### Example of usage: 164 | ```javascript 165 | [2, 1, 4, 3].orderBy(s => s); // will return [1, 2, 3, 4] 166 | [2, 1, 4, 3].orderBy(s => s, (first, second) => first - second); // will return [1, 2, 3, 4] 167 | ``` 168 | --- 169 | 170 | ### .orderByDescending 171 | ###### Description: 172 | `.orderByDescending` order elements in descending order by using [selector](#selector) and [order comparator](#orderComparator) (if specified). 173 | ###### Arguments: 174 | `.orderByDescending` accepts [selector](#selector) as first argument and may accept [order comparator](#orderComparator) as a second argument. 175 | ###### Returns: 176 | Array of ordered elements. 177 | ###### Example of usage: 178 | ```javascript 179 | [2, 1, 4, 3].orderByDescending(s => s); // will return [4, 3, 2, 1] 180 | [2, 1, 4, 3].orderByDescending(s => s, (first, second) => first - second); // will return [4, 3, 2, 1] 181 | ``` 182 | --- 183 | 184 | ### .groupBy 185 | ###### Description: 186 | `.groupBy` group elements by specified [selector](#selector) as a key. 187 | ###### Arguments: 188 | `.groupBy` accepts [selector](#selector) as first argument and may accept result [selector](#selector) as a second argument. If result [selector](#selector) is not specified then `(group, values) => { group: group, values: values }` selector will be used. 189 | ###### Returns: 190 | Array of grouped elements. 191 | ###### Example of usage: 192 | ```javascript 193 | [2, 1, 4, 3, 5, 6].groupBy(s => s % 2); // will return [{group: '0', values: [2, 4, 6]}, {group: '1', values: [1, 3, 5]}] 194 | [2, 1, 4, 3, 5, 6].groupBy(s => s % 2, (group, values) => (group == 0 ? 'even: ' : 'odd: ') + values); // Will return ["even: 2,4,6", "odd: 1,3,5"] 195 | ``` 196 | --- 197 | 198 | ### .distinct 199 | ###### Description: 200 | `.distinct` selects distinct elements by using [selector](#selector) as a key or element if [selector](#selector) is not specified, and [comparator](#comparator) (optional) to compare equality of keys 201 | ###### Arguments: 202 | `.distinct` may accept [selector](#selector) as a first argument and [comparator](#comparator) as a second argument. 203 | ###### Returns: 204 | Array of distinct elements. 205 | ###### Example of usage: 206 | ```javascript 207 | [2, 1, 2, 3, 1, 6, 7, 3, 2].distinct(); // will return [2, 1, 3, 6, 7] 208 | [2, 1, 2, 3, 1, 6, 7, 3, 2].distinct(d => d % 3); // will return [2, 1, 3] 209 | [1, 2, '2', '3', 3, 4, 5, 8, 5].distinct(); // will return [1, 2, '2', '3', 3, 4, 5, 8] (default comparator is "a === b"; 210 | [1, 2, '2', '3', 3, 4, 5, 8, 5].distinct('x => x', '(a, b) => a == b'); // will return [1, 2, '3', 4, 5, 8] (here we used custom comparator) 211 | ``` 212 | --- 213 | 214 | ### .firstOrDefault 215 | ###### Description: 216 | `.firstOrDefault` selects first element which matches [predicate](#predicate) if there is not such element, then `null` will be returned. If predicate is not specified then first element will be returned or `null` if array is empty. 217 | ###### Arguments: 218 | `.firstOrDefault` may accept [predicate](#predicate). 219 | ###### Returns: 220 | First element which matches [predicate](#predicate) or `null` if there is no such element. If predicate is not specified then first element will be returned or `null` if array is empty. 221 | ###### Example of usage: 222 | ```javascript 223 | [2, 1, 2, 3, 1, 6, 7, 3, 2].firstOrDefault(f => f % 2 == 1); // will return 1 224 | [2, 1, 2, 3, 1, 6, 7, 3, 2].firstOrDefault() // will return 2 225 | [2, 1, 2, 3, 1, 6, 7, 3, 2].firstOrDefault(f => f < 0) // will return null 226 | [].firstOrDefault() // will return null 227 | ``` 228 | --- 229 | 230 | ### .lastOrDefault 231 | ###### Description: 232 | `.lastOrDefault` selects last element which matches [predicate](#predicate) if there is not such element, then `null` will be returned. If predicate is not specified then last element will be returned or `null` if array is empty. 233 | ###### Arguments: 234 | `.lastOrDefault` may accept [predicate](#predicate). 235 | ###### Returns: 236 | Last element which matches [predicate](#predicate) or `null` if there is no such element. If predicate is not specified then last element will be returned or `null` if array is empty. 237 | ###### Example of usage: 238 | ```javascript 239 | [2, 1, 2, 3, 1, 6, 7, 3, 2].lastOrDefault(f => f % 2 == 1); // will return 3 240 | [2, 1, 2, 3, 1, 6, 7, 3, 9].lastOrDefault() // will return 9 241 | [2, 1, 2, 3, 1, 6, 7, 3, 2].lastOrDefault(f => f < 0) // will return null 242 | [].lastOrDefault() // will return null 243 | ``` 244 | --- 245 | 246 | ### .joinWith 247 | ###### Description: 248 | `.joinWith` combines two arrays based upon the [inner key selector](#selector) and [outer key selector](#selector). 249 | ###### Arguments: 250 | `.joinWith` accepts following arguments (1-4 are mandatory, 5-th is optional): 251 | 252 | 1. inner array to join with; 253 | 2. inner key [selector](#selector) which will be applied to inner array elements; 254 | 3. outer key [selector](#selector) which will be applied to outer array elements; 255 | 4. result [selector](#selector) which should accept two arguments (inner element and outer element) and return result element; 256 | 5. key [comparator](#comparator) which implements comparation logic between inner key and outer key. (optional) 257 | 258 | ###### Returns: 259 | Array of combined elements. 260 | ###### Example of usage: 261 | ```javascript 262 | [1, 2, 8, 2, 6, 3, 9, 2, 4].joinWith([1, 2, 3, 4], 'ik => ik', 'ok => ok', '(i, o) => i'); // will return [1, 2, 2, 3, 2, 4] 263 | [1, 2, 3].joinWith([4, 5, 6], ik => true, ok => true, (i, o) => '' + i + o); // will return ["41", "51", "61", "42", "52", "62", "43", "53", "63"] 264 | [1, 2, 3].joinWith([1, 2, 3], ik => ik + 1, ok => ok, (i, o) => i); // will return [1, 2] 265 | [1, 2, 3].joinWith([4, 5, 6], ik => ik, ok => ok, (i, o) => i) // will return [] 266 | ``` 267 | --- 268 | 269 | ### .groupJoinWith 270 | ###### Description: 271 | `.groupJoinWith` correlates the elements of two arrays based on equality of keys and groups the results. 272 | ###### Arguments: 273 | `.groupJoinWith` accepts following arguments (1-4 are mandatory, 5-th is optional): 274 | 275 | 1. inner array to join with; 276 | 2. inner key [selector](#selector) which will be applied to inner array elements; 277 | 3. outer key [selector](#selector) which will be applied to outer array elements; 278 | 4. result [selector](#selector) which should accept two arguments (array of matched inner element and outer element) and return result element; 279 | 5. key [comparator](#comparator) which implements comparation logic between inner key and outer key. (optional) 280 | 281 | ###### Returns: 282 | Array of combined elements. 283 | ###### Example of usage: 284 | ```javascript 285 | [1, 2, 3, 4].groupJoinWith([1, 2, 3, 1, 2, 3], 'ik => ik', 'ok => ok', '(g, o) => g'); // will return [[1, 1], [2, 2], [3, 3], []] 286 | [1, 2, 3, 4].groupJoinWith([], 'ik => ik', 'ok => ok', '(g, o) => o'); // will return [1, 2, 3, 4] 287 | [].groupJoinWith([1, 2, 3, 1, 2, 3], 'ik => ik', 'ok => ok', '(g, o) => g'); // will return [] 288 | ``` 289 | --- 290 | 291 | ### .contains 292 | ###### Description: 293 | `.contains` checks if passed value presents in array. 294 | ###### Arguments: 295 | `.contains` accepts value and may accept [comparator](#comparator) as a second argument. If [comparator](#comparator) is not passed then `(a, b) => a === b` [comparator](#comparator) will be used by default. 296 | ###### Returns: 297 | `true` if value presents in array; `false` otherwise. 298 | ###### Example of usage: 299 | ```javascript 300 | [1, 2, 3, 4].contains(3); // will return true 301 | [1, 2, 3, 4].contains(5); // will return false 302 | [1, 2, 3, 4].contains('2'); // will return false, comparator is not passed, so === equality has been used. 303 | [1, 2, 3, 4].contains('2', (a, b) => a == b); // will return true 304 | ``` 305 | --- 306 | 307 | ### .aggregate 308 | ###### Description: 309 | `.aggregate` applies an accumulator function over a sequence. It acts the same as [`Array.prototype.reduce`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce). 310 | ###### Arguments: 311 | `.aggregate` accepts accumulator function, which should accept two arguments: previous result and current element. Also may accept initial value as a second argument. 312 | ###### Returns: 313 | Aggregated result. 314 | ###### Example of usage: 315 | ```javascript 316 | [1, 2, 3, 4].aggregate((c, n) => c + n); // will return 10 317 | [1, 2, 3, 4].aggregate((c, n) => c + n, 10); // will return 20 (because initial value is passed) 318 | [].aggregate((c, n) => c + n); // will return undefined 319 | [].aggregate((c, n) => c + n, 0); // will return 0 320 | ``` 321 | --- 322 | 323 | ### .sum 324 | ###### Description: 325 | `.sum` calculates total sum of elements using [selector](#selector) if specified. 326 | ###### Arguments: 327 | `.sum` may accept [selector](#selector). 328 | ###### Returns: 329 | Total sum. 330 | ###### Example of usage: 331 | ```javascript 332 | [1, 2, 3, 4].sum(); // will return 10 333 | [1, 2, 3, 4].sum(s => s); // will return 10 334 | [1, 2, 3, 4].sum(s => s * 10); // will return 100 335 | ``` 336 | --- 337 | 338 | ### .min 339 | ###### Description: 340 | `.min` finds minimum value using [selector](#selector) if specified. 341 | ###### Arguments: 342 | `.min` may accept [selector](#selector). 343 | ###### Returns: 344 | Minimum value. Or `undefined` if array is empty. 345 | ###### Example of usage: 346 | ```javascript 347 | "the quick brown fox jumps over the lazy dog".split(' ').min('s => s.length'); // will return 3 348 | [].min(); // will return undefined 349 | [9, 5, 1, 9, 3, 5, 6].min(); // will return 1 350 | ``` 351 | --- 352 | 353 | ### .max 354 | ###### Description: 355 | `.max` finds maximum value using [selector](#selector) if specified. 356 | ###### Arguments: 357 | `.max` may accept [selector](#selector). 358 | ###### Returns: 359 | Maximum value. Or `undefined` if array is empty. 360 | ###### Example of usage: 361 | ```javascript 362 | "the quick brown fox jumps over the lazy dog".split(' ').max('s => s.length'); // will return 5 363 | [].max(); // will return undefined 364 | [9, 5, 1, 9, 3, 5, 6].max(); // will return 9 365 | ``` 366 | --- 367 | 368 | ### .skip 369 | ###### Description: 370 | `.skip` skips specified amount of elements. 371 | ###### Arguments: 372 | `.skip` accepts number of elements to skip. 373 | ###### Returns: 374 | Array of elements after skipped elements. 375 | ###### Example of usage: 376 | ```javascript 377 | [1, 2, 3, 4].skip(2); // will return [3, 4] 378 | [1, 2, 3, 4].skip(0); // will return [1, 2, 3, 4] 379 | [1, 2, 3, 4].skip(9); // will return [] 380 | ``` 381 | --- 382 | 383 | ### .take 384 | ###### Description: 385 | `.take` takes specified amount of elements. 386 | ###### Arguments: 387 | `.take` accepts number of elements to take. 388 | ###### Returns: 389 | Array of taken elements. 390 | ###### Example of usage: 391 | ```javascript 392 | [1, 2, 3, 4].take(2); // will return [1, 2] 393 | [1, 2, 3, 4].take(0); // will return [] 394 | [1, 2, 3, 4].take(9); // will return [1, 2, 3, 4] 395 | ``` 396 | --- 397 | 398 | ### .ofType 399 | ###### Description: 400 | `.ofType` filter elements based on specified type. Basically it's a shortcut for `.where(w => typeof(w) === specifiedType)`. 401 | ###### Arguments: 402 | `.ofType` accepts the type to filter the elements on. 403 | ###### Returns: 404 | Array of elements of specified type. 405 | ###### Example of usage: 406 | ```javascript 407 | [1, '2', '3', 4].ofType('string'); // will return ['2', '3'] 408 | [1, '2', '3', 4].ofType('number'); // will return [1, 4] 409 | [1, '2', '3', 4].ofType('object'); // will return []; 410 | ``` 411 | --- 412 | 413 | ### .union 414 | ###### Description: 415 | `.union` produces the set union of arrays by using the [comparator](#comparator) (optional) to compare values. 416 | ###### Arguments: 417 | `.union` accepts array to combine and may accept [comparator](#comparator) as a second argument. 418 | ###### Returns: 419 | Array of merged elements from source arrays. 420 | ###### Example of usage: 421 | ```javascript 422 | [1, 2, 3, 4].union([2, 3, 4, 5]); // will return [1, 2, 3, 4, 5] 423 | [1, 2, 3, 4].union([]); // will return [1, 2, 3, 4] 424 | [].union([]); // will return [] 425 | [1, 2, 3, 4].union([2, '3', '4', 5], '(a, b) => a == b'); // will return [1, 2, 3, 4, 5]; 426 | ``` 427 | --- 428 | 429 | ### .except 430 | ###### Description: 431 | `.except` produces the set difference of two arrays by using the [comparator](#comparator) (optional) to compare values. 432 | ###### Arguments: 433 | `.except` accepts array to compare and may accept [comparator](#comparator) as a second argument. 434 | ###### Returns: 435 | Array of differences of source arrays. 436 | ###### Example of usage: 437 | ```javascript 438 | [1, 2, 3, 4].except([3, 4, 5]); // will return [1, 2] 439 | [1, 2, 3, 4].except([5, 6, 7]); // will return [1, 2, 3, 4] 440 | [1, 2, 3, 4].except([1, 2, 3, 4]); // will return [] 441 | [1, 2, 3, 4].except(['3', 4, '5'], '(a, b) => a == b'); // will return [1, 2] 442 | ``` 443 | --- 444 | 445 | ## Lazy array 446 | By default all mini-linq methods will execute their logic instantly and return result. But in some cases it may be useful to postpone execution, until some conditions take place. 447 | To do this with mini-linq it's necesasry to load `mini-linq.lazy.js` module. After that it will be possible to use `.toLazy()` to cast array to lazy array. `.toLazy()` doesn't accept any argument and returns `LazyArray` instance. `LazyArray` has the same mini-linq methods as normal array with the exception that some of them are not executing instantly. To get final result array you have to use `.toArray()` method. For example: 448 | ```javascript 449 | [1, 2, 3, 4].toLazy(); // will return LazyArray instance 450 | [1, 2, 3, 4].toLazy().where(w => w > 2); // will return LazyArray instance, .where is postponed, nothing executed. 451 | [1, 2, 3, 4].toLazy().where(w => w > 2).toArray(); // will return [3, 4] 452 | ``` 453 | LazyArray has some optimization logic, to reduce amount of array traversals, for example: 454 | ```javascript 455 | [1, 2, 3, 4].toLazy().where(w => w > 1).where(w => w < 4).toArray(); // will merge two .where methods into one, then execute and return [2, 3] (with just one traversal) 456 | ``` 457 | 458 | ## Author 459 | [Alexander Kopachov](https://www.linkedin.com/in/akopachov) (alex.kopachov@gmail.com) 460 | 461 | ## License 462 | [MIT](https://raw.githubusercontent.com/akopachov/mini-linq-js/master/LICENSE) 463 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-linq-js", 3 | "version": "1.3.2", 4 | "description": "LINQ for JavaScript library, which allows to work with arrays in a more easy way and focus on business logic.", 5 | "main": "dist/mini-linq.full.min.js", 6 | "authors": [ 7 | "Alex Kopachov" 8 | ], 9 | "license": "MIT", 10 | "keywords": [ 11 | "linq", 12 | "array" 13 | ], 14 | "homepage": "https://github.com/akopachov/mini-linq-js" 15 | } 16 | -------------------------------------------------------------------------------- /dist/mini-linq.full.min.js: -------------------------------------------------------------------------------- 1 | /* ! https://github.com/akopachov/mini-linq-js */ 2 | /* ! 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2016 Alexander Kopachov (https://www.linkedin.com/in/akopachov) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | !function(){var stringTrim=function(a){return"string"!=typeof a||null===a?a:String.prototype.trim?a.trim():a.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")},expressionCache={},LINQ={utils:{parseExpression:function(a){if("function"==typeof a)return a;if(null==a||"string"!=typeof a||a.indexOf("=>")<0)throw new SyntaxError('Expression "'+a+'" is invalid');if("function"==typeof expressionCache[a])return expressionCache[a];var b,c=a.split("=>"),d=stringTrim(c[0]).replace(/[\(\)\s]/gi,""),e=stringTrim(c[1]);try{e.indexOf("return")<0&&(e="return ("+e+")"),b=new Function(d,e)}catch(f){b=new Function(d,e)}if("function"!=typeof b)throw new SyntaxError('Expression "'+a+'" is invalid');return expressionCache[a]=b,b},getType:function(a){var b=typeof a;if("object"!==b)return b;if(null===a)return"null";var c=a.constructor,d="function"==typeof c&&c.name;return"string"==typeof d&&d.length>0?d:"object"},getDefaultValue:function(type){if("string"!=typeof type)throw new TypeError("Type must be a string.");switch(type){case"boolean":return!1;case"function":return function(){};case"null":return null;case"number":return 0;case"object":return{};case"string":return"";case"symbol":return Symbol();case"undefined":return}try{var ctor="function"==typeof this[type]?this[type]:eval(type);return new ctor}catch(e){return{}}},isArray:function(a){return Array.isArray?Array.isArray(a):"[object Array]"===Object.prototype.toString.call(a)}},methods:{any:function(a){if("string"==typeof a)a=LINQ.utils.parseExpression(a);else if("function"!=typeof a)return this.length>0;if("function"==typeof Array.prototype.some)return this.some(a);for(var b=0,c=this.length;bb?1:0}),this.slice(0).sort(function(c,d){var e=a(c),f=a(d);return b(e,f)})},orderByDescending:function(a,b){"string"==typeof b?b=LINQ.utils.parseExpression(b):"function"!=typeof b&&(b=function(a,b){return ab?1:0});var c=function(){return b.apply(this,arguments)*-1};return LINQ.methods.orderBy.apply(this,[a,c])},groupBy:function(a,b){if("string"==typeof a)a=LINQ.utils.parseExpression(a);else if("function"!=typeof a)throw new Error("Key selector is required");"string"==typeof b?b=LINQ.utils.parseExpression(b):"function"!=typeof b&&(b=function(a,b){return{group:a,values:b}});for(var c={},d=0,e=this.length;d=0;b--)if(a(this[b],b,this))return this[b];return null},joinWith:function(a,b,c,d,e){if("string"==typeof b)b=LINQ.utils.parseExpression(b);else if("function"!=typeof b)throw new Error("Inner key selector is required");if("string"==typeof c)c=LINQ.utils.parseExpression(c);else if("function"!=typeof c)throw new Error("Outer key selector is required");if("string"==typeof d)d=LINQ.utils.parseExpression(d);else if("function"!=typeof d)throw new Error("Results selector is required");"string"==typeof e?e=LINQ.utils.parseExpression(e):"function"!=typeof e&&(e=function(a,b){return a===b});for(var f=[],g=0,h=this.length;gf?f:b},a(this[0],0,this)])},max:function(a){if("string"==typeof a?a=LINQ.utils.parseExpression(a):"function"!=typeof a&&(a=function(a){return a}),!(this.length<=0))return LINQ.methods.aggregate.apply(this,[function(b,c,d,e){var f=a(c,d,e);return b100&&d._modificatorQueue.length>2&&d.optimize(),f(),a.utils.isArray(e)?e.slice(0):e}};Array.prototype.toLazy=function(){return new c(this)};for(var d in a.methods)a.methods.hasOwnProperty(d)&&(c.prototype[d]=function(b){return function(){var c=a.methods[b];return this._modificatorQueue.push({fn:a.methods[b],args:arguments,name:b}),c.finalize?this.toArray():this}}(d))}(LINQ),function(a){if("undefined"!=typeof ko&&ko.observableArray&&ko.observableArray.fn){Array.prototype.toObservableArray=function(){return ko.observableArray(this)},ko.observableArray.fn.toArray=function(){return this()};for(var b in a.methods)a.methods.hasOwnProperty(b)&&(ko.observableArray.fn[b]=function(b){return function(){return a.methods[b].apply(this(),arguments)}}(b))}}(LINQ)}(); -------------------------------------------------------------------------------- /dist/mini-linq.min.js: -------------------------------------------------------------------------------- 1 | /* ! https://github.com/akopachov/mini-linq-js */ 2 | /* ! 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2016 Alexander Kopachov (https://www.linkedin.com/in/akopachov) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | !function(){var stringTrim=function(a){return"string"!=typeof a||null===a?a:String.prototype.trim?a.trim():a.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")},expressionCache={},LINQ={utils:{parseExpression:function(a){if("function"==typeof a)return a;if(null==a||"string"!=typeof a||a.indexOf("=>")<0)throw new SyntaxError('Expression "'+a+'" is invalid');if("function"==typeof expressionCache[a])return expressionCache[a];var b,c=a.split("=>"),d=stringTrim(c[0]).replace(/[\(\)\s]/gi,""),e=stringTrim(c[1]);try{e.indexOf("return")<0&&(e="return ("+e+")"),b=new Function(d,e)}catch(f){b=new Function(d,e)}if("function"!=typeof b)throw new SyntaxError('Expression "'+a+'" is invalid');return expressionCache[a]=b,b},getType:function(a){var b=typeof a;if("object"!==b)return b;if(null===a)return"null";var c=a.constructor,d="function"==typeof c&&c.name;return"string"==typeof d&&d.length>0?d:"object"},getDefaultValue:function(type){if("string"!=typeof type)throw new TypeError("Type must be a string.");switch(type){case"boolean":return!1;case"function":return function(){};case"null":return null;case"number":return 0;case"object":return{};case"string":return"";case"symbol":return Symbol();case"undefined":return}try{var ctor="function"==typeof this[type]?this[type]:eval(type);return new ctor}catch(e){return{}}},isArray:function(a){return Array.isArray?Array.isArray(a):"[object Array]"===Object.prototype.toString.call(a)}},methods:{any:function(a){if("string"==typeof a)a=LINQ.utils.parseExpression(a);else if("function"!=typeof a)return this.length>0;if("function"==typeof Array.prototype.some)return this.some(a);for(var b=0,c=this.length;bb?1:0}),this.slice(0).sort(function(c,d){var e=a(c),f=a(d);return b(e,f)})},orderByDescending:function(a,b){"string"==typeof b?b=LINQ.utils.parseExpression(b):"function"!=typeof b&&(b=function(a,b){return ab?1:0});var c=function(){return b.apply(this,arguments)*-1};return LINQ.methods.orderBy.apply(this,[a,c])},groupBy:function(a,b){if("string"==typeof a)a=LINQ.utils.parseExpression(a);else if("function"!=typeof a)throw new Error("Key selector is required");"string"==typeof b?b=LINQ.utils.parseExpression(b):"function"!=typeof b&&(b=function(a,b){return{group:a,values:b}});for(var c={},d=0,e=this.length;d=0;b--)if(a(this[b],b,this))return this[b];return null},joinWith:function(a,b,c,d,e){if("string"==typeof b)b=LINQ.utils.parseExpression(b);else if("function"!=typeof b)throw new Error("Inner key selector is required");if("string"==typeof c)c=LINQ.utils.parseExpression(c);else if("function"!=typeof c)throw new Error("Outer key selector is required");if("string"==typeof d)d=LINQ.utils.parseExpression(d);else if("function"!=typeof d)throw new Error("Results selector is required");"string"==typeof e?e=LINQ.utils.parseExpression(e):"function"!=typeof e&&(e=function(a,b){return a===b});for(var f=[],g=0,h=this.length;gf?f:b},a(this[0],0,this)])},max:function(a){if("string"==typeof a?a=LINQ.utils.parseExpression(a):"function"!=typeof a&&(a=function(a){return a}),!(this.length<=0))return LINQ.methods.aggregate.apply(this,[function(b,c,d,e){var f=a(c,d,e);return b (https://www.linkedin.com/in/akopachov) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | !function(){var stringTrim=function(a){return"string"!=typeof a||null===a?a:String.prototype.trim?a.trim():a.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")},expressionCache={},LINQ={utils:{parseExpression:function(a){if("function"==typeof a)return a;if(null==a||"string"!=typeof a||a.indexOf("=>")<0)throw new SyntaxError('Expression "'+a+'" is invalid');if("function"==typeof expressionCache[a])return expressionCache[a];var b,c=a.split("=>"),d=stringTrim(c[0]).replace(/[\(\)\s]/gi,""),e=stringTrim(c[1]);try{e.indexOf("return")<0&&(e="return ("+e+")"),b=new Function(d,e)}catch(f){b=new Function(d,e)}if("function"!=typeof b)throw new SyntaxError('Expression "'+a+'" is invalid');return expressionCache[a]=b,b},getType:function(a){var b=typeof a;if("object"!==b)return b;if(null===a)return"null";var c=a.constructor,d="function"==typeof c&&c.name;return"string"==typeof d&&d.length>0?d:"object"},getDefaultValue:function(type){if("string"!=typeof type)throw new TypeError("Type must be a string.");switch(type){case"boolean":return!1;case"function":return function(){};case"null":return null;case"number":return 0;case"object":return{};case"string":return"";case"symbol":return Symbol();case"undefined":return}try{var ctor="function"==typeof this[type]?this[type]:eval(type);return new ctor}catch(e){return{}}},isArray:function(a){return Array.isArray?Array.isArray(a):"[object Array]"===Object.prototype.toString.call(a)}},methods:{any:function(a){if("string"==typeof a)a=LINQ.utils.parseExpression(a);else if("function"!=typeof a)return this.length>0;if("function"==typeof Array.prototype.some)return this.some(a);for(var b=0,c=this.length;bb?1:0}),this.slice(0).sort(function(c,d){var e=a(c),f=a(d);return b(e,f)})},orderByDescending:function(a,b){"string"==typeof b?b=LINQ.utils.parseExpression(b):"function"!=typeof b&&(b=function(a,b){return ab?1:0});var c=function(){return b.apply(this,arguments)*-1};return LINQ.methods.orderBy.apply(this,[a,c])},groupBy:function(a,b){if("string"==typeof a)a=LINQ.utils.parseExpression(a);else if("function"!=typeof a)throw new Error("Key selector is required");"string"==typeof b?b=LINQ.utils.parseExpression(b):"function"!=typeof b&&(b=function(a,b){return{group:a,values:b}});for(var c={},d=0,e=this.length;d=0;b--)if(a(this[b],b,this))return this[b];return null},joinWith:function(a,b,c,d,e){if("string"==typeof b)b=LINQ.utils.parseExpression(b);else if("function"!=typeof b)throw new Error("Inner key selector is required");if("string"==typeof c)c=LINQ.utils.parseExpression(c);else if("function"!=typeof c)throw new Error("Outer key selector is required");if("string"==typeof d)d=LINQ.utils.parseExpression(d);else if("function"!=typeof d)throw new Error("Results selector is required");"string"==typeof e?e=LINQ.utils.parseExpression(e):"function"!=typeof e&&(e=function(a,b){return a===b});for(var f=[],g=0,h=this.length;gf?f:b},a(this[0],0,this)])},max:function(a){if("string"==typeof a?a=LINQ.utils.parseExpression(a):"function"!=typeof a&&(a=function(a){return a}),!(this.length<=0))return LINQ.methods.aggregate.apply(this,[function(b,c,d,e){var f=a(c,d,e);return b (https://www.linkedin.com/in/akopachov) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | !function(){var stringTrim=function(a){return"string"!=typeof a||null===a?a:String.prototype.trim?a.trim():a.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")},expressionCache={},LINQ={utils:{parseExpression:function(a){if("function"==typeof a)return a;if(null==a||"string"!=typeof a||a.indexOf("=>")<0)throw new SyntaxError('Expression "'+a+'" is invalid');if("function"==typeof expressionCache[a])return expressionCache[a];var b,c=a.split("=>"),d=stringTrim(c[0]).replace(/[\(\)\s]/gi,""),e=stringTrim(c[1]);try{e.indexOf("return")<0&&(e="return ("+e+")"),b=new Function(d,e)}catch(f){b=new Function(d,e)}if("function"!=typeof b)throw new SyntaxError('Expression "'+a+'" is invalid');return expressionCache[a]=b,b},getType:function(a){var b=typeof a;if("object"!==b)return b;if(null===a)return"null";var c=a.constructor,d="function"==typeof c&&c.name;return"string"==typeof d&&d.length>0?d:"object"},getDefaultValue:function(type){if("string"!=typeof type)throw new TypeError("Type must be a string.");switch(type){case"boolean":return!1;case"function":return function(){};case"null":return null;case"number":return 0;case"object":return{};case"string":return"";case"symbol":return Symbol();case"undefined":return}try{var ctor="function"==typeof this[type]?this[type]:eval(type);return new ctor}catch(e){return{}}},isArray:function(a){return Array.isArray?Array.isArray(a):"[object Array]"===Object.prototype.toString.call(a)}},methods:{any:function(a){if("string"==typeof a)a=LINQ.utils.parseExpression(a);else if("function"!=typeof a)return this.length>0;if("function"==typeof Array.prototype.some)return this.some(a);for(var b=0,c=this.length;bb?1:0}),this.slice(0).sort(function(c,d){var e=a(c),f=a(d);return b(e,f)})},orderByDescending:function(a,b){"string"==typeof b?b=LINQ.utils.parseExpression(b):"function"!=typeof b&&(b=function(a,b){return ab?1:0});var c=function(){return b.apply(this,arguments)*-1};return LINQ.methods.orderBy.apply(this,[a,c])},groupBy:function(a,b){if("string"==typeof a)a=LINQ.utils.parseExpression(a);else if("function"!=typeof a)throw new Error("Key selector is required");"string"==typeof b?b=LINQ.utils.parseExpression(b):"function"!=typeof b&&(b=function(a,b){return{group:a,values:b}});for(var c={},d=0,e=this.length;d=0;b--)if(a(this[b],b,this))return this[b];return null},joinWith:function(a,b,c,d,e){if("string"==typeof b)b=LINQ.utils.parseExpression(b);else if("function"!=typeof b)throw new Error("Inner key selector is required");if("string"==typeof c)c=LINQ.utils.parseExpression(c);else if("function"!=typeof c)throw new Error("Outer key selector is required");if("string"==typeof d)d=LINQ.utils.parseExpression(d);else if("function"!=typeof d)throw new Error("Results selector is required");"string"==typeof e?e=LINQ.utils.parseExpression(e):"function"!=typeof e&&(e=function(a,b){return a===b});for(var f=[],g=0,h=this.length;gf?f:b},a(this[0],0,this)])},max:function(a){if("string"==typeof a?a=LINQ.utils.parseExpression(a):"function"!=typeof a&&(a=function(a){return a}),!(this.length<=0))return LINQ.methods.aggregate.apply(this,[function(b,c,d,e){var f=a(c,d,e);return b100&&d._modificatorQueue.length>2&&d.optimize(),f(),a.utils.isArray(e)?e.slice(0):e}};Array.prototype.toLazy=function(){return new c(this)};for(var d in a.methods)a.methods.hasOwnProperty(d)&&(c.prototype[d]=function(b){return function(){var c=a.methods[b];return this._modificatorQueue.push({fn:a.methods[b],args:arguments,name:b}),c.finalize?this.toArray():this}}(d))}(LINQ)}(); -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | var license = grunt.file.read('LICENSE'); 3 | var githubUrl = 'https://github.com/akopachov/mini-linq-js'; 4 | grunt.initConfig({ 5 | copy: { 6 | core: { 7 | src: 'src/mini-linq.js', 8 | dest: 'dist/mini-linq.min.js', 9 | options: { 10 | process: function (content, srcpath) { 11 | return content.replace('/* ADDITIONAL_ATTACHMENTS */', ''); 12 | } 13 | } 14 | }, 15 | coreAndLazy: { 16 | src: 'src/mini-linq.js', 17 | dest: 'dist/mini-linq.with-lazy.min.js', 18 | options: { 19 | process: function (content, srcpath) { 20 | return content.replace('/* ADDITIONAL_ATTACHMENTS */', grunt.file.read('src/mini-linq.lazy.js')); 21 | } 22 | } 23 | }, 24 | coreAndKnockout: { 25 | src: 'src/mini-linq.js', 26 | dest: 'dist/mini-linq.with-knockout.min.js', 27 | options: { 28 | process: function (content, srcpath) { 29 | return content.replace('/* ADDITIONAL_ATTACHMENTS */', grunt.file.read('src/mini-linq.knockout.js')); 30 | } 31 | } 32 | }, 33 | full: { 34 | src: 'src/mini-linq.js', 35 | dest: 'dist/mini-linq.full.min.js', 36 | options: { 37 | process: function (content, srcpath) { 38 | return content.replace('/* ADDITIONAL_ATTACHMENTS */', grunt.file.read('src/mini-linq.lazy.js') + grunt.file.read('src/mini-linq.knockout.js')); 39 | } 40 | } 41 | } 42 | }, 43 | uglify: { 44 | options: { 45 | preserveComments: false, 46 | banner: '/* ! ' + githubUrl + ' */\n/* !\n' + license + '\n */\n' 47 | }, 48 | build: { 49 | files: [{ 50 | expand: true, 51 | cwd: 'dist/', 52 | src: '*.js', 53 | dest: 'dist/' 54 | }] 55 | } 56 | } 57 | }); 58 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 59 | 60 | grunt.registerTask('default', ['copy', 'uglify']); 61 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-linq-js", 3 | "version": "1.3.2", 4 | "description": "LINQ for JavaScript library, which allows to work with arrays in a more easy way and focus on business logic.", 5 | "homepage": "https://github.com/akopachov/mini-linq-js", 6 | "keywords": [ 7 | "linq", 8 | "array" 9 | ], 10 | "license": "MIT", 11 | "main": "dist/mini-linq.full.min.js", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/akopachov/mini-linq-js.git" 15 | }, 16 | "author": { 17 | "name": "Alex Kopachov", 18 | "email": "alex.kopachov@gmail.com", 19 | "url": "https://www.linkedin.com/in/akopachov" 20 | }, 21 | "files": [ 22 | "dist", 23 | "src", 24 | "README.md", 25 | "tests", 26 | "gruntfile.js" 27 | ], 28 | "devDependencies": { 29 | "grunt": "^1.0.1", 30 | "grunt-contrib-copy": "^1.0.0", 31 | "grunt-contrib-uglify": "^1.0.1", 32 | "matchdep": "^1.0.1", 33 | "mocha": "^2.5.0", 34 | "unit.js": "^2.0.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/mini-linq.js: -------------------------------------------------------------------------------- 1 | /* ! https://github.com/akopachov/mini-linq-js */ 2 | /* ! 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2016 Alexander Kopachov (https://www.linkedin.com/in/akopachov) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | (function () { 26 | var stringTrim = function(str) { 27 | if (typeof(str) !== 'string' || str === null) { 28 | return str; 29 | } 30 | 31 | if (!String.prototype.trim) { 32 | return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); 33 | } 34 | 35 | return str.trim(); 36 | }; 37 | var expressionCache = {}; 38 | var LINQ = { 39 | utils: { 40 | parseExpression: function (expression) { 41 | if (typeof (expression) === "function") { 42 | return expression; 43 | } 44 | 45 | if (expression == null || typeof (expression) !== 'string' || expression.indexOf('=>') < 0) { 46 | throw new SyntaxError('Expression "' + expression + '" is invalid'); 47 | } 48 | 49 | if (typeof(expressionCache[expression]) === "function") { 50 | return expressionCache[expression]; 51 | } 52 | 53 | var parts = expression.split('=>'); 54 | var args = stringTrim(parts[0]).replace(/[\(\)\s]/gi, ''); 55 | var body = stringTrim(parts[1]); 56 | var expressionFn; 57 | 58 | try { 59 | if (body.indexOf('return') < 0) { 60 | body = 'return (' + body + ')'; 61 | } 62 | 63 | expressionFn = new Function(args, body); 64 | } catch(error) { 65 | expressionFn = new Function(args, body); 66 | } 67 | 68 | if (typeof(expressionFn) !== 'function') { 69 | throw new SyntaxError('Expression "' + expression + '" is invalid'); 70 | } 71 | 72 | expressionCache[expression] = expressionFn; 73 | return expressionFn; 74 | }, 75 | 76 | getType: function(obj) { 77 | var type = typeof obj; 78 | 79 | if (type !== 'object') return type; // primitive or function 80 | if (obj === null) return 'null'; // null 81 | 82 | // Everything else, check for a constructor 83 | var ctor = obj.constructor; 84 | var name = typeof ctor === 'function' && ctor.name; 85 | 86 | return typeof name === 'string' && name.length > 0 ? name : 'object'; 87 | }, 88 | 89 | getDefaultValue: function(type) { 90 | if (typeof type !== 'string') throw new TypeError('Type must be a string.'); 91 | 92 | // Handle simple types (primitives and plain function/object) 93 | switch (type) { 94 | case 'boolean' : return false; 95 | case 'function' : return function () {}; 96 | case 'null' : return null; 97 | case 'number' : return 0; 98 | case 'object' : return {}; 99 | case 'string' : return ""; 100 | case 'symbol' : return Symbol(); 101 | case 'undefined' : return void 0; 102 | } 103 | 104 | try { 105 | // Look for constructor in this or current scope 106 | var ctor = typeof this[type] === 'function' 107 | ? this[type] 108 | : eval(type); 109 | 110 | return new ctor; 111 | 112 | // Constructor not found, return new object 113 | } catch (e) { return {}; } 114 | }, 115 | 116 | isArray: function(arg) { 117 | if (Array.isArray) { 118 | return Array.isArray(arg); 119 | } 120 | 121 | return Object.prototype.toString.call(arg) === '[object Array]'; 122 | } 123 | }, 124 | methods: { 125 | any: function (predicate) { 126 | if (typeof (predicate) === "string") { 127 | predicate = LINQ.utils.parseExpression(predicate); 128 | } else if (typeof (predicate) !== "function") { 129 | return this.length > 0; 130 | } 131 | 132 | if (typeof(Array.prototype.some) === 'function') { 133 | return this.some(predicate); 134 | } 135 | 136 | for (var i = 0, l = this.length; i < l; i++) { 137 | if (predicate(this[i], i, this)) { 138 | return true; 139 | } 140 | } 141 | 142 | return false; 143 | }, 144 | 145 | all: function (predicate) { 146 | if (typeof (predicate) === "string") { 147 | predicate = LINQ.utils.parseExpression(predicate); 148 | } else if (typeof (predicate) !== "function") { 149 | throw new Error('Predicate is required'); 150 | } 151 | 152 | if (typeof(Array.prototype.every) === 'function') { 153 | return this.every(predicate); 154 | } 155 | 156 | var oppositePredicate = function () { 157 | return !predicate.apply(this, arguments); 158 | } 159 | 160 | return !LINQ.methods.any.apply(this, [oppositePredicate]); 161 | }, 162 | 163 | where: function (predicate) { 164 | if (typeof (predicate) === "string") { 165 | predicate = LINQ.utils.parseExpression(predicate); 166 | } else if (typeof (predicate) !== "function") { 167 | throw new Error('Predicate is required'); 168 | } 169 | 170 | if (typeof (Array.prototype.filter) === "function") { 171 | return this.filter(predicate); 172 | } 173 | 174 | var result = []; 175 | for (var i = 0, l = this.length; i < l; i++) { 176 | if (predicate(this[i], i, this)) { 177 | result.push(this[i]); 178 | } 179 | } 180 | 181 | return result; 182 | }, 183 | 184 | select: function (selector) { 185 | if (typeof (selector) === "string") { 186 | selector = LINQ.utils.parseExpression(selector); 187 | } else if (typeof (selector) !== "function") { 188 | throw new Error('Selector is required'); 189 | } 190 | 191 | if (typeof (Array.prototype.map) === "function") { 192 | return this.map(selector); 193 | } 194 | 195 | var result = []; 196 | for (var i = 0, l = this.length; i < l; i++) { 197 | result.push(selector(this[i], i, this)); 198 | } 199 | 200 | return result; 201 | }, 202 | 203 | selectMany: function(selector) { 204 | if (typeof (selector) === "string") { 205 | selector = LINQ.utils.parseExpression(selector); 206 | } else if (typeof (selector) !== "function") { 207 | throw new Error('Selector is required'); 208 | } 209 | 210 | var results = []; 211 | for (var i = 0, l = this.length; i < l; i++) { 212 | var subArray = selector(this[i], i, this); 213 | if (!LINQ.utils.isArray(subArray)) { 214 | continue; 215 | } 216 | 217 | results.push(subArray); 218 | } 219 | 220 | return Array.prototype.concat.apply([], results); 221 | }, 222 | 223 | count: function (predicate) { 224 | if (typeof (predicate) === "string") { 225 | predicate = LINQ.utils.parseExpression(predicate); 226 | } else if (typeof (predicate) !== "function") { 227 | return this.length; 228 | } 229 | 230 | if (typeof (Array.prototype.filter) === "function") { 231 | return this.filter(predicate).length; 232 | } 233 | 234 | var count = 0; 235 | for (var i = 0, l = this.length; i < l; i++) { 236 | if (predicate(this[i], i, this)) { 237 | count++; 238 | } 239 | } 240 | 241 | return count; 242 | }, 243 | 244 | orderBy: function (selector, comparator) { 245 | if (typeof (selector) === "string") { 246 | selector = LINQ.utils.parseExpression(selector); 247 | } else if (typeof (selector) !== "function") { 248 | selector = function(s) { return s; }; 249 | } 250 | 251 | if (typeof (comparator) === "string") { 252 | comparator = LINQ.utils.parseExpression(comparator); 253 | } else if (typeof (comparator) !== "function") { 254 | comparator = function (a, b) { 255 | if (a < b) return -1; 256 | if (a > b) return 1; 257 | return 0; 258 | }; 259 | } 260 | 261 | return this.slice(0) 262 | .sort(function (a, b) { 263 | var valA = selector(a); 264 | var valB = selector(b); 265 | return comparator(valA, valB); 266 | }); 267 | }, 268 | 269 | orderByDescending: function (selector, comparator) { 270 | if (typeof (comparator) === "string") { 271 | comparator = LINQ.utils.parseExpression(comparator); 272 | } else if (typeof (comparator) !== "function") { 273 | comparator = function (a, b) { 274 | if (a < b) return -1; 275 | if (a > b) return 1; 276 | return 0; 277 | }; 278 | } 279 | 280 | var oppositeComparator = function () { 281 | return comparator.apply(this, arguments) * -1; 282 | }; 283 | 284 | return LINQ.methods.orderBy.apply(this, [selector, oppositeComparator]); 285 | }, 286 | 287 | groupBy: function (keySelector, resultSelector) { 288 | if (typeof (keySelector) === "string") { 289 | keySelector = LINQ.utils.parseExpression(keySelector); 290 | } else if (typeof (keySelector) !== "function") { 291 | throw new Error("Key selector is required"); 292 | } 293 | 294 | if (typeof (resultSelector) === "string") { 295 | resultSelector = LINQ.utils.parseExpression(resultSelector); 296 | } else if (typeof (resultSelector) !== "function") { 297 | resultSelector = function (group, values) { 298 | return { group: group, values: values }; 299 | }; 300 | } 301 | 302 | var resultMap = {}; 303 | for (var i = 0, l = this.length; i < l; i++) { 304 | var key = keySelector(this[i], i, this); 305 | if (!resultMap[key]) { 306 | resultMap[key] = []; 307 | } 308 | 309 | resultMap[key].push(this[i]); 310 | } 311 | 312 | var result = []; 313 | for (var k in resultMap) { 314 | if (resultMap.hasOwnProperty(k)) { 315 | result.push(resultSelector(k, resultMap[k])); 316 | } 317 | } 318 | 319 | return result; 320 | }, 321 | 322 | distinct: function (selector, comparator) { 323 | if (typeof (selector) === "string") { 324 | selector = LINQ.utils.parseExpression(selector); 325 | } else if (typeof (selector) !== "function") { 326 | selector = function (a) { return a; } 327 | } 328 | 329 | if (typeof (comparator) === "string") { 330 | comparator = LINQ.utils.parseExpression(comparator); 331 | } else if (typeof(comparator) !== 'function') { 332 | comparator = function(a, b) { return a === b; } 333 | } 334 | 335 | var unique = []; 336 | var uniqueMap = {}; 337 | for (var i = 0, l = this.length; i < l; i++) { 338 | var key = selector(this[i], i, this); 339 | if (!uniqueMap[key]) { 340 | uniqueMap[key] = [key]; 341 | unique.push(this[i]); 342 | } else { 343 | var isUnique = true; 344 | for (var j = 0, jl = uniqueMap[key].length; j < jl; j++) { 345 | if (comparator(uniqueMap[key][j], key)) { 346 | isUnique = false; 347 | break; 348 | } 349 | } 350 | 351 | if (isUnique) { 352 | unique.push(this[i]); 353 | uniqueMap[key].push(key); 354 | } 355 | } 356 | } 357 | 358 | return unique; 359 | }, 360 | 361 | firstOrDefault: function (predicate) { 362 | if (typeof (predicate) === "string") { 363 | predicate = LINQ.utils.parseExpression(predicate); 364 | } else if (typeof (predicate) !== "function") { 365 | predicate = function () { return true; } 366 | } 367 | 368 | if (typeof(Array.prototype.find) === 'function') { 369 | return this.find(predicate) || null; 370 | } 371 | 372 | for (var i = 0, l = this.length; i < l; i++) { 373 | if (predicate(this[i], i, this)) { 374 | return this[i]; 375 | } 376 | } 377 | 378 | return null; 379 | }, 380 | 381 | lastOrDefault: function (predicate) { 382 | if (typeof (predicate) === "string") { 383 | predicate = LINQ.utils.parseExpression(predicate); 384 | } else if (typeof (predicate) !== "function") { 385 | predicate = function () { return true; } 386 | } 387 | 388 | for (var i = this.length - 1; i >= 0; i--) { 389 | if (predicate(this[i], i, this)) { 390 | return this[i]; 391 | } 392 | } 393 | 394 | return null; 395 | }, 396 | 397 | joinWith: function(innerArray, innerKeySelector, outerKeySelector, resultSelector, keyComparator) { 398 | if (typeof (innerKeySelector) === "string") { 399 | innerKeySelector = LINQ.utils.parseExpression(innerKeySelector); 400 | } else if (typeof (innerKeySelector) !== "function") { 401 | throw new Error("Inner key selector is required"); 402 | } 403 | 404 | if (typeof (outerKeySelector) === "string") { 405 | outerKeySelector = LINQ.utils.parseExpression(outerKeySelector); 406 | } else if (typeof (outerKeySelector) !== "function") { 407 | throw new Error("Outer key selector is required"); 408 | } 409 | 410 | if (typeof (resultSelector) === "string") { 411 | resultSelector = LINQ.utils.parseExpression(resultSelector); 412 | } else if (typeof (resultSelector) !== "function") { 413 | throw new Error("Results selector is required"); 414 | } 415 | 416 | if (typeof (keyComparator) === "string") { 417 | keyComparator = LINQ.utils.parseExpression(keyComparator); 418 | } else if (typeof (keyComparator) !== "function") { 419 | keyComparator = function(a, b) { return a === b; } 420 | } 421 | 422 | var result = []; 423 | for (var i = 0, outerLength = this.length; i < outerLength; i++) { 424 | var outerKey = outerKeySelector(this[i], i, this); 425 | for (var j = 0, innerLength = innerArray.length; j < innerLength; j++) { 426 | var innerKey = innerKeySelector(innerArray[j], j, innerArray); 427 | if (keyComparator(innerKey, outerKey)) { 428 | result.push(resultSelector(innerArray[j], this[i])); 429 | } 430 | } 431 | } 432 | 433 | return result; 434 | }, 435 | 436 | groupJoinWith: function(innerArray, innerKeySelector, outerKeySelector, resultSelector, keyComparator) { 437 | if (typeof (innerKeySelector) === "string") { 438 | innerKeySelector = LINQ.utils.parseExpression(innerKeySelector); 439 | } else if (typeof (innerKeySelector) !== "function") { 440 | throw new Error("Inner key selector is required"); 441 | } 442 | 443 | if (typeof (outerKeySelector) === "string") { 444 | outerKeySelector = LINQ.utils.parseExpression(outerKeySelector); 445 | } else if (typeof (outerKeySelector) !== "function") { 446 | throw new Error("Outer key selector is required"); 447 | } 448 | 449 | if (typeof (resultSelector) === "string") { 450 | resultSelector = LINQ.utils.parseExpression(resultSelector); 451 | } else if (typeof (resultSelector) !== "function") { 452 | throw new Error("Results selector is required"); 453 | } 454 | 455 | if (typeof (keyComparator) === "string") { 456 | keyComparator = LINQ.utils.parseExpression(keyComparator); 457 | } else if (typeof (keyComparator) !== "function") { 458 | keyComparator = function(a, b) { return a === b; } 459 | } 460 | 461 | var result = []; 462 | for (var i = 0, outerLength = this.length; i < outerLength; i++) { 463 | var outerKey = outerKeySelector(this[i], i, this); 464 | var joinGroup = LINQ.methods.where.apply(innerArray, [function(innerElement, j) { 465 | innerKey = innerKeySelector(innerArray[j], j, innerArray); 466 | return keyComparator(innerKey, outerKey); 467 | }]); 468 | result.push(resultSelector(joinGroup, this[i])); 469 | } 470 | 471 | return result; 472 | }, 473 | 474 | contains: function (value, comparator) { 475 | var anyComparator; 476 | if (typeof (comparator) === "string") { 477 | var comparatorFn = LINQ.utils.parseExpression(comparator); 478 | anyComparator = function (v) { return comparatorFn(v, value); }; 479 | } else if (typeof (comparator) !== "function") { 480 | anyComparator = function (v) { return v === value; } 481 | } else { 482 | anyComparator = function (v) { return comparator(v, value); }; 483 | } 484 | 485 | return LINQ.methods.any.apply(this, [anyComparator]); 486 | }, 487 | 488 | aggregate: function(aggregator, seed) { 489 | if (typeof (aggregator) === "string") { 490 | aggregator = LINQ.utils.parseExpression(aggregator); 491 | } else if (typeof (aggregator) !== "function") { 492 | throw new Error("Aggregator function is required"); 493 | } 494 | 495 | if (this.length <= 0) return seed; 496 | 497 | var result = typeof(seed) === 'undefined' ? LINQ.utils.getDefaultValue(LINQ.utils.getType(this[0])) : seed; 498 | if (typeof(Array.prototype.reduce) === 'function') { 499 | return this.reduce(aggregator, result); 500 | } 501 | 502 | for (var i = 0, l = this.length; i < l; i++) { 503 | result = aggregator(result, this[i], i, this); 504 | } 505 | 506 | return result; 507 | }, 508 | 509 | sum: function(selector, defaultValue) { 510 | if (typeof (selector) === "string") { 511 | selector = LINQ.utils.parseExpression(selector); 512 | } else if (typeof (selector) !== "function") { 513 | selector = function(s) { return s; } 514 | } 515 | 516 | defaultValue = typeof(defaultValue) === 'undefined' ? LINQ.utils.getDefaultValue(LINQ.utils.getType(selector(this[0], 0, this))) : defaultValue; 517 | return LINQ.methods.aggregate.apply(this, [function(sum, next, index, array) { return sum + selector(next, index, array); }, defaultValue]); 518 | }, 519 | 520 | min: function(selector) { 521 | if (typeof (selector) === "string") { 522 | selector = LINQ.utils.parseExpression(selector); 523 | } else if (typeof (selector) !== "function") { 524 | selector = function(s) { return s; } 525 | } 526 | 527 | if (this.length <= 0) return void(0); 528 | 529 | return LINQ.methods.aggregate.apply(this, [ 530 | function(min, next, index, array) { var nextVal = selector(next, index, array); return min > nextVal ? nextVal : min; }, 531 | selector(this[0], 0, this)]); 532 | }, 533 | 534 | max: function(selector) { 535 | if (typeof (selector) === "string") { 536 | selector = LINQ.utils.parseExpression(selector); 537 | } else if (typeof (selector) !== "function") { 538 | selector = function(s) { return s; } 539 | } 540 | 541 | if (this.length <= 0) return void(0); 542 | 543 | return LINQ.methods.aggregate.apply(this, [ 544 | function(max, next, index, array) { var nextVal = selector(next, index, array); return max < nextVal ? nextVal : max; }, 545 | selector(this[0], 0, this)]); 546 | }, 547 | 548 | skip: function(count) { 549 | if (typeof(count) !== 'number' || count < 0) { 550 | throw new TypeError("Count is required and should be a positive number"); 551 | } 552 | 553 | return this.slice(count, this.length); 554 | }, 555 | 556 | take: function(count) { 557 | if (typeof(count) !== 'number' || count < 0) { 558 | throw new TypeError("Count is required and should be a positive number"); 559 | } 560 | 561 | return this.slice(0, count); 562 | }, 563 | 564 | ofType: function(type) { 565 | if (typeof(type) !== 'string') { 566 | throw new TypeError("Type is required."); 567 | } 568 | 569 | return LINQ.methods.where.apply(this, [function(item) { 570 | return typeof(item) === type; 571 | }]); 572 | }, 573 | 574 | union: function(anotherCollection, comparator) { 575 | return [].concat(this, anotherCollection).distinct(function(e) {return e}, comparator); 576 | }, 577 | 578 | except: function(anotherCollection, comparator) { 579 | if (typeof (comparator) === "string") { 580 | comparator = LINQ.utils.parseExpression(comparator); 581 | } else if (typeof (comparator) !== "function") { 582 | comparator = function (a, b) { return a === b; } 583 | } 584 | 585 | var result = []; 586 | for (var i = 0, l = this.length; i < l; i++) { 587 | var addToResult = true; 588 | for (var j = 0, rl = anotherCollection.length; j < rl; j++) { 589 | if (comparator(this[i], anotherCollection[j])) { 590 | addToResult = false; 591 | break; 592 | } 593 | } 594 | 595 | if (addToResult) { 596 | result.push(this[i]); 597 | } 598 | } 599 | 600 | return result; 601 | } 602 | } 603 | }; 604 | LINQ.methods.firstOrDefault.finalize = true; 605 | LINQ.methods.lastOrDefault.finalize = true; 606 | LINQ.methods.count.finalize = true; 607 | LINQ.methods.any.finalize = true; 608 | LINQ.methods.contains.finalize = true; 609 | LINQ.methods.all.finalize = true; 610 | LINQ.methods.aggregate.finalize = true; 611 | LINQ.methods.sum.finalize = true; 612 | LINQ.methods.min.finalize = true; 613 | LINQ.methods.max.finalize = true; 614 | 615 | for (var key in LINQ.methods) { 616 | if (LINQ.methods.hasOwnProperty(key)) { 617 | Array.prototype[key] = LINQ.methods[key]; 618 | } 619 | } 620 | 621 | /* ADDITIONAL_ATTACHMENTS */ 622 | })(); -------------------------------------------------------------------------------- /src/mini-linq.knockout.js: -------------------------------------------------------------------------------- 1 | /* ! https://github.com/akopachov/mini-linq-js */ 2 | /* ! 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2016 Alexander Kopachov (https://www.linkedin.com/in/akopachov) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | (function(LINQ) { 26 | if (typeof(ko) == 'undefined' || !ko.observableArray || !ko.observableArray.fn) { 27 | return; 28 | } 29 | 30 | Array.prototype.toObservableArray = function() { 31 | return ko.observableArray(this); 32 | }; 33 | ko.observableArray.fn.toArray = function() { 34 | return this(); 35 | }; 36 | 37 | for (var key in LINQ.methods) { 38 | if (LINQ.methods.hasOwnProperty(key)) { 39 | ko.observableArray.fn[key] = (function(key) { 40 | return function() { 41 | return LINQ.methods[key].apply(this(), arguments); 42 | } 43 | })(key); 44 | } 45 | } 46 | })(LINQ); -------------------------------------------------------------------------------- /src/mini-linq.lazy.js: -------------------------------------------------------------------------------- 1 | /* ! https://github.com/akopachov/mini-linq-js */ 2 | /* ! 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2016 Alexander Kopachov (https://www.linkedin.com/in/akopachov) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | (function(LINQ) { 26 | var MERGE_FUNCTIONS = [ 27 | function(current, next) { 28 | if (current.name === next.name && next.name === 'where') { 29 | var currentPredicate = LINQ.utils.parseExpression(current.args[0]); 30 | var nextPredicate = LINQ.utils.parseExpression(next.args[0]); 31 | return (function(currentPredicate, nextPredicate) { 32 | return { 33 | name: current.name, 34 | args: [function() { 35 | return currentPredicate.apply(this, arguments) && nextPredicate.apply(this, arguments); 36 | }], 37 | fn: LINQ.methods[current.name] 38 | } 39 | })(currentPredicate, nextPredicate); 40 | } 41 | 42 | return false; 43 | }, 44 | 45 | function(current, next) { 46 | if (current.name === next.name && next.name === 'select') { 47 | var currentPredicate = LINQ.utils.parseExpression(current.args[0]); 48 | var nextPredicate = LINQ.utils.parseExpression(next.args[0]); 49 | return (function(currentPredicate, nextPredicate) { 50 | return { 51 | name: current.name, 52 | args: [function() { 53 | return nextPredicate.apply(this, [currentPredicate.apply(this, arguments)]); 54 | }], 55 | fn: LINQ.methods[current.name] 56 | } 57 | })(currentPredicate, nextPredicate); 58 | } 59 | 60 | return false; 61 | }, 62 | 63 | function(current, next) { 64 | var orderMethods = ['orderBy', 'orderByDescending']; 65 | if (orderMethods.contains(current.name) && orderMethods.contains(next.name)) { 66 | return next; 67 | } 68 | 69 | return false; 70 | }, 71 | 72 | function(current, next) { 73 | if (current.name == 'skip' && next.name == 'take') { 74 | return (function(skipCnt, takeCnt) { 75 | return { 76 | name: 'skiptake', 77 | args: [skipCnt, takeCnt], 78 | fn: function(skip, take) { 79 | return this.slice(skip, skip + take); 80 | } 81 | } 82 | })(current.args[0], next.args[0]); 83 | } 84 | 85 | return false; 86 | } 87 | ]; 88 | 89 | var LazyArray = function (array) { 90 | var that = this; 91 | var _array = array; 92 | that._modificatorQueue = []; 93 | 94 | var applyModificators = function () { 95 | var l = that._modificatorQueue.length; 96 | for (var i = 0; i < l; i++) { 97 | var modificator = that._modificatorQueue[i]; 98 | _array = modificator.fn.apply(_array, modificator.args); 99 | } 100 | 101 | that._modificatorQueue.splice(0, l); 102 | return l; 103 | }; 104 | 105 | var mergeModificators = function() { 106 | var i = 0; 107 | while (i < that._modificatorQueue.length - 1) { 108 | var current = that._modificatorQueue[i]; 109 | var next = that._modificatorQueue[i + 1]; 110 | var merged = false; 111 | for (var j = 0, l = MERGE_FUNCTIONS.length; j < l; j++) { 112 | var mergeFn = MERGE_FUNCTIONS[j]; 113 | var mergedModificator = mergeFn(current, next); 114 | if (mergedModificator) { 115 | merged = true; 116 | that._modificatorQueue.splice(i + 1, 1); 117 | that._modificatorQueue[i] = mergedModificator; 118 | } 119 | } 120 | 121 | if (!merged) { 122 | i++; 123 | } 124 | } 125 | }; 126 | 127 | that.optimize = function() { 128 | mergeModificators(); 129 | return that; 130 | }; 131 | 132 | that.toArray = function () { 133 | if (_array.length > 100 && that._modificatorQueue.length > 2) { 134 | that.optimize(); 135 | } 136 | 137 | applyModificators(); 138 | return LINQ.utils.isArray(_array) ? _array.slice(0) : _array; 139 | }; 140 | }; 141 | 142 | Array.prototype.toLazy = function() { 143 | return new LazyArray(this); 144 | }; 145 | 146 | for (var key in LINQ.methods) { 147 | if (LINQ.methods.hasOwnProperty(key)) { 148 | LazyArray.prototype[key] = (function(key) { 149 | return function() { 150 | var linqFn = LINQ.methods[key]; 151 | this._modificatorQueue.push({ fn: LINQ.methods[key], args: arguments, name: key }); 152 | if (linqFn.finalize) { 153 | return this.toArray(); 154 | } 155 | 156 | return this; 157 | }; 158 | })(key); 159 | } 160 | } 161 | })(LINQ); -------------------------------------------------------------------------------- /tests/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-linq_tests", 3 | "homepage": "https://github.com/akopachov/mini-linq-js", 4 | "authors": [ 5 | "akopachov " 6 | ], 7 | "description": "", 8 | "main": "tests.html", 9 | "moduleType": [], 10 | "license": "MIT", 11 | "private": true, 12 | "ignore": [ 13 | "**/.*", 14 | "node_modules", 15 | "bower_components", 16 | "test", 17 | "tests" 18 | ], 19 | "devDependencies": { 20 | "mocha": "^2.5.1", 21 | "unit.js": "https://github.com/unitjs/unit.js.git#^2.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/test-core.js: -------------------------------------------------------------------------------- 1 | if (typeof(require) !== 'undefined') { 2 | require('../dist/mini-linq.min.js'); 3 | var test = require('unit.js'); 4 | } else { 5 | var test = unitjs; 6 | } 7 | 8 | var testArray1 = [1, 2, 8, 2, 6, 3, 9, 2, 4]; 9 | 10 | describe('Methods', function() { 11 | it('.any', function() { 12 | test.value(testArray1.any('a => a == 2')) 13 | .isNotType('undefined') 14 | .isBool() 15 | .isTrue(); 16 | test.value(testArray1.any('a => a == 999')) 17 | .isNotType('undefined') 18 | .isBool() 19 | .isFalse(); 20 | }); 21 | 22 | it('.all', function() { 23 | test.value(testArray1.all('a => a == 2')) 24 | .isNotType('undefined') 25 | .isBool() 26 | .isFalse(); 27 | test.value(testArray1.all('a => a <= 9')) 28 | .isNotType('undefined') 29 | .isBool() 30 | .isTrue(); 31 | }); 32 | 33 | it('.where', function() { 34 | test.array(testArray1.where('a => a > 2 && a < 5')) 35 | .isNotEmpty() 36 | .match(function(arr) { 37 | for (var i = 0; i < arr.length; i++) { 38 | if (arr[i] <= 2 || arr[i] >= 5) { 39 | return false; 40 | } 41 | } 42 | 43 | return true; 44 | }); 45 | }); 46 | 47 | it('.select', function() { 48 | test.array(testArray1.select('a => { x: a, xx: a * a }')) 49 | .isNotEmpty() 50 | .match(function(arr) { 51 | for (var i = 0; i < arr.length; i++) { 52 | if (typeof(arr[i]) !== 'object' || arr[i].x !== testArray1[i]) { 53 | return false; 54 | } 55 | } 56 | 57 | return true; 58 | }); 59 | }); 60 | 61 | it('.count', function() { 62 | test.value(testArray1.count('a => a == 2')) 63 | .isNotType('undefined') 64 | .isNumber() 65 | .is(3); 66 | 67 | test.value(testArray1.count()) 68 | .isNotType('undefined') 69 | .isNumber() 70 | .is(testArray1.length); 71 | }); 72 | 73 | it('.orderBy', function() { 74 | test.array(testArray1.orderBy('o => o')) 75 | .isNotEmpty() 76 | .match(function(arr) { 77 | return arr.length === testArray1.length; 78 | }) 79 | .match(function(arr) { 80 | for (var i = 0; i < arr.length - 1; i++) { 81 | if (arr[i] > arr[i + 1]) { 82 | return false; 83 | } 84 | 85 | return true; 86 | } 87 | }); 88 | }); 89 | 90 | it('.orderByDescending', function() { 91 | test.array(testArray1.orderByDescending('o => o')) 92 | .isNotEmpty() 93 | .match(function(arr) { 94 | return arr.length === testArray1.length; 95 | }) 96 | .match(function(arr) { 97 | for (var i = 0; i < arr.length - 1; i++) { 98 | if (arr[i] < arr[i + 1]) { 99 | return false; 100 | } 101 | 102 | return true; 103 | } 104 | }); 105 | }); 106 | 107 | it('.distinct', function() { 108 | var distinctElements = {}; 109 | test.array(testArray1.distinct()) 110 | .match(function(arr) { 111 | return arr.length <= testArray1.length; 112 | }) 113 | .matchEach(function(it, key) { 114 | if (testArray1.hasOwnProperty(key) && typeof(distinctElements[it]) !== 'undefined') { 115 | return false; 116 | } 117 | 118 | return distinctElements[it] = true; 119 | }); 120 | test.array([1, 2, '2', '3', 3, 4, 5, 8, 5].distinct()).is([1, 2, '2', '3', 3, 4, 5, 8]); 121 | test.array([1, 2, '2', '3', 3, 4, 5, 8, 5].distinct('x => x', '(a, b) => a == b')).is([1, 2, '3', 4, 5, 8]); 122 | }); 123 | 124 | it('.firstOrDefault', function() { 125 | test.value(testArray1.firstOrDefault()).is(testArray1[0]); 126 | test.value(testArray1.firstOrDefault('f => f % 2 === 0')).is(2); 127 | test.value(testArray1.firstOrDefault('f => f === 999')).isNull(); 128 | }); 129 | 130 | it('.lastOrDefault', function() { 131 | test.value(testArray1.lastOrDefault()).is(testArray1[testArray1.length - 1]); 132 | test.value(testArray1.lastOrDefault('f => f % 2 === 0')).is(4); 133 | test.value(testArray1.lastOrDefault('f => f === 999')).isNull(); 134 | }); 135 | 136 | it('.contains', function() { 137 | test.value(testArray1.contains(2)).isTrue(); 138 | test.value(testArray1.contains(999)).isFalse(); 139 | test.value(testArray1.contains('2')).isFalse(); // === by default is in use. 140 | test.value(testArray1.contains('2', '(a, b) => a == b')).isTrue(); // force == to use 141 | test.value([1, 2, 3, 4].contains('2', function(a, b) { return a == b;})).isTrue(); 142 | }); 143 | 144 | it('.groupBy', function() { 145 | var distinctElements = {}; 146 | test.array(testArray1.groupBy('o => o')) 147 | .isNotEmpty() 148 | .matchEach(function(it, key) { 149 | if (testArray1.hasOwnProperty(key) && typeof(distinctElements[it.group]) !== 'undefined') { 150 | return false; 151 | } 152 | 153 | return distinctElements[it.group] = true; 154 | }) 155 | .matchEach(function(it, key) { 156 | if (testArray1.hasOwnProperty(key)) { 157 | test.array(it.values).isNotEmpty() 158 | } 159 | 160 | return true; 161 | }); 162 | }); 163 | 164 | it('.joinWith', function() { 165 | test.array(testArray1.joinWith(testArray1, 'ik => true', 'ok => true', '(i, o) => i')) 166 | .isNotEmpty() 167 | .match(function(arr) { 168 | return arr.length === testArray1.length * testArray1.length; 169 | }); 170 | test.array(testArray1.joinWith([1, 2, 3, 4], 'ik => ik', 'ok => ok', '(i, o) => i')) 171 | .isNotEmpty() 172 | .match(function(arr) { 173 | return arr.length === 6; 174 | }); 175 | }); 176 | 177 | it('.aggregate', function() { 178 | var testArray = "the quick brown fox jumps over the lazy dog".split(' '); 179 | test.value(testArray.aggregate('(workingSentence, next) => next + " " + workingSentence')) 180 | .is('dog lazy the over jumps fox brown quick the '); 181 | 182 | var testArraySumm = 0; 183 | for (var i = 0; i < testArray1.length; i++) { 184 | testArraySumm += testArray1[i]; 185 | } 186 | test.value(testArray1.aggregate('(c, n) => c + n')).is(testArraySumm); 187 | }); 188 | 189 | it('.sum', function() { 190 | var testArray = "the quick brown fox jumps over the lazy dog".split(' '); 191 | test.value(testArray.sum('s => s + "-"')).is('the-quick-brown-fox-jumps-over-the-lazy-dog-'); 192 | 193 | var testArraySumm = 0; 194 | for (var i = 0; i < testArray1.length; i++) { 195 | testArraySumm += testArray1[i]; 196 | } 197 | test.value(testArray1.sum('s => s')).is(testArraySumm); 198 | }); 199 | 200 | it('.min', function() { 201 | var testArray = "the quick brown fox jumps over the lazy dog".split(' '); 202 | test.value(testArray.min('s => s.length')).is(3); 203 | test.value([].min()).isUndefined(); 204 | test.value([9, 5, 1, 9, 3, 5, 6].min()).is(1); 205 | }); 206 | 207 | it('.max', function() { 208 | var testArray = "the quick brown fox jumps over the lazy dog".split(' '); 209 | test.value(testArray.max('s => s.length')).is(5); 210 | test.value([].max()).isUndefined(); 211 | test.value([9, 5, 1, 9, 3, 5, 6].max()).is(9); 212 | }); 213 | 214 | it('.skip', function() { 215 | test.array([1, 2, 3, 4].skip(2)).is([3, 4]); 216 | test.array([1, 2, 3, 4].skip(0)).is([1, 2, 3, 4]); 217 | test.array([1, 2, 3, 4].skip(9)).is([]); 218 | }); 219 | 220 | it('.take', function() { 221 | test.array([1, 2, 3, 4].take(2)).is([1, 2]); 222 | test.array([1, 2, 3, 4].take(0)).is([]); 223 | test.array([1, 2, 3, 4].take(9)).is([1, 2, 3, 4]); 224 | }); 225 | 226 | it('.ofType', function() { 227 | test.array([1, '2', '3', 4].ofType('string')).is(['2', '3']); 228 | test.array([1, '2', '3', 4].ofType('number')).is([1, 4]); 229 | test.array([1, '2', '3', 4].ofType('object')).is([]); 230 | test.array([].ofType('object')).is([]); 231 | }); 232 | 233 | it('.union', function() { 234 | test.array([1, 2, 3, 4].union([2, 3, 4, 5])).is([1, 2, 3, 4, 5]); 235 | test.array([1, 2, 3, 4].union([5, 6, 7, 8])).is([1, 2, 3, 4, 5, 6, 7, 8]); 236 | test.array([1, 2, 3, 4].union([])).is([1, 2, 3, 4]); 237 | test.array([].union([])).is([]); 238 | test.array([1, 2, 3, 4].union([2, '3', '4', 5], '(a, b) => a == b')).is([1, 2, 3, 4, 5]); 239 | }); 240 | 241 | it('.except', function() { 242 | test.array([1, 2, 3, 4].except([3, 4, 5])).is([1, 2]); 243 | test.array([1, 2, 3, 4].except([5, 6, 7])).is([1, 2, 3, 4]); 244 | test.array([1, 2, 3, 4].except([1, 2, 3, 4])).is([]); 245 | test.array([1, 2, 3, 4].except(['3', 4, '5'], '(a, b) => a == b')).is([1, 2]); 246 | }); 247 | 248 | it('.selectMany', function() { 249 | var testArray2 = [ 250 | { x: [1, 2], y: 0 }, 251 | { x: [3, 4], y: 1 }, 252 | { x: [5, 6], y: 2 }]; 253 | test.array(testArray2.selectMany('sm => sm.x')).is([1, 2, 3, 4, 5, 6]); 254 | test.array(testArray2.selectMany('sm => sm.y')).is([]); 255 | }); 256 | it('.groupJoinWith', function() { 257 | test.array([1, 2, 3, 4].groupJoinWith([1, 2, 3, 1, 2, 3], 'ik => ik', 'ok => ok', '(g, o) => g')) 258 | .isNotEmpty() 259 | .is([[1, 1], [2, 2], [3, 3], []]); 260 | 261 | test.array([].groupJoinWith([1, 2, 3, 1, 2, 3], 'ik => ik', 'ok => ok', '(g, o) => g')) 262 | .isEmpty(); 263 | 264 | test.array([1, 2, 3, 4].groupJoinWith([], 'ik => ik', 'ok => ok', '(g, o) => g')) 265 | .isNotEmpty() 266 | .is([[], [], [], []]); 267 | 268 | test.array([1, 2, 3, 4].groupJoinWith([], 'ik => ik', 'ok => ok', '(g, o) => o')) 269 | .isNotEmpty() 270 | .is([1, 2, 3, 4]); 271 | }); 272 | }); -------------------------------------------------------------------------------- /tests/test-lazy.js: -------------------------------------------------------------------------------- 1 | if (typeof(require) !== 'undefined') { 2 | require('../dist/mini-linq.with-lazy.min.js'); 3 | var test = require('unit.js'); 4 | } else { 5 | var test = unitjs; 6 | } 7 | 8 | var testArray1 = [1, 2, 8, 2, 6, 3, 9, 2, 4]; 9 | 10 | describe('Lazy array', function() { 11 | it('Results are the same as for standart array', function() { 12 | var result1 = testArray1.select('s => s').where('w => w > 3'); 13 | var result2 = testArray1.toLazy().select('s => s').where('w => w > 3').toArray(); 14 | test.array(result1).is(result2); 15 | var val1 = testArray1.select('s => s').where('w => w > 3').sum(); 16 | var val2 = testArray1.toLazy().select('s => s').where('w => w > 3').sum(); 17 | test.value(val1).is(val2); 18 | }); 19 | 20 | it('Optimizer is working', function() { 21 | var result1 = testArray1.where('w => w > 1').where('w => w > 2').where('w => w > 3'); 22 | var result2 = testArray1.toLazy().where('w => w > 1').where('w => w > 2').where('w => w > 3').optimize().toArray(); 23 | test.array(result1).is(result2); 24 | 25 | var result3 = testArray1.where('w => w > 1').where('w => w < 1').where('w => w > 3'); 26 | var result4 = testArray1.toLazy().where('w => w > 1').where('w => w < 1').where('w => w > 3').optimize().toArray(); 27 | test.array(result3).is(result4); 28 | 29 | var result5 = testArray1.where(function(w) {return w > 1;}).where('w => w > 2').where(function(w) {return w > 3;}).select(function(s) {return s + 1;}).select('s => s + 5').select(function(s) {return s + 1;}); 30 | var result6 = testArray1.toLazy().where('w => w > 1').where(function(w) {return w > 2;}).where(function(w) {return w > 3;}).select(function(s) {return s + 1;}).select('s => s + 5').select(function(s) {return s + 1;}).optimize().toArray(); 31 | test.array(result5).is(result6); 32 | 33 | test.array(testArray1.toLazy().orderBy('o => o').orderBy().orderByDescending().optimize().toArray()).is(testArray1.orderByDescending()); 34 | 35 | test.array(testArray1.toLazy().skip(2).take(3).optimize().toArray()).is(testArray1.skip(2).take(3)); 36 | test.array(testArray1.toLazy().skip(0).take(99).optimize().toArray()).is(testArray1.skip(0).take(99)); 37 | test.array(testArray1.toLazy().skip(99).take(99).optimize().toArray()).is(testArray1.skip(99).take(99)); 38 | }); 39 | }); -------------------------------------------------------------------------------- /tests/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mini-LINQ unit tests 5 | 6 | 7 | 8 | 9 | 10 |

Mini-LINQ unit tests:

11 |
12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | --------------------------------------------------------------------------------