├── .gitignore ├── npm.export.ts ├── .vscode └── settings.json ├── tests.slim.html ├── tests.html ├── tests.performance.html ├── package.json ├── LICENSE ├── Release.txt ├── LInQer.extra.min.js ├── LInQer.slim.min.js ├── README.md ├── tsconfig.slim.json ├── tsconfig.json ├── tsconfig.all.json ├── tsconfig.extra.json ├── LInQer.GroupEnumerable.ts ├── tests.performance.js ├── qUnit └── qunit-2.9.2.css ├── LInQer.extra.js.map ├── tests.slim.js ├── LInQer.slim.js.map ├── LInQer.OrderedEnumerable.ts ├── LInQer.extra.js ├── LInQer.min.js ├── LInQer.extra.ts ├── LInQer.Slim.ts └── LInQer.Enumerable.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /npm.export.ts: -------------------------------------------------------------------------------- 1 | // export to NPM 2 | if (typeof(module) !== 'undefined') { 3 | module.exports = Linqer; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "es6-css-minify.hideButton": "auto", 3 | "es6-css-minify.js.mangle": true, 4 | "es6-css-minify.js.compress": true 5 | } -------------------------------------------------------------------------------- /tests.slim.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Unit tests for LInQer (Slim) 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Unit tests for LInQer 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests.performance.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Performance tests for LInQer 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@siderite/linqer", 3 | "version": "1.3.0", 4 | "description": "The C# Language Integrated Queries ported for Javascript for amazing performance", 5 | "main": "LInQer.all.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Siderite/LInQer.git" 12 | }, 13 | "keywords": [ 14 | "LINQ", 15 | "Javascript", 16 | "ES6", 17 | "iterator", 18 | "generator", 19 | "Typescript" 20 | ], 21 | "author": "Siderite", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/Siderite/LInQer/issues" 25 | }, 26 | "homepage": "https://siderite.dev/blog/linq-in-javascript-linqer", 27 | "dependencies": { 28 | "@types/node": "^13.7.0" 29 | }, 30 | "devDependencies": {}, 31 | "typings": "./LInQer.all.d.ts" 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Siderite 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. 22 | -------------------------------------------------------------------------------- /Release.txt: -------------------------------------------------------------------------------- 1 | 2020-03-08 - 1.2.2 2 | - made typings work for Visual Studio Code for Node.js 3 | 2020-02-16 - 1.2.1 4 | - added comments to the code to better explain the mechanism 5 | - bugfix: canSeek was not set for reverse 6 | 2020-02-16 - 1.2.0 7 | - bugfix: lead had the wrong indexing function for elementAt 8 | - bugfix: ordered enumerables mistakenly reported being able to seek (elementAt) 9 | - performance improvements for toArray, reverse, shuffle, binarySearch 10 | - performance improvements for orderBy and sort in place 11 | 2020-02-14 - 1.1.5 12 | - fixed a few bugs 13 | - added slice 14 | - added splice and length, however Enumerable is not seen as an Array-like object, because length is a property not a field 15 | 2020-02-13 - 1.1.4 16 | - Simplified the partition stack code 17 | 2020-02-09 - 1.1.3 18 | - Added typings for Intellisense in TypeScript 19 | 2020-02-09 - 1.1.2 20 | - optimized Quicksort and orderBy even more 21 | 2020-02-09 - 1.1.1 22 | - separated the performance tests in their own files 23 | - small performance improvements 24 | - reverted the Quicksort algorithm to its original version 25 | - tried Timsort, it's pretty cool, but large and hard to control 26 | 2020-02-06 - 1.1.0 27 | - change of algorithm for sorting 28 | - useQuickSort and useBrowserSort now just set the sort mechanism, QuickSort is default 29 | - added static Enumerable.sort(arr,comparer) which uses Quicksort to sort an array in place 30 | 2020-02-02 - 1.0.7 31 | - library now exports Linqer as the module.exports object 32 | 2020-02-02 - 1.0.6 33 | - used case sensitive main entry point 34 | 2020-02-02 - 1.0.5 35 | - added Linqer.all.js for node.js use and used it as the main entry point 36 | - optimized sorting even more 37 | 2020-01-29 - 1.0.3 38 | - updated README 39 | - moved binarySearch on Enumerable (the user has the responsibility to have it ordered) 40 | 2020-01-27 - 1.0.2 41 | - added toList to return a seekable Enumerable 42 | - added lag and lead to return a join between the enumerable and itself with an offset 43 | - added padEnd and padStart to return enumerable of at least a specific length, filling the missing items with a given value 44 | 2020-01-23 - 1.0.1 45 | - added randomSample functionality 46 | 2020-01-22 - 1.0.0 47 | - official launch 48 | -------------------------------------------------------------------------------- /LInQer.extra.min.js: -------------------------------------------------------------------------------- 1 | "use strict";var Linqer;!function(t){t.Enumerable.prototype.shuffle=function(){const e=this;const n=t.Enumerable.from((function*(){const t=e.toArray(),n=t.length;let o=0;for(;oe.count(),n},t.Enumerable.prototype.randomSample=function(e,n=Number.MAX_SAFE_INTEGER){let o=0;const r=[];if(t._ensureInternalTryGetAt(this),this._canSeek){const t=this.count();let o=0;for(o=0;o=n)break}return t.Enumerable.from(r)},t.Enumerable.prototype.distinctByHash=function(e){const n=this;return new t.Enumerable((function*(){const t=new Set;for(const o of n){const n=t.size;t.add(e(o)),n>1,s=n(o.elementAt(t),e);if(0==s)return t;s<0?r=t+1:a=t-1}return!1},t.Enumerable.prototype.lag=function(e,n){if(!e)throw new Error("offset has to be positive");if(e<0)throw new Error("offset has to be positive. Use .lead if you want to join with next items");n?t._ensureFunction(n):n=(t,e)=>[t,e];const o=this;t._ensureInternalTryGetAt(this);const r=new t.Enumerable((function*(){const t=Array(e);let r=0;for(const a of o){const o=r-e,s=o<0?void 0:t[o%e];yield n(a,s),t[r%e]=a,r++}}));return r._count=()=>{const t=o.count();return r._wasIterated||(r._wasIterated=o._wasIterated),t},o._canSeek&&(r._canSeek=!0,r._tryGetAt=t=>{const r=o._tryGetAt(t),a=o._tryGetAt(t-e);return r?{value:n(r.value,a?a.value:void 0)}:null}),r},t.Enumerable.prototype.lead=function(e,n){if(!e)throw new Error("offset has to be positive");if(e<0)throw new Error("offset has to be positive. Use .lag if you want to join with previous items");n?t._ensureFunction(n):n=(t,e)=>[t,e];const o=this;t._ensureInternalTryGetAt(this);const r=new t.Enumerable((function*(){const t=Array(e);let r=0;for(const a of o){const o=r-e;if(o>=0){const r=t[o%e];yield n(r,a)}t[r%e]=a,r++}for(let o=0;o{const t=o.count();return r._wasIterated||(r._wasIterated=o._wasIterated),t},o._canSeek&&(r._canSeek=!0,r._tryGetAt=t=>{const r=o._tryGetAt(t),a=o._tryGetAt(t+e);return r?{value:n(r.value,a?a.value:void 0)}:null}),r},t.Enumerable.prototype.padEnd=function(e,n){if(e<=0)throw new Error("minLength has to be positive.");let o;o="function"!=typeof n?t=>n:n;const r=this;t._ensureInternalTryGetAt(this);const a=new t.Enumerable((function*(){let t=0;for(const e of r)yield e,t++;for(;t{const t=Math.max(e,r.count());return a._wasIterated||(a._wasIterated=r._wasIterated),t},r._canSeek&&(a._canSeek=!0,a._tryGetAt=t=>{const n=r._tryGetAt(t);return n||(tn:n;const r=this;t._ensureInternalTryGetAt(r);const a=new t.Enumerable((function*(){const t=Array(e);let n=0;const a=r[Symbol.iterator]();let s=!1,u=!1;do{const r=a.next();if(u=!!r.done,u||(t[n]=r.value,n++),s&&!u)yield r.value;else if(u||n===e){for(let t=0;t{const t=Math.max(e,r.count());return a._wasIterated||(a._wasIterated=r._wasIterated),t},r._canSeek&&(a._canSeek=!0,a._tryGetAt=t=>{const n=r.count(),a=e-n;return a<=0?r._tryGetAt(t):t0,t._tryGetAt=t=>null,t._canSeek=!0,t}static range(t,n){const r=new e((function*(){for(let e=0;en,r._tryGetAt=e=>e>=0&&en,r._tryGetAt=e=>e>=0&&er.count()+i.count(),s(this),s(i),o._canSeek=r._canSeek&&i._canSeek,r._canSeek&&(o._tryGetAt=t=>r._tryGetAt(t)||i._tryGetAt(t-r.count())),o}count(){return i(this),this._count()}distinct(n=t.EqualityComparer.default){const r=this,o=n===t.EqualityComparer.default?function*(){const t=new Set;for(const e of r){const n=t.size;t.add(e),n0)&&(n.max=t),n.count++;return n}min(t){const e=this.stats(t);return 0===e.count?void 0:e.min}max(t){const e=this.stats(t);return 0===e.count?void 0:e.max}select(t){r(t);const n=this,o=new e((function*(){let e=0;for(const r of n)yield t(r,e),e++}));return i(this),o._count=this._count,s(n),o._canSeek=n._canSeek,o._tryGetAt=e=>{const r=n._tryGetAt(e);return r?{value:t(r.value)}:r},o}skip(t){const n=this,r=new e((function*(){let e=t;for(const t of n)e>0?e--:yield t}));return r._count=()=>Math.max(0,n.count()-t),s(this),r._canSeek=this._canSeek,r._tryGetAt=e=>n._tryGetAt(e+t),r}splice(t,e,...n){return this.take(t).concat(n).concat(this.skip(t+e))}sum(){const t=this.sumAndCount();return 0===t.count?void 0:t.sum}sumAndCount(){const t={count:0,sum:0};for(const e of this)t.sum=0===t.count?o(e):t.sum+o(e),t.count++;return t}take(t){const n=this,r=new e((function*(){let e=t;for(const t of n)if(e>0&&(yield t,e--),e<=0)break}));return r._count=()=>Math.min(t,n.count()),s(this),r._canSeek=n._canSeek,n._canSeek&&(r._tryGetAt=e=>e>=t?null:n._tryGetAt(e)),r}toArray(){var t;if(s(this),this._canSeek){const e=new Array(this.count());for(let n=0;ne._count())}const n=t._src;"function"==typeof n||"number"!=typeof n.length?"number"!=typeof n.size?t._count=()=>{let e=0;for(const n of t)e++;return e}:t._count=()=>n.size:t._count=()=>n.length}function s(t){if(t._tryGetAt)return;if(t._canSeek=!0,t._src instanceof e){const e=t._src;return s(e),t._tryGetAt=t=>e._tryGetAt(t),void(t._canSeek=e._canSeek)}if("string"==typeof t._src)return void(t._tryGetAt=e=>ee>=0&&e{let n=0;for(const r of t){if(e===n)return{value:r};n++}return null}):t._tryGetAt=t=>tt>e?1:tt==e,exact:(t,e)=>t===e}}(Linqer||(Linqer={})); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MAJOR UPDATE: I've rewritten the entire thing into [linqer-ts](https://www.npmjs.com/package/@siderite/linqer-ts). Any improvements or updates will be on that project. 2 | 3 | # LInQer 4 | The C# Language Integrated Queries ported for Javascript for amazing performance 5 | 6 | [![npm version](https://badge.fury.io/js/%40siderite%2Flinqer.svg)](https://badge.fury.io/js/%40siderite%2Flinqer) [![License: MIT](https://img.shields.io/badge/Licence-MIT-blueviolet)](https://opensource.org/licenses/MIT) 7 | 8 | # Installation 9 | ```sh 10 | $ npm install @siderite/linqer 11 | ``` 12 | 13 | # Quick start 14 | ```sh 15 | const source = ... an array or a generator function or anything that is iterable... ; 16 | const enumerable = Linqer.Enumerable.from(source); // now you can both iterate and use LINQ like functions 17 | const result = enumerable 18 | .where(item=>!!item.value) // like filter 19 | .select(item=>{ value: item.value, key: item.name }) // like map 20 | .groupBy(item=>item.key) 21 | .where(g=>g.length>10) 22 | .orderBy(g=>g.key) 23 | .selectMany() 24 | .skip(15) 25 | .take(5); 26 | for (const item of result) ... 27 | ``` 28 | in Node.js you have to prepend: 29 | ``` 30 | const Linqer = require('@siderite/linqer'); 31 | ``` 32 | 33 | in Typescript, use the .ts files directly or install @types/node and use require, I guess. I couldn't make it work for both TS and JS. 34 | I will try to make that happen in version 2, which will probably have a different file layout. 35 | 36 | 37 | # Licence 38 | MIT 39 | 40 | Array functions in Javascript create a new array for each operation, which is terribly wasteful. Using iterators and generator functions and objects, we can limit the operations to the items in the array that interest us, not all of them. 41 | 42 | # Blog post 43 | https://siderite.dev/blog/linq-in-javascript-linqer. Leave comments there or add Issues on GitHub for feedback and support. 44 | 45 | # Hosted 46 | Find it hosted on GitHub Pages and use it freely in your projects at: 47 | - https://siderite.github.io/LInQer/LInQer.min.js - main library 48 | - https://siderite.github.io/LInQer/LInQer.slim.min.js - only basic functionality 49 | - https://siderite.github.io/LInQer/LInQer.extra.min.js - extra functionality (needs main Linqer) 50 | 51 | # Reference 52 | Reference **Linqer.slim.js** for the basic methods: 53 | - from, empty, range, repeat - static on Linqer.Enumerable 54 | - length property - same as count, but throws error if the enumerable needs to be enumerated to get the length (no side effects) 55 | - concat 56 | - count 57 | - distinct 58 | - elementAt and elementAtOrDefault 59 | - first and firstOrDefault 60 | - last and lastOrDefault 61 | - min, max, stats (min, max and count) 62 | - select 63 | - skip and take 64 | - splice function - kind of useless, but it was an experiment to see if I can make Enumerable appear as an Array-like object 65 | - sum and sumAndCount (sum and count) 66 | - toArray 67 | - toList - similar to toArray, but returns a seekable Enumerable (itself if already seekable) that can do *count* and *elementAt* without iterating 68 | - where 69 | 70 | Reference **Linqer.js** for all of the original Enumerable methods, the ones in slim and then the following: 71 | - aggregate 72 | - all 73 | - any 74 | - append 75 | - average 76 | - asEnumerable 77 | - cast 78 | - contains 79 | - defaultIfEmpty - throws not implemented 80 | - except 81 | - intersect 82 | - join 83 | - groupBy 84 | - groupJoin 85 | - longCount 86 | - ofType 87 | - orderBy 88 | - orderByDescending 89 | - prepend 90 | - reverse 91 | - selectMany 92 | - sequenceEqual 93 | - single 94 | - singleOrDefault 95 | - skip - on an ordered enumerable 96 | - skipLast - on a regular or ordered enumerable 97 | - skipWhile 98 | - slice 99 | - take - on an ordered enumerable 100 | - takeLast - on a regular or ordered enumerable 101 | - takeWhile 102 | - thenBy - on an ordered enumerable 103 | - thenByDescending - on an ordered enumerable 104 | - toDictionary - throws not implemented 105 | - toLookup - throws not implemented 106 | - toMap 107 | - toObject 108 | - toHashSet - throws not implemented 109 | - toSet 110 | - union 111 | - zip 112 | 113 | Reference **Linqer.extra.js** (needs **Linqer.js**) for some additional methods: 114 | - shuffle - randomizes the enumerable 115 | - randomSample - implements random reservoir sampling of k items 116 | - distinctByHash - distinct based on a hashing function, not a comparer - faster 117 | - exceptByHash - except based on a hashing function, not a comparer - faster 118 | - intersectByHash - intersect based on a hashing function, not a comparer - faster 119 | - binarySearch - find the index of a value in a sorted enumerable by binary search 120 | - lag - joins each item of the enumerable with previous items from the same enumerable 121 | - lead - joins each item of the enumerable with next items from the same enumerable 122 | - padStart - pad enumerable at the start to a minimum length 123 | - padEnd - pad enumerable at the end to a minimum length 124 | 125 | # Original *Enumerable* .NET class 126 | 127 | The original C# class can be found here: https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable . 128 | 129 | # Building the solution 130 | 131 | The library has been ported to Typescript. Run **build.bat** to create the .js and .map files from the .ts code. -------------------------------------------------------------------------------- /tsconfig.slim.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "system", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | "outFile": "./LInQer.slim.js", /* Concatenate and emit output to single file. */ 15 | // "outDir": "./", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | "strictNullChecks": true, /* Enable strict null checks. */ 29 | "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | }, 66 | "compileOnSave": true, 67 | "files": ["./LInQer.Slim.ts"] 68 | } 69 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "system", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | "outFile": "./LInQer.js", /* Concatenate and emit output to single file. */ 15 | // "outDir": "./", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | "strictNullChecks": true, /* Enable strict null checks. */ 29 | "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | }, 66 | "compileOnSave": true, 67 | "files": ["./LInQer.Slim.ts","./LInQer.Enumerable.ts","./LInQer.GroupEnumerable.ts","./LInQer.OrderedEnumerable.ts","./npm.export.ts"] 68 | } 69 | -------------------------------------------------------------------------------- /tsconfig.all.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "system", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | "outFile": "./LInQer.all.js", /* Concatenate and emit output to single file. */ 15 | // "outDir": "./", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | "strictNullChecks": true, /* Enable strict null checks. */ 29 | "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | }, 66 | "compileOnSave": true, 67 | "files": ["./LInQer.Slim.ts","./LInQer.Enumerable.ts","./LInQer.GroupEnumerable.ts","./LInQer.OrderedEnumerable.ts","./LInQer.Extra.ts","./npm.export.ts"] 68 | } 69 | -------------------------------------------------------------------------------- /tsconfig.extra.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "system", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./LInQer.extra.js", /* Concatenate and emit output to single file. */ 15 | // "outDir": "./", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | "strictNullChecks": true, /* Enable strict null checks. */ 29 | "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | }, 66 | "compileOnSave": true, 67 | "files": ["./LInQer.extra.ts", "./LInQer.Slim.ts","./LInQer.Enumerable.ts","./LInQer.GroupEnumerable.ts","./LInQer.OrderedEnumerable.ts"] 68 | } 69 | -------------------------------------------------------------------------------- /LInQer.GroupEnumerable.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Linqer { 4 | 5 | export interface Enumerable extends Iterable { 6 | /** 7 | * Groups the elements of a sequence. 8 | * 9 | * @param {ISelector} keySelector 10 | * @returns {Enumerable} 11 | * @memberof Enumerable 12 | */ 13 | groupBy(keySelector: ISelector): Enumerable; 14 | /** 15 | * Correlates the elements of two sequences based on key equality and groups the results. A specified equalityComparer is used to compare keys. 16 | * WARNING: using the equality comparer will be slower 17 | * 18 | * @param {IterableType} iterable 19 | * @param {ISelector} innerKeySelector 20 | * @param {ISelector} outerKeySelector 21 | * @param {(item1: any, item2: any) => any} resultSelector 22 | * @param {IEqualityComparer} equalityComparer 23 | * @returns {Enumerable} 24 | * @memberof Enumerable 25 | */ 26 | groupJoin(iterable: IterableType, 27 | innerKeySelector: ISelector, 28 | outerKeySelector: ISelector, 29 | resultSelector: (item1: any, item2: any) => any, 30 | equalityComparer: IEqualityComparer): Enumerable; 31 | /** 32 | * Correlates the elements of two sequences based on matching keys. 33 | * WARNING: using the equality comparer will be slower 34 | * 35 | * @param {IterableType} iterable 36 | * @param {ISelector} innerKeySelector 37 | * @param {ISelector} outerKeySelector 38 | * @param {(item1: any, item2: any) => any} resultSelector 39 | * @param {IEqualityComparer} equalityComparer 40 | * @returns {Enumerable} 41 | * @memberof Enumerable 42 | */ 43 | join(iterable: IterableType, 44 | innerKeySelector: ISelector, 45 | outerKeySelector: ISelector, 46 | resultSelector: (item1: any, item2: any) => any, 47 | equalityComparer: IEqualityComparer): Enumerable; 48 | toLookup(): never; 49 | } 50 | 51 | 52 | /// Groups the elements of a sequence. 53 | Enumerable.prototype.groupBy = function (keySelector: ISelector): Enumerable { 54 | _ensureFunction(keySelector); 55 | const self: Enumerable = this; 56 | const gen = function* () { 57 | const groupMap = new Map(); 58 | let index = 0; 59 | // iterate all items and group them in a Map 60 | for (const item of self) { 61 | const key = keySelector(item, index); 62 | const group = groupMap.get(key); 63 | if (group) { 64 | group.push(item); 65 | } else { 66 | groupMap.set(key, [item]); 67 | } 68 | index++; 69 | } 70 | // then yield a GroupEnumerable for each group 71 | for (const [key, items] of groupMap) { 72 | const group = new GroupEnumerable(items, key); 73 | yield group; 74 | } 75 | }; 76 | const result = new Enumerable(gen); 77 | return result; 78 | } 79 | 80 | /// Correlates the elements of two sequences based on key equality and groups the results. A specified equalityComparer is used to compare keys. 81 | /// WARNING: using the equality comparer will be slower 82 | Enumerable.prototype.groupJoin = function (iterable: IterableType, 83 | innerKeySelector: ISelector, 84 | outerKeySelector: ISelector, 85 | resultSelector: (item1: any, item2: any) => any, 86 | equalityComparer: IEqualityComparer = EqualityComparer.default): Enumerable { 87 | 88 | const self: Enumerable = this; 89 | const gen = equalityComparer === EqualityComparer.default 90 | ? function* () { 91 | const lookup = new Enumerable(iterable) 92 | .groupBy(outerKeySelector) 93 | .toMap(g => g.key, g => g); 94 | let index = 0; 95 | for (const innerItem of self) { 96 | const arr = _toArray(lookup.get(innerKeySelector(innerItem, index))); 97 | yield resultSelector(innerItem, arr); 98 | index++; 99 | } 100 | } 101 | : function* () { 102 | let innerIndex = 0; 103 | for (const innerItem of self) { 104 | const arr = []; 105 | let outerIndex = 0; 106 | for (const outerItem of Enumerable.from(iterable)) { 107 | if (equalityComparer(innerKeySelector(innerItem, innerIndex), outerKeySelector(outerItem, outerIndex))) { 108 | arr.push(outerItem); 109 | } 110 | outerIndex++; 111 | } 112 | yield resultSelector(innerItem, arr); 113 | innerIndex++; 114 | } 115 | }; 116 | return new Enumerable(gen); 117 | } 118 | 119 | /// Correlates the elements of two sequences based on matching keys. 120 | /// WARNING: using the equality comparer will be slower 121 | Enumerable.prototype.join = function (iterable: IterableType, 122 | innerKeySelector: ISelector, 123 | outerKeySelector: ISelector, 124 | resultSelector: (item1: any, item2: any) => any, 125 | equalityComparer: IEqualityComparer = EqualityComparer.default): Enumerable { 126 | const self: Enumerable = this; 127 | const gen = equalityComparer === EqualityComparer.default 128 | ? function* () { 129 | const lookup = new Enumerable(iterable) 130 | .groupBy(outerKeySelector) 131 | .toMap(g => g.key, g => g); 132 | let index = 0; 133 | for (const innerItem of self) { 134 | const group = lookup.get(innerKeySelector(innerItem, index)); 135 | if (group) { 136 | for (const outerItem of group) { 137 | yield resultSelector(innerItem, outerItem); 138 | } 139 | } 140 | index++; 141 | } 142 | } 143 | : function* () { 144 | let innerIndex = 0; 145 | for (const innerItem of self) { 146 | let outerIndex = 0; 147 | for (const outerItem of Enumerable.from(iterable)) { 148 | if (equalityComparer(innerKeySelector(innerItem, innerIndex), outerKeySelector(outerItem, outerIndex))) { 149 | yield resultSelector(innerItem, outerItem); 150 | } 151 | outerIndex++; 152 | } 153 | innerIndex++; 154 | } 155 | }; 156 | return new Enumerable(gen); 157 | } 158 | 159 | 160 | Enumerable.prototype.toLookup = function (): never { 161 | throw new Error('use groupBy instead of toLookup'); 162 | } 163 | 164 | /** 165 | * An Enumerable that also exposes a group key 166 | * 167 | * @export 168 | * @class GroupEnumerable 169 | * @extends {Enumerable} 170 | */ 171 | export class GroupEnumerable extends Enumerable { 172 | key: string; 173 | constructor(iterable: IterableType, key: string) { 174 | super(iterable); 175 | this.key = key; 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /tests.performance.js: -------------------------------------------------------------------------------- 1 | Enumerable = Linqer.Enumerable; 2 | const largeNumber = 10000000; 3 | 4 | // performance tests 5 | QUnit.module('performance tests'); 6 | 7 | QUnit.test( "Use only items that are required - standard", function( assert ) { 8 | const largeArray = Array(largeNumber).fill(10); 9 | const startTime = performance.now(); 10 | const someCalculation = largeArray.filter(x=>x===10).map(x=>'v'+x).slice(100,110); 11 | Array.from(someCalculation); 12 | const endTime = performance.now(); 13 | assert.ok(true,'Standard array use took '+(endTime-startTime)+'milliseconds'); 14 | }); 15 | 16 | QUnit.test( "Use only items that are required - Enumerable", function( assert ) { 17 | const largeArray = Array(largeNumber).fill(10); 18 | const startTime = performance.now(); 19 | const someCalculation = Enumerable.from(largeArray).where(x=>x===10).select(x=>'v'+x).skip(100).take(10).toArray(); 20 | Array.from(someCalculation); 21 | const endTime = performance.now(); 22 | assert.ok(true,'Enumerable use took '+(endTime-startTime)+'milliseconds'); 23 | }); 24 | 25 | QUnit.test( "OrderBy performance random", function( assert ) { 26 | const size = largeNumber; 27 | const largeArray1 = Enumerable.range(1,size).shuffle().toArray(); 28 | 29 | let startTime = performance.now(); 30 | const result1 = Array.from(largeArray1).sort((i1,i2)=>i2-i1); 31 | let endTime = performance.now(); 32 | assert.ok(true,'Order '+size+' items using Array.from then .sort took '+(endTime-startTime)+' milliseconds'); 33 | 34 | startTime = performance.now(); 35 | const result2 = Enumerable.from(largeArray1).orderBy(i=>size-i).useBrowserSort().toArray(); 36 | endTime = performance.now(); 37 | assert.ok(true,'Order '+size+' items using browser sort internally took '+(endTime-startTime)+' milliseconds'); 38 | 39 | for (let i=0; isize-i).useBrowserSort().toArray(); 70 | endTime = performance.now(); 71 | assert.ok(true,'Order '+size+' items using browser sort internally took '+(endTime-startTime)+' milliseconds'); 72 | 73 | for (let i=0; isize-i).useBrowserSort().toArray(); 104 | endTime = performance.now(); 105 | assert.ok(true,'Order '+size+' items using browser sort internally took '+(endTime-startTime)+' milliseconds'); 106 | 107 | for (let i=0; isize-i).skip(100000).take(10000).toArray(); 139 | endTime = performance.now(); 140 | assert.ok(true,'Order '+size+' items skip and take using QuickSort took '+(endTime-startTime)+' milliseconds'); 141 | 142 | for (let i=0; i +(i1) === +(i2)).toArray(); 71 | assert.deepEqual(result, [1, 2, 3], "Passed!"); 72 | }); 73 | 74 | QUnit.test("Enumerable.elementAt in range array", function (assert) { 75 | const result = Enumerable.from([1, 2, 2, 3, '3']).elementAt(3); 76 | assert.deepEqual(result, 3, "Passed!"); 77 | }); 78 | QUnit.test("Enumerable.elementAt below range array", function (assert) { 79 | assert.throws(() => Enumerable.from([1, 2, 2, 3, '3']).elementAt(-3), "Passed!"); 80 | }); 81 | QUnit.test("Enumerable.elementAt above range array", function (assert) { 82 | assert.throws(() => Enumerable.from([1, 2, 2, 3, '3']).elementAt(30), "Passed!"); 83 | }); 84 | QUnit.test("Enumerable.elementAtOrDefault in range array", function (assert) { 85 | const result = Enumerable.from([1, 2, 2, 3, '3']).elementAtOrDefault(3); 86 | assert.deepEqual(result, 3, "Passed!"); 87 | }); 88 | QUnit.test("Enumerable.elementAtOrDefault below range array", function (assert) { 89 | const result = Enumerable.from([1, 2, 2, 3, '3']).elementAtOrDefault(-3); 90 | assert.deepEqual(result, undefined, "Passed!"); 91 | }); 92 | QUnit.test("Enumerable.elementAtOrDefault above range array", function (assert) { 93 | const result = Enumerable.from([1, 2, 2, 3, '3']).elementAtOrDefault(30); 94 | assert.deepEqual(result, undefined, "Passed!"); 95 | }); 96 | 97 | QUnit.test("Enumerable.first", function (assert) { 98 | const result = Enumerable.from([1, 2, 2, 3, '3']).first(); 99 | assert.deepEqual(result, 1, "Passed!"); 100 | }); 101 | QUnit.test("Enumerable.first empty", function (assert) { 102 | assert.throws(() => Enumerable.from([]).first(), "Passed!"); 103 | }); 104 | QUnit.test("Enumerable.firstOrDefault", function (assert) { 105 | const result = Enumerable.from([]).firstOrDefault(); 106 | assert.deepEqual(result, undefined, "Passed!"); 107 | }); 108 | 109 | QUnit.test("Enumerable.last", function (assert) { 110 | const result = Enumerable.from([1, 2, 2, 3, '3']).last(); 111 | assert.deepEqual(result, '3', "Passed!"); 112 | }); 113 | QUnit.test("Enumerable.last empty", function (assert) { 114 | assert.throws(() => Enumerable.from([]).last(), "Passed!"); 115 | }); 116 | QUnit.test("Enumerable.lastOrDefault", function (assert) { 117 | const result = Enumerable.from([]).lastOrDefault(); 118 | assert.deepEqual(result, undefined, "Passed!"); 119 | }); 120 | 121 | QUnit.test("Enumerable.max numbers", function (assert) { 122 | const result = Enumerable.from([3, 5, 1, 2, 56, 2, -100, 43]).max(); 123 | assert.deepEqual(result, 56, "Passed!"); 124 | }); 125 | QUnit.test("Enumerable.max strings", function (assert) { 126 | const result = Enumerable.from(['ba', 'a', 'abba', 'aaa', 'bb']).max(); 127 | assert.deepEqual(result, 'bb', "Passed!"); 128 | }); 129 | 130 | QUnit.test("Enumerable.min number", function (assert) { 131 | const result = Enumerable.from([3, 5, 1, 2, 56, 2, -100, 43]).min(); 132 | assert.deepEqual(result, -100, "Passed!"); 133 | }); 134 | QUnit.test("Enumerable.min custom comparer", function (assert) { 135 | const result = Enumerable.from([3, 5, 1, 2, 56, 2, -100, 43]).min((i1, i2) => i1.toString().length - i2.toString().length); 136 | assert.deepEqual(result, 3, "Passed!"); 137 | }); 138 | 139 | QUnit.test("Enumerable.select", function (assert) { 140 | const result = Enumerable.from(['a', 1, 3, 2]).select(item => Number.isInteger(item) ? item * item : item + '^2').toArray(); 141 | assert.deepEqual(result, ['a^2', 1, 9, 4], "Passed!"); 142 | }); 143 | 144 | QUnit.test("Enumerable.skip", function (assert) { 145 | const result = Enumerable.from([1, 2, 3, 4, 5]).skip(2).toArray(); 146 | assert.deepEqual(result, [3, 4, 5], "Passed!"); 147 | }); 148 | 149 | QUnit.test("Enumerable.sum numbers", function (assert) { 150 | const result = Enumerable.from([1, 2, 3, 4, 5]).sum(); 151 | assert.deepEqual(result, 15, "Passed!"); 152 | }); 153 | QUnit.test("Enumerable.sum numbers with some strings", function (assert) { 154 | const result = Enumerable.from([1, 2, 3, 4, 5, '6']).sum(); 155 | assert.deepEqual(result, Number.NaN, "Passed!"); 156 | }); 157 | 158 | QUnit.test("Enumerable.take", function (assert) { 159 | const result = Enumerable.from([1, 2, 3, 4, 5]).take(2).toArray(); 160 | assert.deepEqual(result, [1, 2], "Passed!"); 161 | }); 162 | 163 | QUnit.test("Enumerable.where", function (assert) { 164 | const result = Enumerable.from([1, 2, 3, 4, 5]).where(item => item % 2).toArray(); 165 | assert.deepEqual(result, [1, 3, 5], "Passed!"); 166 | }); 167 | QUnit.test("Enumerable.where with index", function (assert) { 168 | const idxs = []; 169 | const result = Enumerable.from([1, 2, 3, 4, 5]).where((item, index) => { 170 | idxs.push(index); 171 | return item % 2; 172 | }).toArray(); 173 | assert.deepEqual(result, [1, 3, 5], "Passed!"); 174 | assert.deepEqual(idxs, [0, 1, 2, 3, 4], "Passed!"); 175 | }); 176 | 177 | 178 | // composable count tests 179 | QUnit.module('composable count tests'); 180 | 181 | QUnit.test("Enumerable.concat seekable count ", function (assert) { 182 | const result = Enumerable.range(100, 10000).concat(Enumerable.range(10, 20000)); 183 | assert.deepEqual(result.count(), 30000, "Passed!"); 184 | assert.deepEqual(result._wasIterated, false, "Passed!"); 185 | }); 186 | QUnit.test("Enumerable.concat unseekable count", function (assert) { 187 | const iterable = Enumerable.from(function* () { yield 1; }) 188 | const result = Enumerable.range(100, 10000).concat(iterable); 189 | assert.deepEqual(result.count(), 10001, "Passed!"); 190 | assert.deepEqual(result._wasIterated, false, "Passed!"); 191 | assert.deepEqual(iterable._wasIterated, true, "Passed!"); 192 | }); 193 | QUnit.test("skip count", function (assert) { 194 | const result = Enumerable.range(100, 10000).skip(5); 195 | assert.deepEqual(result.count(), 9995, "Passed!"); 196 | assert.deepEqual(result._wasIterated, false, "Passed!"); 197 | }); 198 | QUnit.test("take count", function (assert) { 199 | const result = Enumerable.range(100, 10000).take(5); 200 | assert.deepEqual(result.count(), 5, "Passed!"); 201 | assert.deepEqual(result._wasIterated, false, "Passed!"); 202 | }); 203 | 204 | // seek tests 205 | QUnit.module('seek tests'); 206 | QUnit.test("Enumerable.empty seek", function (assert) { 207 | const result = Enumerable.from([]); 208 | assert.deepEqual(result.count(), 0, "Passed!"); 209 | assert.deepEqual(result.elementAtOrDefault(10000), undefined, "Passed!"); 210 | assert.deepEqual(result._wasIterated, false, "Passed!"); 211 | }); 212 | QUnit.test("concat array seek", function (assert) { 213 | const result = Enumerable.range(0, 100000).concat([0, 1, 2, 3, 4, 5]); 214 | assert.deepEqual(result.count(), 100006, "Passed!"); 215 | assert.deepEqual(result.elementAtOrDefault(100004), 4, "Passed!"); 216 | assert.deepEqual(result._wasIterated, false, "Passed!"); 217 | }); 218 | QUnit.test("concat Enumerable seek", function (assert) { 219 | const result = Enumerable.range(0, 100000).concat(Enumerable.range(0, 6)); 220 | assert.deepEqual(result.count(), 100006, "Passed!"); 221 | assert.deepEqual(result.elementAtOrDefault(100004), 4, "Passed!"); 222 | assert.deepEqual(result._wasIterated, false, "Passed!"); 223 | }); 224 | QUnit.test("select seek", function (assert) { 225 | const result = Enumerable.range(0, 100000).select(i => 'a' + i); 226 | assert.deepEqual(result.count(), 100000, "Passed!"); 227 | assert.deepEqual(result.elementAtOrDefault(10000), 'a10000', "Passed!"); 228 | assert.deepEqual(result.elementAtOrDefault(1000000), undefined, "Passed!"); 229 | assert.deepEqual(result._wasIterated, false, "Passed!"); 230 | }); 231 | QUnit.test("skip seek", function (assert) { 232 | const result = Enumerable.range(0, 100000).skip(50000); 233 | assert.deepEqual(result.count(), 50000, "Passed!"); 234 | assert.deepEqual(result.elementAtOrDefault(10000), 60000, "Passed!"); 235 | assert.deepEqual(result.elementAtOrDefault(1000000), undefined, "Passed!"); 236 | assert.deepEqual(result._wasIterated, false, "Passed!"); 237 | }); 238 | QUnit.test("take seek", function (assert) { 239 | const result = Enumerable.range(0, 100000).take(50000); 240 | assert.deepEqual(result.count(), 50000, "Passed!"); 241 | assert.deepEqual(result.elementAtOrDefault(10000), 10000, "Passed!"); 242 | assert.deepEqual(result.elementAtOrDefault(50000), undefined, "Passed!"); 243 | assert.deepEqual(result.elementAtOrDefault(1000000), undefined, "Passed!"); 244 | assert.deepEqual(result._wasIterated, false, "Passed!"); 245 | }); 246 | -------------------------------------------------------------------------------- /LInQer.slim.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"LInQer.Slim.js","sourceRoot":"","sources":["LInQer.Slim.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /LInQer.OrderedEnumerable.ts: -------------------------------------------------------------------------------- 1 | /// 2 | namespace Linqer { 3 | 4 | export interface Enumerable extends Iterable { 5 | /** 6 | * Sorts the elements of a sequence in ascending order. 7 | * 8 | * @param {ISelector} keySelector 9 | * @returns {OrderedEnumerable} 10 | * @memberof Enumerable 11 | */ 12 | orderBy(keySelector: ISelector): OrderedEnumerable; 13 | /** 14 | * Sorts the elements of a sequence in descending order. 15 | * 16 | * @param {ISelector} keySelector 17 | * @returns {OrderedEnumerable} 18 | * @memberof Enumerable 19 | */ 20 | orderByDescending(keySelector: ISelector): OrderedEnumerable; 21 | /** 22 | * use QuickSort for ordering (default). Recommended when take, skip, takeLast, skipLast are used after orderBy 23 | * 24 | * @returns {Enumerable} 25 | * @memberof Enumerable 26 | */ 27 | useQuickSort(): Enumerable; 28 | /** 29 | * use the default browser sort implementation for ordering at all times 30 | * 31 | * @returns {Enumerable} 32 | * @memberof Enumerable 33 | */ 34 | useBrowserSort(): Enumerable; 35 | } 36 | 37 | 38 | /// Sorts the elements of a sequence in ascending order. 39 | Enumerable.prototype.orderBy = function (keySelector: ISelector): OrderedEnumerable { 40 | if (keySelector) { 41 | _ensureFunction(keySelector); 42 | } else { 43 | keySelector = item => item; 44 | } 45 | return new OrderedEnumerable(this, keySelector, true); 46 | }; 47 | 48 | /// Sorts the elements of a sequence in descending order. 49 | Enumerable.prototype.orderByDescending = function (keySelector: ISelector): OrderedEnumerable { 50 | if (keySelector) { 51 | _ensureFunction(keySelector); 52 | } else { 53 | keySelector = item => item; 54 | } 55 | return new OrderedEnumerable(this, keySelector, false); 56 | }; 57 | 58 | /// use QuickSort for ordering (default). Recommended when take, skip, takeLast, skipLast are used after orderBy 59 | Enumerable.prototype.useQuickSort = function (): Enumerable { 60 | this._useQuickSort = true; 61 | return this; 62 | }; 63 | 64 | /// use the default browser sort implementation for ordering at all times 65 | Enumerable.prototype.useBrowserSort = function (): Enumerable { 66 | this._useQuickSort = false; 67 | return this; 68 | }; 69 | 70 | 71 | //static sort: (arr: any[], comparer?: IComparer) => void; 72 | Enumerable.sort = function (arr: any[], comparer: IComparer = _defaultComparer): any[] { 73 | _quickSort(arr, 0, arr.length - 1, comparer, 0, Number.MAX_SAFE_INTEGER); 74 | return arr; 75 | } 76 | 77 | enum RestrictionType { 78 | skip, 79 | skipLast, 80 | take, 81 | takeLast 82 | } 83 | 84 | /** 85 | * An Enumerable yielding ordered items 86 | * 87 | * @export 88 | * @class OrderedEnumerable 89 | * @extends {Enumerable} 90 | */ 91 | export class OrderedEnumerable extends Enumerable { 92 | _keySelectors: { keySelector: ISelector, ascending: boolean }[]; 93 | _restrictions: { type: RestrictionType, nr: number }[]; 94 | 95 | /** 96 | *Creates an instance of OrderedEnumerable. 97 | * @param {IterableType} src 98 | * @param {ISelector} [keySelector] 99 | * @param {boolean} [ascending=true] 100 | * @memberof OrderedEnumerable 101 | */ 102 | constructor(src: IterableType, 103 | keySelector?: ISelector, 104 | ascending: boolean = true) { 105 | super(src); 106 | this._keySelectors = []; 107 | this._restrictions = []; 108 | if (keySelector) { 109 | this._keySelectors.push({ keySelector: keySelector, ascending: ascending }); 110 | } 111 | const self: OrderedEnumerable = this; 112 | // generator gets an array of the original, 113 | // sorted inside the interval determined by functions such as skip, take, skipLast, takeLast 114 | this._generator = function* () { 115 | let { startIndex, endIndex, arr } = this.getSortedArray(); 116 | if (arr) { 117 | for (let index = startIndex; index < endIndex; index++) { 118 | yield arr[index]; 119 | } 120 | } 121 | }; 122 | 123 | // the count is the difference between the end and start indexes 124 | // if no skip/take functions were used, this will be the original count 125 | this._count = () => { 126 | const totalCount = Enumerable.from(self._src).count(); 127 | const { startIndex, endIndex } = this.getStartAndEndIndexes(self._restrictions, totalCount); 128 | return endIndex - startIndex; 129 | }; 130 | // an ordered enumerable cannot seek 131 | this._canSeek=false; 132 | this._tryGetAt = ()=>{ throw new Error('Ordered enumerables cannot seek'); }; 133 | } 134 | 135 | private getSortedArray() { 136 | const self = this; 137 | let startIndex: number; 138 | let endIndex: number; 139 | let arr: any[] | null = null; 140 | const innerEnumerable = self._src as Enumerable; 141 | _ensureInternalTryGetAt(innerEnumerable); 142 | // try to avoid enumerating the entire original into an array 143 | if (innerEnumerable._canSeek) { 144 | ({ startIndex, endIndex } = self.getStartAndEndIndexes(self._restrictions, innerEnumerable.count())); 145 | } else { 146 | arr = Array.from(self._src); 147 | ({ startIndex, endIndex } = self.getStartAndEndIndexes(self._restrictions, arr.length)); 148 | } 149 | if (startIndex < endIndex) { 150 | if (!arr) { 151 | arr = Array.from(self._src); 152 | } 153 | // only quicksort supports partial ordering inside an interval 154 | const sort: (item1: any, item2: any) => void = self._useQuickSort 155 | ? (a, c) => _quickSort(a, 0, a.length - 1, c, startIndex, endIndex) 156 | : (a, c) => a.sort(c); 157 | const sortFunc = self.generateSortFunc(self._keySelectors); 158 | sort(arr, sortFunc); 159 | return { 160 | startIndex, 161 | endIndex, 162 | arr 163 | }; 164 | } else { 165 | return { 166 | startIndex, 167 | endIndex, 168 | arr: null 169 | }; 170 | } 171 | } 172 | 173 | private generateSortFunc(selectors: { keySelector: ISelector, ascending: boolean }[]): (i1: any, i2: any) => number { 174 | // simplify the selectors into an array of comparers 175 | const comparers = selectors.map(s => { 176 | const f = s.keySelector; 177 | const comparer = (i1: any, i2: any) => { 178 | const k1 = f(i1); 179 | const k2 = f(i2); 180 | if (k1 > k2) return 1; 181 | if (k1 < k2) return -1; 182 | return 0; 183 | }; 184 | return s.ascending 185 | ? comparer 186 | : (i1: any, i2: any) => -comparer(i1, i2); 187 | }); 188 | // optimize the resulting sort function in the most common case 189 | // (ordered by a single criterion) 190 | return comparers.length == 1 191 | ? comparers[0] 192 | : (i1: any, i2: any) => { 193 | for (let i = 0; i < comparers.length; i++) { 194 | const v = comparers[i](i1, i2); 195 | if (v) return v; 196 | } 197 | return 0; 198 | }; 199 | } 200 | 201 | /// calculate the interval in which an array needs to have ordered items for this ordered enumerable 202 | private getStartAndEndIndexes(restrictions: { type: RestrictionType, nr: number }[], arrLength: number) { 203 | let startIndex = 0; 204 | let endIndex = arrLength; 205 | for (const restriction of restrictions) { 206 | switch (restriction.type) { 207 | case RestrictionType.take: 208 | endIndex = Math.min(endIndex, startIndex + restriction.nr); 209 | break; 210 | case RestrictionType.skip: 211 | startIndex = Math.min(endIndex, startIndex + restriction.nr); 212 | break; 213 | case RestrictionType.takeLast: 214 | startIndex = Math.max(startIndex, endIndex - restriction.nr); 215 | break; 216 | case RestrictionType.skipLast: 217 | endIndex = Math.max(startIndex, endIndex - restriction.nr); 218 | break; 219 | } 220 | } 221 | return { startIndex, endIndex }; 222 | } 223 | 224 | 225 | /** 226 | * Performs a subsequent ordering of the elements in a sequence in ascending order. 227 | * 228 | * @param {ISelector} keySelector 229 | * @returns {OrderedEnumerable} 230 | * @memberof OrderedEnumerable 231 | */ 232 | thenBy(keySelector: ISelector): OrderedEnumerable { 233 | this._keySelectors.push({ keySelector: keySelector, ascending: true }); 234 | return this; 235 | } 236 | /** 237 | * Performs a subsequent ordering of the elements in a sequence in descending order. 238 | * 239 | * @param {ISelector} keySelector 240 | * @returns {OrderedEnumerable} 241 | * @memberof OrderedEnumerable 242 | */ 243 | thenByDescending(keySelector: ISelector): OrderedEnumerable { 244 | this._keySelectors.push({ keySelector: keySelector, ascending: false }); 245 | return this; 246 | } 247 | 248 | /** 249 | * Deferred and optimized implementation of take 250 | * 251 | * @param {number} nr 252 | * @returns {OrderedEnumerable} 253 | * @memberof OrderedEnumerable 254 | */ 255 | take(nr: number): OrderedEnumerable { 256 | this._restrictions.push({ type: RestrictionType.take, nr: nr }); 257 | return this; 258 | } 259 | 260 | /** 261 | * Deferred and optimized implementation of takeLast 262 | * 263 | * @param {number} nr 264 | * @returns {OrderedEnumerable} 265 | * @memberof OrderedEnumerable 266 | */ 267 | takeLast(nr: number): OrderedEnumerable { 268 | this._restrictions.push({ type: RestrictionType.takeLast, nr: nr }); 269 | return this; 270 | } 271 | 272 | /** 273 | * Deferred and optimized implementation of skip 274 | * 275 | * @param {number} nr 276 | * @returns {OrderedEnumerable} 277 | * @memberof OrderedEnumerable 278 | */ 279 | skip(nr: number): OrderedEnumerable { 280 | this._restrictions.push({ type: RestrictionType.skip, nr: nr }); 281 | return this; 282 | } 283 | 284 | /** 285 | * Deferred and optimized implementation of skipLast 286 | * 287 | * @param {number} nr 288 | * @returns {OrderedEnumerable} 289 | * @memberof OrderedEnumerable 290 | */ 291 | skipLast(nr: number): OrderedEnumerable { 292 | this._restrictions.push({ type: RestrictionType.skipLast, nr: nr }); 293 | return this; 294 | } 295 | 296 | 297 | /** 298 | * An optimized implementation of toArray 299 | * 300 | * @returns {any[]} 301 | * @memberof OrderedEnumerable 302 | */ 303 | toArray(): any[] { 304 | const { startIndex, endIndex, arr } = this.getSortedArray(); 305 | return arr 306 | ? arr.slice(startIndex, endIndex) 307 | : []; 308 | } 309 | 310 | 311 | /** 312 | * An optimized implementation of toMap 313 | * 314 | * @param {ISelector} keySelector 315 | * @param {ISelector} [valueSelector=x => x] 316 | * @returns {Map} 317 | * @memberof OrderedEnumerable 318 | */ 319 | toMap(keySelector: ISelector, valueSelector: ISelector = x => x): Map { 320 | _ensureFunction(keySelector); 321 | _ensureFunction(valueSelector); 322 | const result = new Map(); 323 | const arr = this.toArray(); 324 | for (let i = 0; i < arr.length; i++) { 325 | result.set(keySelector(arr[i], i), valueSelector(arr[i], i)); 326 | } 327 | return result; 328 | } 329 | 330 | 331 | /** 332 | * An optimized implementation of toObject 333 | * 334 | * @param {ISelector} keySelector 335 | * @param {ISelector} [valueSelector=x => x] 336 | * @returns {{ [key: string]: any }} 337 | * @memberof OrderedEnumerable 338 | */ 339 | toObject(keySelector: ISelector, valueSelector: ISelector = x => x): { [key: string]: any } { 340 | _ensureFunction(keySelector); 341 | _ensureFunction(valueSelector); 342 | const result: { [key: string]: any } = {}; 343 | const arr = this.toArray(); 344 | for (let i = 0; i < arr.length; i++) { 345 | result[keySelector(arr[i], i)] = valueSelector(arr[i], i); 346 | } 347 | return result; 348 | } 349 | 350 | 351 | /** 352 | * An optimized implementation of to Set 353 | * 354 | * @returns {Set} 355 | * @memberof OrderedEnumerable 356 | */ 357 | toSet(): Set { 358 | const result = new Set(); 359 | const arr = this.toArray(); 360 | for (let i = 0; i < arr.length; i++) { 361 | result.add(arr[i]); 362 | } 363 | return result; 364 | } 365 | 366 | } 367 | 368 | 369 | const _insertionSortThreshold = 64; 370 | /// insertion sort is used for small intervals 371 | function _insertionsort(arr: any[], leftIndex: number, rightIndex: number, comparer: IComparer) { 372 | for (let j = leftIndex; j <= rightIndex; j++) { 373 | const key = arr[j]; 374 | let i = j - 1; 375 | while (i >= leftIndex && comparer(arr[i], key) > 0) { 376 | arr[i + 1] = arr[i]; 377 | i--; 378 | } 379 | arr[i + 1] = key; 380 | } 381 | } 382 | 383 | /// swap two items in an array by index 384 | function _swapArrayItems(array: any[], leftIndex: number, rightIndex: number): void { 385 | const temp = array[leftIndex]; 386 | array[leftIndex] = array[rightIndex]; 387 | array[rightIndex] = temp; 388 | } 389 | // Quicksort partition by center value coming from both sides 390 | function _partition(items: any[], left: number, right: number, comparer: IComparer) { 391 | const pivot = items[(right + left) >> 1]; 392 | while (left <= right) { 393 | while (comparer(items[left], pivot) < 0) { 394 | left++; 395 | } 396 | while (comparer(items[right], pivot) > 0) { 397 | right--; 398 | } 399 | if (left < right) { 400 | _swapArrayItems(items, left, right); 401 | left++; 402 | right--; 403 | } else { 404 | if (left === right) return left + 1; 405 | } 406 | } 407 | return left; 408 | } 409 | 410 | /// optimized Quicksort algorithm 411 | function _quickSort(items: any[], left: number, right: number, comparer: IComparer = _defaultComparer, minIndex: number = 0, maxIndex: number = Number.MAX_SAFE_INTEGER) { 412 | if (!items.length) return items; 413 | 414 | // store partition indexes to be processed in here 415 | const partitions: { left: number, right: number }[] = []; 416 | partitions.push({ left, right }); 417 | let size = 1; 418 | // the actual size of the partitions array never decreases 419 | // but we keep score of the number of partitions in 'size' 420 | // and we reuse slots whenever possible 421 | while (size) { 422 | const partition = { left, right } = partitions[size-1]; 423 | if (right - left < _insertionSortThreshold) { 424 | _insertionsort(items, left, right, comparer); 425 | size--; 426 | continue; 427 | } 428 | const index = _partition(items, left, right, comparer); 429 | if (left < index - 1 && index - 1 >= minIndex) { 430 | partition.right = index - 1; 431 | if (index < right && index < maxIndex) { 432 | partitions[size]={ left: index, right }; 433 | size++; 434 | } 435 | } else { 436 | if (index < right && index < maxIndex) { 437 | partition.left = index; 438 | } else { 439 | size--; 440 | } 441 | } 442 | } 443 | return items; 444 | } 445 | } -------------------------------------------------------------------------------- /LInQer.extra.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /// 3 | /// 4 | /// 5 | var Linqer; 6 | /// 7 | /// 8 | /// 9 | (function (Linqer) { 10 | /// randomizes the enumerable (partial Fisher-Yates) 11 | Linqer.Enumerable.prototype.shuffle = function () { 12 | const self = this; 13 | function* gen() { 14 | const arr = self.toArray(); 15 | const len = arr.length; 16 | let n = 0; 17 | while (n < len) { 18 | let k = n + Math.floor(Math.random() * (len - n)); 19 | const value = arr[k]; 20 | arr[k] = arr[n]; 21 | arr[n] = value; 22 | n++; 23 | yield value; 24 | } 25 | } 26 | const result = Linqer.Enumerable.from(gen); 27 | result._count = () => self.count(); 28 | return result; 29 | }; 30 | /// implements random reservoir sampling of k items, with the option to specify a maximum limit for the items 31 | Linqer.Enumerable.prototype.randomSample = function (k, limit = Number.MAX_SAFE_INTEGER) { 32 | let index = 0; 33 | const sample = []; 34 | Linqer._ensureInternalTryGetAt(this); 35 | if (this._canSeek) { // L algorithm 36 | const length = this.count(); 37 | let index = 0; 38 | for (index = 0; index < k && index < limit && index < length; index++) { 39 | sample.push(this.elementAt(index)); 40 | } 41 | let W = Math.exp(Math.log(Math.random()) / k); 42 | while (index < length && index < limit) { 43 | index += Math.floor(Math.log(Math.random()) / Math.log(1 - W)) + 1; 44 | if (index < length && index < limit) { 45 | sample[Math.floor(Math.random() * k)] = this.elementAt(index); 46 | W *= Math.exp(Math.log(Math.random()) / k); 47 | } 48 | } 49 | } 50 | else { // R algorithm 51 | for (const item of this) { 52 | if (index < k) { 53 | sample.push(item); 54 | } 55 | else { 56 | const j = Math.floor(Math.random() * index); 57 | if (j < k) { 58 | sample[j] = item; 59 | } 60 | } 61 | index++; 62 | if (index >= limit) 63 | break; 64 | } 65 | } 66 | return Linqer.Enumerable.from(sample); 67 | }; 68 | /// returns the distinct values based on a hashing function 69 | Linqer.Enumerable.prototype.distinctByHash = function (hashFunc) { 70 | // this is much more performant than distinct with a custom comparer 71 | const self = this; 72 | const gen = function* () { 73 | const distinctValues = new Set(); 74 | for (const item of self) { 75 | const size = distinctValues.size; 76 | distinctValues.add(hashFunc(item)); 77 | if (size < distinctValues.size) { 78 | yield item; 79 | } 80 | } 81 | }; 82 | return new Linqer.Enumerable(gen); 83 | }; 84 | /// returns the values that have different hashes from the items of the iterable provided 85 | Linqer.Enumerable.prototype.exceptByHash = function (iterable, hashFunc) { 86 | // this is much more performant than except with a custom comparer 87 | Linqer._ensureIterable(iterable); 88 | const self = this; 89 | const gen = function* () { 90 | const distinctValues = Linqer.Enumerable.from(iterable).select(hashFunc).toSet(); 91 | for (const item of self) { 92 | if (!distinctValues.has(hashFunc(item))) { 93 | yield item; 94 | } 95 | } 96 | }; 97 | return new Linqer.Enumerable(gen); 98 | }; 99 | /// returns the values that have the same hashes as items of the iterable provided 100 | Linqer.Enumerable.prototype.intersectByHash = function (iterable, hashFunc) { 101 | // this is much more performant than intersect with a custom comparer 102 | Linqer._ensureIterable(iterable); 103 | const self = this; 104 | const gen = function* () { 105 | const distinctValues = Linqer.Enumerable.from(iterable).select(hashFunc).toSet(); 106 | for (const item of self) { 107 | if (distinctValues.has(hashFunc(item))) { 108 | yield item; 109 | } 110 | } 111 | }; 112 | return new Linqer.Enumerable(gen); 113 | }; 114 | /// returns the index of a value in an ordered enumerable or false if not found 115 | /// WARNING: use the same comparer as the one used in the ordered enumerable. The algorithm assumes the enumerable is already sorted. 116 | Linqer.Enumerable.prototype.binarySearch = function (value, comparer = Linqer._defaultComparer) { 117 | let enumerable = this.toList(); 118 | let start = 0; 119 | let end = enumerable.count() - 1; 120 | while (start <= end) { 121 | const mid = (start + end) >> 1; 122 | const comp = comparer(enumerable.elementAt(mid), value); 123 | if (comp == 0) 124 | return mid; 125 | if (comp < 0) { 126 | start = mid + 1; 127 | } 128 | else { 129 | end = mid - 1; 130 | } 131 | } 132 | return false; 133 | }; 134 | /// joins each item of the enumerable with previous items from the same enumerable 135 | Linqer.Enumerable.prototype.lag = function (offset, zipper) { 136 | if (!offset) { 137 | throw new Error('offset has to be positive'); 138 | } 139 | if (offset < 0) { 140 | throw new Error('offset has to be positive. Use .lead if you want to join with next items'); 141 | } 142 | if (!zipper) { 143 | zipper = (i1, i2) => [i1, i2]; 144 | } 145 | else { 146 | Linqer._ensureFunction(zipper); 147 | } 148 | const self = this; 149 | Linqer._ensureInternalTryGetAt(this); 150 | // generator uses a buffer to hold all the items within the offset interval 151 | const gen = function* () { 152 | const buffer = Array(offset); 153 | let index = 0; 154 | for (const item of self) { 155 | const index2 = index - offset; 156 | const item2 = index2 < 0 157 | ? undefined 158 | : buffer[index2 % offset]; 159 | yield zipper(item, item2); 160 | buffer[index % offset] = item; 161 | index++; 162 | } 163 | }; 164 | const result = new Linqer.Enumerable(gen); 165 | // count is the same as of the original enumerable 166 | result._count = () => { 167 | const count = self.count(); 168 | if (!result._wasIterated) 169 | result._wasIterated = self._wasIterated; 170 | return count; 171 | }; 172 | // seeking is possible only if the original was seekable 173 | if (self._canSeek) { 174 | result._canSeek = true; 175 | result._tryGetAt = (index) => { 176 | const val1 = self._tryGetAt(index); 177 | const val2 = self._tryGetAt(index - offset); 178 | if (val1) { 179 | return { 180 | value: zipper(val1.value, val2 ? val2.value : undefined) 181 | }; 182 | } 183 | return null; 184 | }; 185 | } 186 | return result; 187 | }; 188 | /// joins each item of the enumerable with next items from the same enumerable 189 | Linqer.Enumerable.prototype.lead = function (offset, zipper) { 190 | if (!offset) { 191 | throw new Error('offset has to be positive'); 192 | } 193 | if (offset < 0) { 194 | throw new Error('offset has to be positive. Use .lag if you want to join with previous items'); 195 | } 196 | if (!zipper) { 197 | zipper = (i1, i2) => [i1, i2]; 198 | } 199 | else { 200 | Linqer._ensureFunction(zipper); 201 | } 202 | const self = this; 203 | Linqer._ensureInternalTryGetAt(this); 204 | // generator uses a buffer to hold all the items within the offset interval 205 | const gen = function* () { 206 | const buffer = Array(offset); 207 | let index = 0; 208 | for (const item of self) { 209 | const index2 = index - offset; 210 | if (index2 >= 0) { 211 | const item2 = buffer[index2 % offset]; 212 | yield zipper(item2, item); 213 | } 214 | buffer[index % offset] = item; 215 | index++; 216 | } 217 | for (let i = 0; i < offset; i++) { 218 | const item = buffer[(index + i) % offset]; 219 | yield zipper(item, undefined); 220 | } 221 | }; 222 | const result = new Linqer.Enumerable(gen); 223 | // count is the same as of the original enumerable 224 | result._count = () => { 225 | const count = self.count(); 226 | if (!result._wasIterated) 227 | result._wasIterated = self._wasIterated; 228 | return count; 229 | }; 230 | // seeking is possible only if the original was seekable 231 | if (self._canSeek) { 232 | result._canSeek = true; 233 | result._tryGetAt = (index) => { 234 | const val1 = self._tryGetAt(index); 235 | const val2 = self._tryGetAt(index + offset); 236 | if (val1) { 237 | return { 238 | value: zipper(val1.value, val2 ? val2.value : undefined) 239 | }; 240 | } 241 | return null; 242 | }; 243 | } 244 | return result; 245 | }; 246 | /// returns an enumerable of at least minLength, padding the end with a value or the result of a function 247 | Linqer.Enumerable.prototype.padEnd = function (minLength, filler) { 248 | if (minLength <= 0) { 249 | throw new Error('minLength has to be positive.'); 250 | } 251 | let fillerFunc; 252 | if (typeof filler !== 'function') { 253 | fillerFunc = (index) => filler; 254 | } 255 | else { 256 | fillerFunc = filler; 257 | } 258 | const self = this; 259 | Linqer._ensureInternalTryGetAt(this); 260 | // generator iterates all elements, 261 | // then yields the result of the filler function until minLength items 262 | const gen = function* () { 263 | let index = 0; 264 | for (const item of self) { 265 | yield item; 266 | index++; 267 | } 268 | for (; index < minLength; index++) { 269 | yield fillerFunc(index); 270 | } 271 | }; 272 | const result = new Linqer.Enumerable(gen); 273 | // count is the maximum between minLength and the original count 274 | result._count = () => { 275 | const count = Math.max(minLength, self.count()); 276 | if (!result._wasIterated) 277 | result._wasIterated = self._wasIterated; 278 | return count; 279 | }; 280 | // seeking is possible if the original was seekable 281 | if (self._canSeek) { 282 | result._canSeek = true; 283 | result._tryGetAt = (index) => { 284 | const val = self._tryGetAt(index); 285 | if (val) 286 | return val; 287 | if (index < minLength) { 288 | return { value: fillerFunc(index) }; 289 | } 290 | return null; 291 | }; 292 | } 293 | return result; 294 | }; 295 | /// returns an enumerable of at least minLength, padding the start with a value or the result of a function 296 | /// if the enumerable cannot seek, then it will be iterated minLength time 297 | Linqer.Enumerable.prototype.padStart = function (minLength, filler) { 298 | if (minLength <= 0) { 299 | throw new Error('minLength has to be positive.'); 300 | } 301 | let fillerFunc; 302 | if (typeof filler !== 'function') { 303 | fillerFunc = (index) => filler; 304 | } 305 | else { 306 | fillerFunc = filler; 307 | } 308 | const self = this; 309 | Linqer._ensureInternalTryGetAt(self); 310 | // generator needs a buffer to hold offset values 311 | // it yields values from the buffer when it overflows 312 | // or filler function results if the buffer is not full 313 | // after iterating the entire original enumerable 314 | const gen = function* () { 315 | const buffer = Array(minLength); 316 | let index = 0; 317 | const iterator = self[Symbol.iterator](); 318 | let flushed = false; 319 | let done = false; 320 | do { 321 | const val = iterator.next(); 322 | done = !!val.done; 323 | if (!done) { 324 | buffer[index] = val.value; 325 | index++; 326 | } 327 | if (flushed && !done) { 328 | yield val.value; 329 | } 330 | else { 331 | if (done || index === minLength) { 332 | for (let i = 0; i < minLength - index; i++) { 333 | yield fillerFunc(i); 334 | } 335 | for (let i = 0; i < index; i++) { 336 | yield buffer[i]; 337 | } 338 | flushed = true; 339 | } 340 | } 341 | } while (!done); 342 | }; 343 | const result = new Linqer.Enumerable(gen); 344 | // count is the max of minLength and the original count 345 | result._count = () => { 346 | const count = Math.max(minLength, self.count()); 347 | if (!result._wasIterated) 348 | result._wasIterated = self._wasIterated; 349 | return count; 350 | }; 351 | // seeking is possible only if the original was seekable 352 | if (self._canSeek) { 353 | result._canSeek = true; 354 | result._tryGetAt = (index) => { 355 | const count = self.count(); 356 | const delta = minLength - count; 357 | if (delta <= 0) { 358 | return self._tryGetAt(index); 359 | } 360 | if (index < delta) { 361 | return { value: fillerFunc(index) }; 362 | } 363 | return self._tryGetAt(index - delta); 364 | }; 365 | } 366 | return result; 367 | }; 368 | })(Linqer || (Linqer = {})); 369 | //# sourceMappingURL=LInQer.extra.js.map -------------------------------------------------------------------------------- /LInQer.min.js: -------------------------------------------------------------------------------- 1 | "use strict";var Linqer;!function(t){class e{constructor(t){n(t),this._src=t;const e=t[Symbol.iterator];this._generator=e?e.bind(t):t,this._useQuickSort=void 0===t._useQuickSort||t._useQuickSort,this._canSeek=!1,this._count=null,this._tryGetAt=null,this._wasIterated=!1}static from(t){return t instanceof e?t:new e(t)}[Symbol.iterator](){return this._wasIterated=!0,this._generator()}static empty(){const t=new e([]);return t._count=()=>0,t._tryGetAt=t=>null,t._canSeek=!0,t}static range(t,n){const r=new e((function*(){for(let e=0;en,r._tryGetAt=e=>e>=0&&en,r._tryGetAt=e=>e>=0&&er.count()+u.count(),s(this),s(u),o._canSeek=r._canSeek&&u._canSeek,r._canSeek&&(o._tryGetAt=t=>r._tryGetAt(t)||u._tryGetAt(t-r.count())),o}count(){return u(this),this._count()}distinct(n=t.EqualityComparer.default){const r=this,o=n===t.EqualityComparer.default?function*(){const t=new Set;for(const e of r){const n=t.size;t.add(e),n0)&&(n.max=t),n.count++;return n}min(t){const e=this.stats(t);return 0===e.count?void 0:e.min}max(t){const e=this.stats(t);return 0===e.count?void 0:e.max}select(t){r(t);const n=this,o=new e((function*(){let e=0;for(const r of n)yield t(r,e),e++}));return u(this),o._count=this._count,s(n),o._canSeek=n._canSeek,o._tryGetAt=e=>{const r=n._tryGetAt(e);return r?{value:t(r.value)}:r},o}skip(t){const n=this,r=new e((function*(){let e=t;for(const t of n)e>0?e--:yield t}));return r._count=()=>Math.max(0,n.count()-t),s(this),r._canSeek=this._canSeek,r._tryGetAt=e=>n._tryGetAt(e+t),r}splice(t,e,...n){return this.take(t).concat(n).concat(this.skip(t+e))}sum(){const t=this.sumAndCount();return 0===t.count?void 0:t.sum}sumAndCount(){const t={count:0,sum:0};for(const e of this)t.sum=0===t.count?o(e):t.sum+o(e),t.count++;return t}take(t){const n=this,r=new e((function*(){let e=t;for(const t of n)if(e>0&&(yield t,e--),e<=0)break}));return r._count=()=>Math.min(t,n.count()),s(this),r._canSeek=n._canSeek,n._canSeek&&(r._tryGetAt=e=>e>=t?null:n._tryGetAt(e)),r}toArray(){var t;if(s(this),this._canSeek){const e=new Array(this.count());for(let n=0;ne._count())}const n=t._src;"function"==typeof n||"number"!=typeof n.length?"number"!=typeof n.size?t._count=()=>{let e=0;for(const n of t)e++;return e}:t._count=()=>n.size:t._count=()=>n.length}function s(t){if(t._tryGetAt)return;if(t._canSeek=!0,t._src instanceof e){const e=t._src;return s(e),t._tryGetAt=t=>e._tryGetAt(t),void(t._canSeek=e._canSeek)}if("string"==typeof t._src)return void(t._tryGetAt=e=>ee>=0&&e{let n=0;for(const r of t){if(e===n)return{value:r};n++}return null}):t._tryGetAt=t=>tt>e?1:tt==e,exact:(t,e)=>t===e}}(Linqer||(Linqer={})),function(t){t.Enumerable.prototype.aggregate=function(e,n){t._ensureFunction(n);for(const t of this)e=n(e,t);return e},t.Enumerable.prototype.all=function(e){return t._ensureFunction(e),!this.any(t=>!e(t))},t.Enumerable.prototype.any=function(e){t._ensureFunction(e);let n=0;for(const t of this){if(e(t,n))return!0;n++}return!1},t.Enumerable.prototype.append=function(t){return this.concat([t])},t.Enumerable.prototype.average=function(){const t=this.sumAndCount();return 0===t.count?void 0:t.sum/t.count},t.Enumerable.prototype.asEnumerable=function(){return this},t.Enumerable.prototype.cast=function(t){const e="string"==typeof t?e=>typeof e===t:e=>e instanceof t;return this.select(n=>{if(!e(n))throw new Error(n+" not of type "+t);return n})},t.Enumerable.prototype.contains=function(e,n=t.EqualityComparer.default){return t._ensureFunction(n),this.any(t=>n(t,e))},t.Enumerable.prototype.defaultIfEmpty=function(){throw new Error("defaultIfEmpty not implemented for Javascript")},t.Enumerable.prototype.except=function(e,n=t.EqualityComparer.default){t._ensureIterable(e);const r=this,o=n===t.EqualityComparer.default?function*(){const n=t.Enumerable.from(e).toSet();for(const t of r)n.has(t)||(yield t)}:function*(){const o=t._toArray(e);for(const t of r){let e=!0;for(let r=0;rtypeof e===t:e=>e instanceof t;return this.where(e)},t.Enumerable.prototype.prepend=function(e){return new t.Enumerable([e]).concat(this)},t.Enumerable.prototype.reverse=function(){t._ensureInternalTryGetAt(this);const e=this,n=this._canSeek?function*(){for(let t=e.count()-1;t>=0;t--)yield e.elementAt(t)}:function*(){const t=e.toArray();for(let e=t.length-1;e>=0;e--)yield t[e]},r=new t.Enumerable(n);if(t._ensureInternalCount(this),r._count=this._count,t._ensureInternalTryGetAt(this),this._canSeek){const t=this;r._canSeek=!0,r._tryGetAt=e=>t._tryGetAt(t.count()-e-1)}return r},t.Enumerable.prototype.selectMany=function(e){void 0!==e?t._ensureFunction(e):e=t=>t;const n=this;return new t.Enumerable((function*(){let r=0;for(const o of n){const n=e(o,r);t._ensureIterable(n);for(const t of n)yield t;r++}}))},t.Enumerable.prototype.sequenceEqual=function(e,n=t.EqualityComparer.default){t._ensureIterable(e),t._ensureFunction(n);const r=this[Symbol.iterator](),o=t.Enumerable.from(e)[Symbol.iterator]();let u=!1;do{const t=r.next(),e=o.next();if(!(t.done&&e.done||!t.done&&!e.done&&n(t.value,e.value)))return!1;u=!!t.done}while(!u);return!0},t.Enumerable.prototype.single=function(){const t=this[Symbol.iterator]();let e=t.next();if(e.done)throw new Error("Sequence contains no elements");const n=e.value;if(e=t.next(),!e.done)throw new Error("Sequence contains more than one element");return n},t.Enumerable.prototype.singleOrDefault=function(){const t=this[Symbol.iterator]();let e=t.next();if(e.done)return;const n=e.value;if(e=t.next(),!e.done)throw new Error("Sequence contains more than one element");return n},t.Enumerable.prototype.slice=function(t=0,e){let n=this;return void 0!==e&&e>=0&&(t||0)<0&&(n=n.toList(),t=n.count()+t),0!==t&&(n=t>0?n.skip(t):n.takeLast(-t)),void 0!==e&&(n=e>=0?n.take(e-t):n.skipLast(-e)),n},t.Enumerable.prototype.skipLast=function(e){const n=this,r=new t.Enumerable((function*(){let t=e;const r=Array(t);let o=0,u=0;for(const e of n){const n=r[o-u];r[o-u]=e,o++,o-u>=t&&(u+=t),o>t&&(yield n)}r.length=0}));return r._count=()=>Math.max(0,n.count()-e),t._ensureInternalTryGetAt(this),r._canSeek=this._canSeek,this._canSeek&&(r._tryGetAt=t=>t>=r.count()?null:n._tryGetAt(t)),r},t.Enumerable.prototype.skipWhile=function(e){t._ensureFunction(e);const n=this;let r=!0;return new t.Enumerable((function*(){let t=0;for(const o of n)r&&!e(o,t)&&(r=!1),r||(yield o),t++}))},t.Enumerable.prototype.takeLast=function(e){t._ensureInternalTryGetAt(this);const n=this,r=this._canSeek?function*(){let t=e;const r=n.count();for(let e=r-t;eMath.min(e,n.count()),o._canSeek=n._canSeek,n._canSeek&&(o._tryGetAt=t=>t<0||t>=o.count()?null:n._tryGetAt(n.count()-e+t)),o},t.Enumerable.prototype.takeWhile=function(e){t._ensureFunction(e);const n=this;return new t.Enumerable((function*(){let t=0;for(const r of n){if(!e(r,t))break;yield r,t++}}))},t.Enumerable.prototype.toDictionary=function(){throw new Error("use toMap or toObject instead of toDictionary")},t.Enumerable.prototype.toMap=function(e,n=(t=>t)){t._ensureFunction(e),t._ensureFunction(n);const r=new Map;let o=0;for(const t of this)r.set(e(t,o),n(t,o)),o++;return r},t.Enumerable.prototype.toObject=function(e,n=(t=>t)){t._ensureFunction(e),t._ensureFunction(n);const r={};let o=0;for(const t of this)r[e(t,o)]=n(t),o++;return r},t.Enumerable.prototype.toHashSet=function(){throw new Error("use toSet instead of toHashSet")},t.Enumerable.prototype.toSet=function(){const t=new Set;for(const e of this)t.add(e);return t},t.Enumerable.prototype.union=function(e,n=t.EqualityComparer.default){return t._ensureIterable(e),this.concat(e).distinct(n)},t.Enumerable.prototype.zip=function(e,n){t._ensureIterable(e),n?t._ensureFunction(n):n=(t,e)=>[t,e];const r=this;return new t.Enumerable((function*(){let o=0;const u=r[Symbol.iterator](),s=t.Enumerable.from(e)[Symbol.iterator]();let i=!1;do{const t=u.next(),e=s.next();i=!(!t.done&&!e.done),i||(yield n(t.value,e.value,o)),o++}while(!i)}))}}(Linqer||(Linqer={})),function(t){t.Enumerable.prototype.groupBy=function(n){t._ensureFunction(n);const r=this;return new t.Enumerable((function*(){const t=new Map;let o=0;for(const e of r){const r=n(e,o),u=t.get(r);u?u.push(e):t.set(r,[e]),o++}for(const[n,r]of t){const t=new e(r,n);yield t}}))},t.Enumerable.prototype.groupJoin=function(e,n,r,o,u=t.EqualityComparer.default){const s=this,i=u===t.EqualityComparer.default?function*(){const u=new t.Enumerable(e).groupBy(r).toMap(t=>t.key,t=>t);let i=0;for(const e of s){const r=t._toArray(u.get(n(e,i)));yield o(e,r),i++}}:function*(){let i=0;for(const c of s){const s=[];let a=0;for(const o of t.Enumerable.from(e))u(n(c,i),r(o,a))&&s.push(o),a++;yield o(c,s),i++}};return new t.Enumerable(i)},t.Enumerable.prototype.join=function(e,n,r,o,u=t.EqualityComparer.default){const s=this,i=u===t.EqualityComparer.default?function*(){const u=new t.Enumerable(e).groupBy(r).toMap(t=>t.key,t=>t);let i=0;for(const t of s){const e=u.get(n(t,i));if(e)for(const n of e)yield o(t,n);i++}}:function*(){let i=0;for(const c of s){let s=0;for(const a of t.Enumerable.from(e))u(n(c,i),r(a,s))&&(yield o(c,a)),s++;i++}};return new t.Enumerable(i)},t.Enumerable.prototype.toLookup=function(){throw new Error("use groupBy instead of toLookup")};class e extends t.Enumerable{constructor(t,e){super(t),this.key=e}}t.GroupEnumerable=e}(Linqer||(Linqer={})),function(t){let e;t.Enumerable.prototype.orderBy=function(e){return e?t._ensureFunction(e):e=t=>t,new n(this,e,!0)},t.Enumerable.prototype.orderByDescending=function(e){return e?t._ensureFunction(e):e=t=>t,new n(this,e,!1)},t.Enumerable.prototype.useQuickSort=function(){return this._useQuickSort=!0,this},t.Enumerable.prototype.useBrowserSort=function(){return this._useQuickSort=!1,this},t.Enumerable.sort=function(e,n=t._defaultComparer){return s(e,0,e.length-1,n,0,Number.MAX_SAFE_INTEGER),e},function(t){t[t.skip=0]="skip",t[t.skipLast=1]="skipLast",t[t.take=2]="take",t[t.takeLast=3]="takeLast"}(e||(e={}));class n extends t.Enumerable{constructor(e,n,r=!0){super(e),this._keySelectors=[],this._restrictions=[],n&&this._keySelectors.push({keySelector:n,ascending:r});const o=this;this._generator=function*(){let{startIndex:t,endIndex:e,arr:n}=this.getSortedArray();if(n)for(let r=t;r{const e=t.Enumerable.from(o._src).count(),{startIndex:n,endIndex:r}=this.getStartAndEndIndexes(o._restrictions,e);return r-n},this._canSeek=!1,this._tryGetAt=()=>{throw new Error("Ordered enumerables cannot seek")}}getSortedArray(){const e=this;let n,r,o=null;const u=e._src;if(t._ensureInternalTryGetAt(u),u._canSeek?({startIndex:n,endIndex:r}=e.getStartAndEndIndexes(e._restrictions,u.count())):(o=Array.from(e._src),({startIndex:n,endIndex:r}=e.getStartAndEndIndexes(e._restrictions,o.length))),ns(t,0,t.length-1,e,n,r):(t,e)=>t.sort(e))(o,e.generateSortFunc(e._keySelectors)),{startIndex:n,endIndex:r,arr:o}}return{startIndex:n,endIndex:r,arr:null}}generateSortFunc(t){const e=t.map(t=>{const e=t.keySelector,n=(t,n)=>{const r=e(t),o=e(n);return r>o?1:r-n(t,e)});return 1==e.length?e[0]:(t,n)=>{for(let r=0;rt)){t._ensureFunction(e),t._ensureFunction(n);const r=new Map,o=this.toArray();for(let t=0;tt)){t._ensureFunction(e),t._ensureFunction(n);const r={},o=this.toArray();for(let t=0;t=e&&r(t[u],n)>0;)t[u+1]=t[u],u--;t[u+1]=n}}function o(t,e,n){const r=t[e];t[e]=t[n],t[n]=r}function u(t,e,n,r){const u=t[n+e>>1];for(;e<=n;){for(;r(t[e],u)<0;)e++;for(;r(t[n],u)>0;)n--;if(e=i?(t.right=f-1,f 2 | /// 3 | /// 4 | 5 | namespace Linqer { 6 | 7 | export interface Enumerable extends Iterable { 8 | /** 9 | * Returns a randomized sequence of items from an initial source 10 | * @returns shuffle 11 | */ 12 | shuffle(): Enumerable; 13 | /** 14 | * implements random reservoir sampling of k items, with the option to specify a maximum limit for the items 15 | * @param k 16 | * @param limit 17 | * @returns sample 18 | */ 19 | randomSample(k: number, limit: number): Enumerable; 20 | /** 21 | * Returns the count of the items in a sequence. Depending on the sequence type this iterates through it or not. 22 | * @returns count 23 | */ 24 | count(): number; 25 | /** 26 | * returns the distinct values based on a hashing function 27 | * 28 | * @param {ISelector} hashFunc 29 | * @returns {Enumerable} 30 | * @memberof Enumerable 31 | */ 32 | distinctByHash(hashFunc: ISelector): Enumerable; 33 | /** 34 | * returns the values that have different hashes from the items of the iterable provided 35 | * 36 | * @param {IterableType} iterable 37 | * @param {ISelector} hashFunc 38 | * @returns {Enumerable} 39 | * @memberof Enumerable 40 | */ 41 | exceptByHash(iterable: IterableType, hashFunc: ISelector): Enumerable; 42 | /** 43 | * returns the values that have the same hashes as items of the iterable provided 44 | * 45 | * @param {IterableType} iterable 46 | * @param {ISelector} hashFunc 47 | * @returns {Enumerable} 48 | * @memberof Enumerable 49 | */ 50 | intersectByHash(iterable: IterableType, hashFunc: ISelector): Enumerable; 51 | /** 52 | * returns the index of a value in an ordered enumerable or false if not found 53 | * WARNING: use the same comparer as the one used to order the enumerable. The algorithm assumes the enumerable is already sorted. 54 | * 55 | * @param {*} value 56 | * @param {IComparer} comparer 57 | * @returns {(number | boolean)} 58 | * @memberof Enumerable 59 | */ 60 | binarySearch(value: any, comparer: IComparer): number | boolean; 61 | /** 62 | * joins each item of the enumerable with previous items from the same enumerable 63 | * @param offset 64 | * @param zipper 65 | * @returns lag 66 | */ 67 | lag(offset: number, zipper: (item1: any, item2: any) => any): Enumerable; 68 | /** 69 | * joins each item of the enumerable with next items from the same enumerable 70 | * 71 | * @param {number} offset 72 | * @param {(item1: any, item2: any) => any} zipper 73 | * @returns {Enumerable} 74 | * @memberof Enumerable 75 | */ 76 | lead(offset: number, zipper: (item1: any, item2: any) => any): Enumerable; 77 | /** 78 | * returns an enumerable of at least minLength, padding the end with a value or the result of a function 79 | * 80 | * @param {number} minLength 81 | * @param {(any | ((index: number) => any))} filler 82 | * @returns {Enumerable} 83 | * @memberof Enumerable 84 | */ 85 | padEnd(minLength: number, filler: any | ((index: number) => any)): Enumerable; 86 | /** 87 | * returns an enumerable of at least minLength, padding the start with a value or the result of a function 88 | * if the enumerable cannot seek, then it will be iterated minLength time 89 | * 90 | * @param {number} minLength 91 | * @param {(any | ((index: number) => any))} filler 92 | * @returns {Enumerable} 93 | * @memberof Enumerable 94 | */ 95 | padStart(minLength: number, filler: any | ((index: number) => any)): Enumerable; 96 | } 97 | 98 | /// randomizes the enumerable (partial Fisher-Yates) 99 | Enumerable.prototype.shuffle = function (): Enumerable { 100 | const self = this; 101 | function* gen() { 102 | const arr = self.toArray(); 103 | const len = arr.length; 104 | let n = 0; 105 | while (n < len) { 106 | let k = n + Math.floor(Math.random() * (len - n)); 107 | const value = arr[k]; 108 | arr[k] = arr[n]; 109 | arr[n] = value; 110 | n++; 111 | yield value; 112 | } 113 | } 114 | const result = Enumerable.from(gen); 115 | result._count = () => self.count(); 116 | return result; 117 | }; 118 | 119 | /// implements random reservoir sampling of k items, with the option to specify a maximum limit for the items 120 | Enumerable.prototype.randomSample = function (k: number, limit: number = Number.MAX_SAFE_INTEGER): Enumerable { 121 | let index = 0; 122 | const sample = []; 123 | _ensureInternalTryGetAt(this); 124 | if (this._canSeek) { // L algorithm 125 | const length = this.count(); 126 | let index = 0; 127 | for (index = 0; index < k && index < limit && index < length; index++) { 128 | sample.push(this.elementAt(index)); 129 | } 130 | let W = Math.exp(Math.log(Math.random()) / k); 131 | while (index < length && index < limit) { 132 | index += Math.floor(Math.log(Math.random()) / Math.log(1 - W)) + 1; 133 | if (index < length && index < limit) { 134 | sample[Math.floor(Math.random() * k)] = this.elementAt(index); 135 | W *= Math.exp(Math.log(Math.random()) / k); 136 | } 137 | } 138 | } else { // R algorithm 139 | for (const item of this) { 140 | if (index < k) { 141 | sample.push(item); 142 | } else { 143 | const j = Math.floor(Math.random() * index); 144 | if (j < k) { 145 | sample[j] = item; 146 | } 147 | } 148 | index++; 149 | if (index >= limit) break; 150 | } 151 | } 152 | return Enumerable.from(sample); 153 | } 154 | 155 | /// returns the distinct values based on a hashing function 156 | Enumerable.prototype.distinctByHash = function (hashFunc: ISelector): Enumerable { 157 | // this is much more performant than distinct with a custom comparer 158 | const self = this; 159 | const gen = function* () { 160 | const distinctValues = new Set(); 161 | for (const item of self) { 162 | const size = distinctValues.size; 163 | distinctValues.add(hashFunc(item)); 164 | if (size < distinctValues.size) { 165 | yield item; 166 | } 167 | } 168 | }; 169 | return new Enumerable(gen); 170 | }; 171 | 172 | /// returns the values that have different hashes from the items of the iterable provided 173 | Enumerable.prototype.exceptByHash = function (iterable: IterableType, hashFunc: ISelector): Enumerable { 174 | // this is much more performant than except with a custom comparer 175 | _ensureIterable(iterable); 176 | const self = this; 177 | const gen = function* () { 178 | const distinctValues = Enumerable.from(iterable).select(hashFunc).toSet(); 179 | for (const item of self) { 180 | if (!distinctValues.has(hashFunc(item))) { 181 | yield item; 182 | } 183 | } 184 | }; 185 | return new Enumerable(gen); 186 | }; 187 | 188 | /// returns the values that have the same hashes as items of the iterable provided 189 | Enumerable.prototype.intersectByHash = function (iterable: IterableType, hashFunc: ISelector): Enumerable { 190 | // this is much more performant than intersect with a custom comparer 191 | _ensureIterable(iterable); 192 | const self = this; 193 | const gen = function* () { 194 | const distinctValues = Enumerable.from(iterable).select(hashFunc).toSet(); 195 | for (const item of self) { 196 | if (distinctValues.has(hashFunc(item))) { 197 | yield item; 198 | } 199 | } 200 | }; 201 | return new Enumerable(gen); 202 | }; 203 | 204 | /// returns the index of a value in an ordered enumerable or false if not found 205 | /// WARNING: use the same comparer as the one used in the ordered enumerable. The algorithm assumes the enumerable is already sorted. 206 | Enumerable.prototype.binarySearch = function (value: any, comparer: IComparer = _defaultComparer): number | boolean { 207 | let enumerable: Enumerable = this.toList(); 208 | let start = 0; 209 | let end = enumerable.count() - 1; 210 | 211 | while (start <= end) { 212 | const mid = (start + end) >> 1; 213 | const comp = comparer(enumerable.elementAt(mid), value); 214 | if (comp == 0) return mid; 215 | if (comp < 0) { 216 | start = mid + 1; 217 | } else { 218 | end = mid - 1; 219 | } 220 | } 221 | 222 | return false; 223 | }; 224 | 225 | /// joins each item of the enumerable with previous items from the same enumerable 226 | Enumerable.prototype.lag = function (offset: number, zipper: (item1: any, item2: any) => any): Enumerable { 227 | if (!offset) { 228 | throw new Error('offset has to be positive'); 229 | } 230 | if (offset < 0) { 231 | throw new Error('offset has to be positive. Use .lead if you want to join with next items'); 232 | } 233 | if (!zipper) { 234 | zipper = (i1, i2) => [i1, i2]; 235 | } else { 236 | _ensureFunction(zipper); 237 | } 238 | const self = this; 239 | _ensureInternalTryGetAt(this); 240 | // generator uses a buffer to hold all the items within the offset interval 241 | const gen = function* () { 242 | const buffer = Array(offset); 243 | let index = 0; 244 | for (const item of self) { 245 | const index2 = index - offset; 246 | const item2 = index2 < 0 247 | ? undefined 248 | : buffer[index2 % offset]; 249 | yield zipper(item, item2); 250 | buffer[index % offset] = item; 251 | index++; 252 | } 253 | }; 254 | const result = new Enumerable(gen); 255 | // count is the same as of the original enumerable 256 | result._count = () => { 257 | const count = self.count(); 258 | if (!result._wasIterated) result._wasIterated = self._wasIterated; 259 | return count; 260 | }; 261 | // seeking is possible only if the original was seekable 262 | if (self._canSeek) { 263 | result._canSeek = true; 264 | result._tryGetAt = (index: number) => { 265 | const val1 = self._tryGetAt!(index); 266 | const val2 = self._tryGetAt!(index - offset); 267 | if (val1) { 268 | return { 269 | value: zipper( 270 | val1.value, 271 | val2 ? val2.value : undefined 272 | ) 273 | }; 274 | } 275 | return null; 276 | }; 277 | } 278 | return result; 279 | } 280 | 281 | 282 | /// joins each item of the enumerable with next items from the same enumerable 283 | Enumerable.prototype.lead = function (offset: number, zipper: (item1: any, item2: any) => any): Enumerable { 284 | if (!offset) { 285 | throw new Error('offset has to be positive'); 286 | } 287 | if (offset < 0) { 288 | throw new Error('offset has to be positive. Use .lag if you want to join with previous items'); 289 | } 290 | if (!zipper) { 291 | zipper = (i1, i2) => [i1, i2]; 292 | } else { 293 | _ensureFunction(zipper); 294 | } 295 | const self = this; 296 | _ensureInternalTryGetAt(this); 297 | // generator uses a buffer to hold all the items within the offset interval 298 | const gen = function* () { 299 | const buffer = Array(offset); 300 | let index = 0; 301 | for (const item of self) { 302 | const index2 = index - offset; 303 | if (index2 >= 0) { 304 | const item2 = buffer[index2 % offset]; 305 | yield zipper(item2, item); 306 | } 307 | buffer[index % offset] = item; 308 | index++; 309 | } 310 | for (let i = 0; i < offset; i++) { 311 | const item = buffer[(index + i) % offset]; 312 | yield zipper(item, undefined); 313 | } 314 | }; 315 | const result = new Enumerable(gen); 316 | // count is the same as of the original enumerable 317 | result._count = () => { 318 | const count = self.count(); 319 | if (!result._wasIterated) result._wasIterated = self._wasIterated; 320 | return count; 321 | }; 322 | // seeking is possible only if the original was seekable 323 | if (self._canSeek) { 324 | result._canSeek = true; 325 | result._tryGetAt = (index: number) => { 326 | const val1 = self._tryGetAt!(index); 327 | const val2 = self._tryGetAt!(index + offset); 328 | if (val1) { 329 | return { 330 | value: zipper( 331 | val1.value, 332 | val2 ? val2.value : undefined 333 | ) 334 | }; 335 | } 336 | return null; 337 | }; 338 | } 339 | return result; 340 | } 341 | 342 | /// returns an enumerable of at least minLength, padding the end with a value or the result of a function 343 | Enumerable.prototype.padEnd = function (minLength: number, filler: any | ((index: number) => any)): Enumerable { 344 | if (minLength <= 0) { 345 | throw new Error('minLength has to be positive.'); 346 | } 347 | let fillerFunc: (index: number) => any; 348 | if (typeof filler !== 'function') { 349 | fillerFunc = (index: number) => filler; 350 | } else { 351 | fillerFunc = filler; 352 | } 353 | const self = this; 354 | _ensureInternalTryGetAt(this); 355 | // generator iterates all elements, 356 | // then yields the result of the filler function until minLength items 357 | const gen = function* () { 358 | let index = 0; 359 | for (const item of self) { 360 | yield item; 361 | index++; 362 | } 363 | for (; index < minLength; index++) { 364 | yield fillerFunc(index); 365 | } 366 | }; 367 | const result = new Enumerable(gen); 368 | // count is the maximum between minLength and the original count 369 | result._count = () => { 370 | const count = Math.max(minLength, self.count()); 371 | if (!result._wasIterated) result._wasIterated = self._wasIterated; 372 | return count; 373 | }; 374 | // seeking is possible if the original was seekable 375 | if (self._canSeek) { 376 | result._canSeek = true; 377 | result._tryGetAt = (index: number) => { 378 | const val = self._tryGetAt!(index); 379 | if (val) return val; 380 | if (index < minLength) { 381 | return { value: fillerFunc(index) }; 382 | } 383 | return null; 384 | }; 385 | } 386 | return result; 387 | } 388 | 389 | 390 | /// returns an enumerable of at least minLength, padding the start with a value or the result of a function 391 | /// if the enumerable cannot seek, then it will be iterated minLength time 392 | Enumerable.prototype.padStart = function (minLength: number, filler: any | ((index: number) => any)): Enumerable { 393 | if (minLength <= 0) { 394 | throw new Error('minLength has to be positive.'); 395 | } 396 | let fillerFunc: (index: number) => any; 397 | if (typeof filler !== 'function') { 398 | fillerFunc = (index: number) => filler; 399 | } else { 400 | fillerFunc = filler; 401 | } 402 | const self = this; 403 | _ensureInternalTryGetAt(self); 404 | // generator needs a buffer to hold offset values 405 | // it yields values from the buffer when it overflows 406 | // or filler function results if the buffer is not full 407 | // after iterating the entire original enumerable 408 | const gen = function* () { 409 | const buffer = Array(minLength); 410 | let index = 0; 411 | const iterator = self[Symbol.iterator](); 412 | let flushed = false; 413 | let done = false; 414 | do { 415 | const val = iterator.next(); 416 | done = !!val.done; 417 | if (!done) { 418 | buffer[index] = val.value; 419 | index++; 420 | } 421 | if (flushed && !done) { 422 | yield val.value; 423 | } else { 424 | if (done || index === minLength) { 425 | for (let i = 0; i < minLength - index; i++) { 426 | yield fillerFunc(i); 427 | } 428 | for (let i = 0; i < index; i++) { 429 | yield buffer[i]; 430 | } 431 | flushed = true; 432 | } 433 | } 434 | } while (!done); 435 | }; 436 | const result = new Enumerable(gen); 437 | // count is the max of minLength and the original count 438 | result._count = () => { 439 | const count = Math.max(minLength, self.count()); 440 | if (!result._wasIterated) result._wasIterated = self._wasIterated; 441 | return count; 442 | }; 443 | // seeking is possible only if the original was seekable 444 | if (self._canSeek) { 445 | result._canSeek = true; 446 | result._tryGetAt = (index: number) => { 447 | const count = self.count(); 448 | const delta = minLength-count; 449 | if (delta<=0) { 450 | return self._tryGetAt!(index); 451 | } 452 | if (index} 9 | * @implements {IUsesQuickSort} 10 | */ 11 | export class Enumerable implements Iterable, IUsesQuickSort { 12 | _src: IterableType; 13 | _generator: () => Iterator; 14 | _useQuickSort: boolean; 15 | // indicates that count and elementAt functions will not cause iterating the enumerable 16 | _canSeek: boolean; 17 | _count: null | (() => number); 18 | _tryGetAt: null | ((index: number) => { value: any } | null); 19 | // true if the enumerable was iterated at least once 20 | _wasIterated: boolean; 21 | 22 | /** 23 | * sort an array in place using the Enumerable sort algorithm (Quicksort) 24 | * 25 | * @static 26 | * @memberof Enumerable 27 | */ 28 | static sort: (arr: any[], comparer?: IComparer) => any[]; 29 | 30 | /** 31 | * You should never use this. Instead use Enumerable.from 32 | * @param {IterableType} src 33 | * @memberof Enumerable 34 | */ 35 | constructor(src: IterableType) { 36 | _ensureIterable(src); 37 | this._src = src; 38 | const iteratorFunction: (() => Iterator) = (src as Iterable)[Symbol.iterator]; 39 | // the generator is either the iterator of the source enumerable 40 | // or the generator function that was provided as the source itself 41 | if (iteratorFunction) { 42 | this._generator = iteratorFunction.bind(src); 43 | } else { 44 | this._generator = src as (() => Iterator); 45 | } 46 | // set sorting method on an enumerable and all the derived ones should inherit it 47 | // TODO: a better method of doing this 48 | this._useQuickSort = (src as IUsesQuickSort)._useQuickSort !== undefined 49 | ? (src as IUsesQuickSort)._useQuickSort 50 | : true; 51 | this._canSeek = false; 52 | this._count = null; 53 | this._tryGetAt = null; 54 | this._wasIterated = false; 55 | } 56 | 57 | /** 58 | * Wraps an iterable item into an Enumerable if it's not already one 59 | * 60 | * @static 61 | * @param {IterableType} iterable 62 | * @returns {Enumerable} 63 | * @memberof Enumerable 64 | */ 65 | static from(iterable: IterableType): Enumerable { 66 | if (iterable instanceof Enumerable) return iterable; 67 | return new Enumerable(iterable); 68 | } 69 | 70 | /** 71 | * the Enumerable instance exposes the same iterator as the wrapped iterable or generator function 72 | * 73 | * @returns {Iterator} 74 | * @memberof Enumerable 75 | */ 76 | [Symbol.iterator](): Iterator { 77 | this._wasIterated = true; 78 | return this._generator(); 79 | } 80 | 81 | /** 82 | * returns an empty Enumerable 83 | * 84 | * @static 85 | * @returns {Enumerable} 86 | * @memberof Enumerable 87 | */ 88 | static empty(): Enumerable { 89 | const result = new Enumerable([]); 90 | result._count = () => 0; 91 | result._tryGetAt = (index: number) => null; 92 | result._canSeek = true; 93 | return result; 94 | } 95 | 96 | /** 97 | * generates a sequence of integer numbers within a specified range. 98 | * 99 | * @static 100 | * @param {number} start 101 | * @param {number} count 102 | * @returns {Enumerable} 103 | * @memberof Enumerable 104 | */ 105 | static range(start: number, count: number): Enumerable { 106 | const gen = function* () { 107 | for (let i = 0; i < count; i++) { 108 | yield start + i; 109 | } 110 | }; 111 | const result = new Enumerable(gen); 112 | result._count = () => count; 113 | result._tryGetAt = index => { 114 | if (index >= 0 && index < count) return { value: start + index }; 115 | return null; 116 | }; 117 | result._canSeek = true; 118 | return result; 119 | } 120 | 121 | /** 122 | * Generates a sequence that contains one repeated value. 123 | * 124 | * @static 125 | * @param {*} item 126 | * @param {number} count 127 | * @returns {Enumerable} 128 | * @memberof Enumerable 129 | */ 130 | static repeat(item: any, count: number): Enumerable { 131 | const gen = function* () { 132 | for (let i = 0; i < count; i++) { 133 | yield item; 134 | } 135 | }; 136 | const result = new Enumerable(gen); 137 | result._count = () => count; 138 | result._tryGetAt = index => { 139 | if (index >= 0 && index < count) return { value: item }; 140 | return null; 141 | }; 142 | result._canSeek = true; 143 | return result; 144 | } 145 | 146 | /** 147 | * Same value as count(), but will throw an Error if enumerable is not seekable and has to be iterated to get the length 148 | */ 149 | get length():number { 150 | _ensureInternalTryGetAt(this); 151 | if (!this._canSeek) throw new Error('Calling length on this enumerable will iterate it. Use count()'); 152 | return this.count(); 153 | } 154 | 155 | /** 156 | * Concatenates two sequences by appending iterable to the existing one. 157 | * 158 | * @param {IterableType} iterable 159 | * @returns {Enumerable} 160 | * @memberof Enumerable 161 | */ 162 | concat(iterable: IterableType): Enumerable { 163 | _ensureIterable(iterable); 164 | const self: Enumerable = this; 165 | // the generator will iterate the enumerable first, then the iterable that was given as a parameter 166 | // this will be able to seek if both the original and the iterable derived enumerable can seek 167 | // the indexing function will get items from the first and then second enumerable without iteration 168 | const gen = function* () { 169 | for (const item of self) { 170 | yield item; 171 | } 172 | for (const item of Enumerable.from(iterable)) { 173 | yield item; 174 | } 175 | }; 176 | const result = new Enumerable(gen); 177 | const other = Enumerable.from(iterable); 178 | result._count = () => self.count() + other.count(); 179 | _ensureInternalTryGetAt(this); 180 | _ensureInternalTryGetAt(other); 181 | result._canSeek = self._canSeek && other._canSeek; 182 | if (self._canSeek) { 183 | result._tryGetAt = index => { 184 | return self._tryGetAt!(index) || other._tryGetAt!(index - self.count()); 185 | }; 186 | } 187 | return result; 188 | } 189 | 190 | 191 | /** 192 | * Returns the number of elements in a sequence. 193 | * 194 | * @returns {number} 195 | * @memberof Enumerable 196 | */ 197 | count(): number { 198 | _ensureInternalCount(this); 199 | return this._count!(); 200 | } 201 | 202 | 203 | /** 204 | * Returns distinct elements from a sequence. 205 | * WARNING: using a comparer makes this slower. Not specifying it uses a Set to determine distinctiveness. 206 | * 207 | * @param {IEqualityComparer} [equalityComparer=EqualityComparer.default] 208 | * @returns {Enumerable} 209 | * @memberof Enumerable 210 | */ 211 | distinct(equalityComparer: IEqualityComparer = EqualityComparer.default): Enumerable { 212 | const self: Enumerable = this; 213 | // if the comparer function is not provided, a Set will be used to quickly determine distinctiveness 214 | const gen = equalityComparer === EqualityComparer.default 215 | ? function* () { 216 | const distinctValues = new Set(); 217 | for (const item of self) { 218 | const size = distinctValues.size; 219 | distinctValues.add(item); 220 | if (size < distinctValues.size) { 221 | yield item; 222 | } 223 | } 224 | } 225 | // otherwise values will be compared with previous values ( O(n^2) ) 226 | // use distinctByHash in Linqer.extra to use a hashing function ( O(n log n) ) 227 | : function* () { 228 | const values = []; 229 | for (const item of self) { 230 | let unique = true; 231 | for (let i=0; i 0) agg.max = item; 363 | agg.count++; 364 | } 365 | return agg; 366 | } 367 | 368 | /** 369 | * Returns the minimum value in a sequence of values. 370 | * A custom function can be used to establish order (the result 0 means equal, 1 means larger, -1 means smaller) 371 | * 372 | * @param {IComparer} [comparer] 373 | * @returns {*} 374 | * @memberof Enumerable 375 | */ 376 | min(comparer?: IComparer): any { 377 | const stats = this.stats(comparer); 378 | return stats.count === 0 379 | ? undefined 380 | : stats.min; 381 | } 382 | 383 | 384 | /** 385 | * Returns the maximum value in a sequence of values. 386 | * A custom function can be used to establish order (the result 0 means equal, 1 means larger, -1 means smaller) 387 | * 388 | * @param {IComparer} [comparer] 389 | * @returns {*} 390 | * @memberof Enumerable 391 | */ 392 | max(comparer?: IComparer): any { 393 | const stats = this.stats(comparer); 394 | return stats.count === 0 395 | ? undefined 396 | : stats.max; 397 | } 398 | 399 | 400 | /** 401 | * Projects each element of a sequence into a new form. 402 | * 403 | * @param {ISelector} selector 404 | * @returns {Enumerable} 405 | * @memberof Enumerable 406 | */ 407 | select(selector: ISelector): Enumerable { 408 | _ensureFunction(selector); 409 | const self: Enumerable = this; 410 | // the generator is applying the selector on all the items of the enumerable 411 | // the count of the resulting enumerable is the same as the original's 412 | // the indexer is the same as that of the original, with the selector applied on the value 413 | const gen = function* () { 414 | let index = 0; 415 | for (const item of self) { 416 | yield selector(item, index); 417 | index++; 418 | } 419 | }; 420 | const result = new Enumerable(gen); 421 | _ensureInternalCount(this); 422 | result._count = this._count; 423 | _ensureInternalTryGetAt(self); 424 | result._canSeek = self._canSeek; 425 | result._tryGetAt = index => { 426 | const res = self._tryGetAt!(index); 427 | if (!res) return res; 428 | return { value: selector(res.value) }; 429 | }; 430 | return result; 431 | } 432 | 433 | 434 | /** 435 | * Bypasses a specified number of elements in a sequence and then returns the remaining elements. 436 | * 437 | * @param {number} nr 438 | * @returns {Enumerable} 439 | * @memberof Enumerable 440 | */ 441 | skip(nr: number): Enumerable { 442 | const self: Enumerable = this; 443 | // the generator just enumerates the first nr numbers then starts yielding values 444 | // the count is the same as the original enumerable, minus the skipped items and at least 0 445 | // the indexer is the same as for the original, with an offset 446 | const gen = function* () { 447 | let nrLeft = nr; 448 | for (const item of self) { 449 | if (nrLeft > 0) { 450 | nrLeft--; 451 | } else { 452 | yield item; 453 | } 454 | } 455 | }; 456 | const result = new Enumerable(gen); 457 | 458 | result._count = () => Math.max(0, self.count() - nr); 459 | _ensureInternalTryGetAt(this); 460 | result._canSeek = this._canSeek; 461 | result._tryGetAt = index => self._tryGetAt!(index + nr); 462 | return result; 463 | } 464 | 465 | 466 | /** 467 | * Takes start elements, ignores howmany elements, continues with the new items and continues with the original enumerable 468 | * Equivalent to the value of an array after performing splice on it with the same parameters 469 | * @param start 470 | * @param howmany 471 | * @param items 472 | * @returns splice 473 | */ 474 | splice(start: number, howmany: number, ...newItems:any[]) : Enumerable { 475 | // tried to define length and splice so that this is seen as an Array-like object, 476 | // but it doesn't work on properties. length needs to be a field. 477 | return this.take(start).concat(newItems).concat(this.skip(start+howmany)); 478 | } 479 | 480 | /** 481 | * Computes the sum of a sequence of numeric values. 482 | * 483 | * @returns {(number | undefined)} 484 | * @memberof Enumerable 485 | */ 486 | sum(): number | undefined { 487 | const stats = this.sumAndCount(); 488 | return stats.count === 0 489 | ? undefined 490 | : stats.sum; 491 | } 492 | 493 | 494 | /** 495 | * Computes the sum and count of a sequence of numeric values. 496 | * 497 | * @returns {{ sum: number, count: number }} 498 | * @memberof Enumerable 499 | */ 500 | sumAndCount(): { sum: number, count: number } { 501 | const agg = { 502 | count: 0, 503 | sum: 0 504 | }; 505 | for (const item of this) { 506 | agg.sum = agg.count === 0 507 | ? _toNumber(item) 508 | : agg.sum + _toNumber(item); 509 | agg.count++; 510 | } 511 | return agg; 512 | } 513 | 514 | 515 | /** 516 | * Returns a specified number of contiguous elements from the start of a sequence. 517 | * 518 | * @param {number} nr 519 | * @returns {Enumerable} 520 | * @memberof Enumerable 521 | */ 522 | take(nr: number): Enumerable { 523 | const self: Enumerable = this; 524 | // the generator will stop after nr items yielded 525 | // the count is the maximum between the total count and nr 526 | // the indexer is the same, as long as it's not higher than nr 527 | const gen = function* () { 528 | let nrLeft = nr; 529 | for (const item of self) { 530 | if (nrLeft > 0) { 531 | yield item; 532 | nrLeft--; 533 | } 534 | if (nrLeft <= 0) { 535 | break; 536 | } 537 | } 538 | }; 539 | const result = new Enumerable(gen); 540 | 541 | result._count = () => Math.min(nr, self.count()); 542 | _ensureInternalTryGetAt(this); 543 | result._canSeek = self._canSeek; 544 | if (self._canSeek) { 545 | result._tryGetAt = index => { 546 | if (index >= nr) return null; 547 | return self._tryGetAt!(index); 548 | }; 549 | } 550 | return result; 551 | } 552 | 553 | 554 | /** 555 | * creates an array from an Enumerable 556 | * 557 | * @returns {any[]} 558 | * @memberof Enumerable 559 | */ 560 | toArray(): any[] { 561 | _ensureInternalTryGetAt(this); 562 | // this should be faster than Array.from(this) 563 | if (this._canSeek) { 564 | const arr = new Array(this.count()); 565 | for (let i = 0; i < arr.length; i++) { 566 | arr[i] = this._tryGetAt!(i)?.value; 567 | } 568 | return arr; 569 | } 570 | // try to optimize the array growth by increasing it 571 | // by 64 every time it is needed 572 | const minIncrease = 64; 573 | let size = 0; 574 | const arr = []; 575 | for (const item of this) { 576 | if (size === arr.length) { 577 | arr.length += minIncrease; 578 | } 579 | arr[size] = item; 580 | size++; 581 | } 582 | arr.length = size; 583 | return arr; 584 | } 585 | 586 | 587 | /** 588 | * similar to toArray, but returns a seekable Enumerable (itself if already seekable) that can do count and elementAt without iterating 589 | * 590 | * @returns {Enumerable} 591 | * @memberof Enumerable 592 | */ 593 | toList(): Enumerable { 594 | _ensureInternalTryGetAt(this); 595 | if (this._canSeek) return this; 596 | return Enumerable.from(this.toArray()); 597 | } 598 | 599 | /** 600 | * Filters a sequence of values based on a predicate. 601 | * 602 | * @param {IFilter} condition 603 | * @returns {Enumerable} 604 | * @memberof Enumerable 605 | */ 606 | where(condition: IFilter): Enumerable { 607 | _ensureFunction(condition); 608 | const self: Enumerable = this; 609 | // cannot imply the count or indexer from the condition 610 | // where will have to iterate through the whole thing 611 | const gen = function* () { 612 | let index = 0; 613 | for (const item of self) { 614 | if (condition(item, index)) { 615 | yield item; 616 | } 617 | index++; 618 | } 619 | }; 620 | return new Enumerable(gen); 621 | } 622 | } 623 | 624 | // throw if src is not a generator function or an iteratable 625 | export function _ensureIterable(src: IterableType): void { 626 | if (src) { 627 | if ((src as Iterable)[Symbol.iterator]) return; 628 | if (typeof src === 'function' && (src as Function).constructor.name === 'GeneratorFunction') return; 629 | } 630 | throw new Error('the argument must be iterable!'); 631 | } 632 | // throw if f is not a function 633 | export function _ensureFunction(f: Function): void { 634 | if (!f || typeof f !== 'function') throw new Error('the argument needs to be a function!'); 635 | } 636 | // return Nan if this is not a number 637 | // different from Number(obj), which would cast strings to numbers 638 | function _toNumber(obj: any): number { 639 | return typeof obj === 'number' 640 | ? obj 641 | : Number.NaN; 642 | } 643 | // return the iterable if already an array or use Array.from to create one 644 | export function _toArray(iterable: IterableType) { 645 | if (!iterable) return []; 646 | if (Array.isArray(iterable)) return iterable; 647 | return Array.from(iterable); 648 | } 649 | // if the internal count function is not defined, set it to the most appropriate one 650 | export function _ensureInternalCount(enumerable: Enumerable) { 651 | if (enumerable._count) return; 652 | if (enumerable._src instanceof Enumerable) { 653 | // the count is the same as the underlying enumerable 654 | const innerEnumerable = enumerable._src as Enumerable; 655 | _ensureInternalCount(innerEnumerable); 656 | enumerable._count = () => innerEnumerable._count!(); 657 | return; 658 | } 659 | const src = enumerable._src as any; 660 | // this could cause false positives, but if it has a numeric length or size, use it 661 | if (typeof src !== 'function' && typeof src.length === 'number') { 662 | enumerable._count = () => src.length; 663 | return; 664 | } 665 | if (typeof src.size === 'number') { 666 | enumerable._count = () => src.size; 667 | return; 668 | } 669 | // otherwise iterate the whole thing and count all items 670 | enumerable._count = () => { 671 | let x = 0; 672 | for (const item of enumerable) x++; 673 | return x; 674 | }; 675 | } 676 | // ensure there is an internal indexer function adequate for this enumerable 677 | // this also determines if the enumerable can seek 678 | export function _ensureInternalTryGetAt(enumerable: Enumerable) { 679 | if (enumerable._tryGetAt) return; 680 | enumerable._canSeek = true; 681 | if (enumerable._src instanceof Enumerable) { 682 | // indexer and seekability is the same as for the underlying enumerable 683 | const innerEnumerable = enumerable._src as Enumerable; 684 | _ensureInternalTryGetAt(innerEnumerable); 685 | enumerable._tryGetAt = index => innerEnumerable._tryGetAt!(index); 686 | enumerable._canSeek = innerEnumerable._canSeek; 687 | return; 688 | } 689 | if (typeof enumerable._src === 'string') { 690 | // a string can be accessed by index 691 | enumerable._tryGetAt = index => { 692 | if (index < (enumerable._src as string).length) { 693 | return { value: (enumerable._src as string).charAt(index) }; 694 | } 695 | return null; 696 | }; 697 | return; 698 | } 699 | if (Array.isArray(enumerable._src)) { 700 | // an array can be accessed by index 701 | enumerable._tryGetAt = index => { 702 | if (index >= 0 && index < (enumerable._src as any[]).length) { 703 | return { value: (enumerable._src as any[])[index] }; 704 | } 705 | return null; 706 | }; 707 | return; 708 | } 709 | const src = enumerable._src as any; 710 | if (typeof enumerable._src !== 'function' && typeof src.length === 'number') { 711 | // try to access an object with a defined numeric length by indexing it 712 | // might cause false positives 713 | enumerable._tryGetAt = index => { 714 | if (index < src.length && typeof src[index] !== 'undefined') { 715 | return { value: src[index] }; 716 | } 717 | return null; 718 | }; 719 | return; 720 | } 721 | enumerable._canSeek = false; 722 | // TODO other specialized types? objects, maps, sets? 723 | enumerable._tryGetAt = index => { 724 | let x = 0; 725 | for (const item of enumerable) { 726 | if (index === x) return { value: item }; 727 | x++; 728 | } 729 | return null; 730 | } 731 | } 732 | 733 | /** 734 | * an extended iterable type that also supports generator functions 735 | */ 736 | export type IterableType = Iterable | (() => Iterator) | Enumerable; 737 | 738 | /** 739 | * A comparer function to be used in sorting 740 | */ 741 | export type IComparer = (item1: any, item2: any) => -1 | 0 | 1; 742 | /** 743 | * A selector function to be used in mapping 744 | */ 745 | export type ISelector = (item: any, index?: number) => T; 746 | /** 747 | * A filter function 748 | */ 749 | export type IFilter = ISelector; 750 | 751 | /** 752 | * The default comparer function between two items 753 | * @param item1 754 | * @param item2 755 | */ 756 | export const _defaultComparer: IComparer = (item1, item2) => { 757 | if (item1 > item2) return 1; 758 | if (item1 < item2) return -1; 759 | return 0; 760 | }; 761 | 762 | /** 763 | * Interface for an equality comparer 764 | */ 765 | export type IEqualityComparer = (item1: any, item2: any) => boolean; 766 | 767 | /** 768 | * Predefined equality comparers 769 | * default is the equivalent of == 770 | * exact is the equivalent of === 771 | */ 772 | export const EqualityComparer = { 773 | default: (item1: any, item2: any) => item1 == item2, 774 | exact: (item1: any, item2: any) => item1 === item2, 775 | }; 776 | 777 | // used to access the variable determining if 778 | // an enumerable should be ordered using Quicksort or not 779 | interface IUsesQuickSort { 780 | _useQuickSort: boolean; 781 | } 782 | } -------------------------------------------------------------------------------- /LInQer.Enumerable.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Linqer { 4 | 5 | export interface Enumerable extends Iterable { 6 | /** 7 | * Applies an accumulator function over a sequence. 8 | * The specified seed value is used as the initial accumulator value, and the specified function is used to select the result value. 9 | * 10 | * @param {*} accumulator 11 | * @param {(acc: any, item: any) => any} aggregator 12 | * @returns {*} 13 | * @memberof Enumerable 14 | */ 15 | aggregate(accumulator: any, aggregator: (acc: any, item: any) => any): any; 16 | /** 17 | * Determines whether all elements of a sequence satisfy a condition. 18 | * @param condition 19 | * @returns true if all 20 | */ 21 | all(condition: IFilter): boolean; 22 | /** 23 | * Determines whether any element of a sequence exists or satisfies a condition. 24 | * 25 | * @param {IFilter} condition 26 | * @returns {boolean} 27 | * @memberof Enumerable 28 | */ 29 | any(condition: IFilter): boolean; 30 | /** 31 | * Appends a value to the end of the sequence. 32 | * 33 | * @param {*} item 34 | * @returns {Enumerable} 35 | * @memberof Enumerable 36 | */ 37 | append(item: any): Enumerable; 38 | /** 39 | * Computes the average of a sequence of numeric values. 40 | * 41 | * @returns {(number | undefined)} 42 | * @memberof Enumerable 43 | */ 44 | average(): number | undefined; 45 | /** 46 | * Returns itself 47 | * 48 | * @returns {Enumerable} 49 | * @memberof Enumerable 50 | */ 51 | asEnumerable(): Enumerable; 52 | /** 53 | * Checks the elements of a sequence based on their type 54 | * If type is a string, it will check based on typeof, else it will use instanceof. 55 | * Throws if types are different. 56 | * @param {(string | Function)} type 57 | * @returns {Enumerable} 58 | * @memberof Enumerable 59 | */ 60 | cast(type: string | Function): Enumerable; 61 | /** 62 | * Determines whether a sequence contains a specified element. 63 | * A custom function can be used to determine equality between elements. 64 | * 65 | * @param {*} item 66 | * @param {IEqualityComparer} equalityComparer 67 | * @returns {boolean} 68 | * @memberof Enumerable 69 | */ 70 | contains(item: any, equalityComparer: IEqualityComparer): boolean; 71 | 72 | defaultIfEmpty(): never; 73 | 74 | /** 75 | * Produces the set difference of two sequences 76 | * WARNING: using the comparer is slower 77 | * 78 | * @param {IterableType} iterable 79 | * @param {IEqualityComparer} equalityComparer 80 | * @returns {Enumerable} 81 | * @memberof Enumerable 82 | */ 83 | except(iterable: IterableType, equalityComparer: IEqualityComparer): Enumerable; 84 | /** 85 | * Produces the set intersection of two sequences. 86 | * WARNING: using a comparer is slower 87 | * 88 | * @param {IterableType} iterable 89 | * @param {IEqualityComparer} equalityComparer 90 | * @returns {Enumerable} 91 | * @memberof Enumerable 92 | */ 93 | intersect(iterable: IterableType, equalityComparer: IEqualityComparer): Enumerable; 94 | /** 95 | * Same as count 96 | * 97 | * @returns {number} 98 | * @memberof Enumerable 99 | */ 100 | longCount(): number; 101 | /** 102 | * Filters the elements of a sequence based on their type 103 | * If type is a string, it will filter based on typeof, else it will use instanceof 104 | * 105 | * @param {(string | Function)} type 106 | * @returns {Enumerable} 107 | * @memberof Enumerable 108 | */ 109 | ofType(type: string | Function): Enumerable; 110 | /** 111 | * Adds a value to the beginning of the sequence. 112 | * 113 | * @param {*} item 114 | * @returns {Enumerable} 115 | * @memberof Enumerable 116 | */ 117 | prepend(item: any): Enumerable; 118 | /** 119 | * Inverts the order of the elements in a sequence. 120 | * 121 | * @returns {Enumerable} 122 | * @memberof Enumerable 123 | */ 124 | reverse(): Enumerable; 125 | /** 126 | * Projects each element of a sequence to an iterable and flattens the resulting sequences into one sequence. 127 | * 128 | * @param {ISelector} selector 129 | * @returns {Enumerable} 130 | * @memberof Enumerable 131 | */ 132 | selectMany(selector: ISelector): Enumerable; 133 | /** 134 | * Determines whether two sequences are equal and in the same order according to an optional equality comparer. 135 | * 136 | * @param {IterableType} iterable 137 | * @param {IEqualityComparer} equalityComparer 138 | * @returns {boolean} 139 | * @memberof Enumerable 140 | */ 141 | sequenceEqual(iterable: IterableType, equalityComparer: IEqualityComparer): boolean; 142 | /** 143 | * Returns the single element of a sequence and throws if it doesn't have exactly one 144 | * 145 | * @returns {*} 146 | * @memberof Enumerable 147 | */ 148 | single(): any; 149 | /** 150 | * Returns the single element of a sequence or undefined if none found. It throws if the sequence contains multiple items. 151 | * 152 | * @returns {(any | undefined)} 153 | * @memberof Enumerable 154 | */ 155 | singleOrDefault(): any | undefined; 156 | /** 157 | * Returns a new enumerable collection that contains the elements from source with the last nr elements of the source collection omitted. 158 | * 159 | * @param {number} nr 160 | * @returns {Enumerable} 161 | * @memberof Enumerable 162 | */ 163 | skipLast(nr: number): Enumerable; 164 | /** 165 | * Bypasses elements in a sequence as long as a specified condition is true and then returns the remaining elements. 166 | * 167 | * @param {IFilter} condition 168 | * @returns {Enumerable} 169 | * @memberof Enumerable 170 | */ 171 | skipWhile(condition: IFilter): Enumerable; 172 | 173 | /** 174 | * Selects the elements starting at the given start argument, and ends at, but does not include, the given end argument. 175 | * @param start 176 | * @param end 177 | * @returns slice 178 | */ 179 | slice(start: number | undefined, end: number | undefined) : Enumerable; 180 | 181 | /** 182 | * Returns a new enumerable collection that contains the last nr elements from source. 183 | * 184 | * @param {number} nr 185 | * @returns {Enumerable} 186 | * @memberof Enumerable 187 | */ 188 | takeLast(nr: number): Enumerable; 189 | /** 190 | * Returns elements from a sequence as long as a specified condition is true, and then skips the remaining elements. 191 | * 192 | * @param {IFilter} condition 193 | * @returns {Enumerable} 194 | * @memberof Enumerable 195 | */ 196 | takeWhile(condition: IFilter): Enumerable; 197 | toDictionary(): never; 198 | /** 199 | * creates a Map from an Enumerable 200 | * 201 | * @param {ISelector} keySelector 202 | * @param {ISelector} valueSelector 203 | * @returns {Map} 204 | * @memberof Enumerable 205 | */ 206 | toMap(keySelector: ISelector, valueSelector: ISelector): Map; 207 | /** 208 | * creates an object from an Enumerable 209 | * 210 | * @param {ISelector} keySelector 211 | * @param {ISelector} valueSelector 212 | * @returns {{ [key: string]: any }} 213 | * @memberof Enumerable 214 | */ 215 | toObject(keySelector: ISelector, valueSelector: ISelector): { [key: string]: any }; 216 | toHashSet(): never; 217 | /** 218 | * creates a Set from an enumerable 219 | * 220 | * @returns {Set} 221 | * @memberof Enumerable 222 | */ 223 | toSet(): Set; 224 | /** 225 | * Produces the set union of two sequences. 226 | * 227 | * @param {IterableType} iterable 228 | * @param {IEqualityComparer} equalityComparer 229 | * @returns {Enumerable} 230 | * @memberof Enumerable 231 | */ 232 | union(iterable: IterableType, equalityComparer: IEqualityComparer): Enumerable; 233 | /** 234 | * Applies a specified function to the corresponding elements of two sequences, producing a sequence of the results. 235 | * 236 | * @param {IterableType} iterable 237 | * @param {(item1: any, item2: any, index: number) => any} zipper 238 | * @returns {*} 239 | * @memberof Enumerable 240 | */ 241 | zip(iterable: IterableType, zipper: (item1: any, item2: any, index: number) => any): any; 242 | 243 | } 244 | 245 | /// Applies an accumulator function over a sequence. 246 | /// The specified seed value is used as the initial accumulator value, and the specified function is used to select the result value. 247 | Enumerable.prototype.aggregate = function (accumulator: any, aggregator: (acc: any, item: any) => any): any { 248 | _ensureFunction(aggregator); 249 | for (const item of this) { 250 | accumulator = aggregator(accumulator, item); 251 | } 252 | return accumulator; 253 | } 254 | 255 | /// Determines whether all elements of a sequence satisfy a condition. 256 | Enumerable.prototype.all = function (condition: IFilter): boolean { 257 | _ensureFunction(condition); 258 | return !this.any(x => !condition(x)); 259 | } 260 | 261 | /// Determines whether any element of a sequence exists or satisfies a condition. 262 | Enumerable.prototype.any = function (condition: IFilter): boolean { 263 | _ensureFunction(condition); 264 | let index = 0; 265 | for (const item of this) { 266 | if (condition(item, index)) return true; 267 | index++; 268 | } 269 | return false; 270 | } 271 | 272 | /// Appends a value to the end of the sequence. 273 | Enumerable.prototype.append = function (item: any): Enumerable { 274 | return this.concat([item]); 275 | } 276 | 277 | /// Computes the average of a sequence of numeric values. 278 | Enumerable.prototype.average = function (): number | undefined { 279 | const stats = this.sumAndCount(); 280 | return stats.count === 0 281 | ? undefined 282 | : stats.sum / stats.count; 283 | } 284 | 285 | /// Returns the same enumerable 286 | Enumerable.prototype.asEnumerable = function (): Enumerable { 287 | return this; 288 | } 289 | 290 | /// Checks the elements of a sequence based on their type 291 | /// If type is a string, it will check based on typeof, else it will use instanceof. 292 | /// Throws if types are different. 293 | Enumerable.prototype.cast = function (type: string | Function): Enumerable { 294 | const f: ((x: any) => boolean) = typeof type === 'string' 295 | ? x => typeof x === type 296 | : x => x instanceof type; 297 | return this.select(item => { 298 | if (!f(item)) throw new Error(item + ' not of type ' + type); 299 | return item; 300 | }); 301 | } 302 | 303 | /// Determines whether a sequence contains a specified element. 304 | /// A custom function can be used to determine equality between elements. 305 | Enumerable.prototype.contains = function (item: any, equalityComparer: IEqualityComparer = EqualityComparer.default): boolean { 306 | _ensureFunction(equalityComparer); 307 | return this.any(x => equalityComparer(x, item)); 308 | } 309 | 310 | Enumerable.prototype.defaultIfEmpty = function (): never { 311 | throw new Error('defaultIfEmpty not implemented for Javascript'); 312 | } 313 | 314 | /// Produces the set difference of two sequences WARNING: using the comparer is slower 315 | Enumerable.prototype.except = function (iterable: IterableType, equalityComparer: IEqualityComparer = EqualityComparer.default): Enumerable { 316 | _ensureIterable(iterable); 317 | const self: Enumerable = this; 318 | // use a Set for performance if the comparer is not set 319 | const gen = equalityComparer === EqualityComparer.default 320 | ? function* () { 321 | const distinctValues = Enumerable.from(iterable).toSet(); 322 | for (const item of self) { 323 | if (!distinctValues.has(item)) yield item; 324 | } 325 | } 326 | // use exceptByHash from Linqer.extra for better performance 327 | : function* () { 328 | const values = _toArray(iterable); 329 | for (const item of self) { 330 | let unique = true; 331 | for (let i=0; i typeof x === type 383 | : x => x instanceof type; 384 | return this.where(condition); 385 | } 386 | 387 | /// Adds a value to the beginning of the sequence. 388 | Enumerable.prototype.prepend = function (item: any): Enumerable { 389 | return new Enumerable([item]).concat(this); 390 | } 391 | 392 | /// Inverts the order of the elements in a sequence. 393 | Enumerable.prototype.reverse = function (): Enumerable { 394 | _ensureInternalTryGetAt(this); 395 | const self: Enumerable = this; 396 | // if it can seek, just read the enumerable backwards 397 | const gen = this._canSeek 398 | ? function* () { 399 | const length = self.count(); 400 | for (let index = length - 1; index >= 0; index--) { 401 | yield self.elementAt(index); 402 | } 403 | } 404 | // else enumerate it all into an array, then read it backwards 405 | : function* () { 406 | const arr = self.toArray(); 407 | for (let index = arr.length - 1; index >= 0; index--) { 408 | yield arr[index]; 409 | } 410 | }; 411 | // the count is the same when reversed 412 | const result = new Enumerable(gen); 413 | _ensureInternalCount(this); 414 | result._count = this._count; 415 | _ensureInternalTryGetAt(this); 416 | // have a custom indexer only if the original enumerable could seek 417 | if (this._canSeek) { 418 | const self = this; 419 | result._canSeek = true; 420 | result._tryGetAt = index => self._tryGetAt!(self.count() - index - 1); 421 | } 422 | return result; 423 | } 424 | 425 | /// Projects each element of a sequence to an iterable and flattens the resulting sequences into one sequence. 426 | Enumerable.prototype.selectMany = function (selector: ISelector): Enumerable { 427 | if (typeof selector !== 'undefined') { 428 | _ensureFunction(selector); 429 | } else { 430 | selector = x => x; 431 | } 432 | const self: Enumerable = this; 433 | const gen = function* () { 434 | let index = 0; 435 | for (const item of self) { 436 | const iter = selector(item, index); 437 | _ensureIterable(iter); 438 | for (const child of iter) { 439 | yield child; 440 | } 441 | index++; 442 | } 443 | }; 444 | return new Enumerable(gen); 445 | } 446 | 447 | /// Determines whether two sequences are equal and in the same order according to an equality comparer. 448 | Enumerable.prototype.sequenceEqual = function (iterable: IterableType, equalityComparer: IEqualityComparer = EqualityComparer.default): boolean { 449 | _ensureIterable(iterable); 450 | _ensureFunction(equalityComparer); 451 | const iterator1 = this[Symbol.iterator](); 452 | const iterator2 = Enumerable.from(iterable)[Symbol.iterator](); 453 | let done = false; 454 | do { 455 | const val1 = iterator1.next(); 456 | const val2 = iterator2.next(); 457 | const equal = (val1.done && val2.done) || (!val1.done && !val2.done && equalityComparer(val1.value, val2.value)); 458 | if (!equal) return false; 459 | done = !!val1.done; 460 | } while (!done); 461 | return true; 462 | } 463 | 464 | /// Returns the single element of a sequence and throws if it doesn't have exactly one 465 | Enumerable.prototype.single = function (): any { 466 | const iterator = this[Symbol.iterator](); 467 | let val = iterator.next(); 468 | if (val.done) throw new Error('Sequence contains no elements'); 469 | const result = val.value; 470 | val = iterator.next(); 471 | if (!val.done) throw new Error('Sequence contains more than one element'); 472 | return result; 473 | } 474 | 475 | /// Returns the single element of a sequence or undefined if none found. It throws if the sequence contains multiple items. 476 | Enumerable.prototype.singleOrDefault = function (): any | undefined { 477 | const iterator = this[Symbol.iterator](); 478 | let val = iterator.next(); 479 | if (val.done) return undefined; 480 | const result = val.value; 481 | val = iterator.next(); 482 | if (!val.done) throw new Error('Sequence contains more than one element'); 483 | return result; 484 | } 485 | 486 | /// Selects the elements starting at the given start argument, and ends at, but does not include, the given end argument. 487 | Enumerable.prototype.slice = function (start: number = 0, end: number | undefined): Enumerable { 488 | let enumerable: Enumerable = this; 489 | // when the end is defined and positive and start is negative, 490 | // the only way to compute the last index is to know the count 491 | if (end !== undefined && end >= 0 && (start || 0) < 0) { 492 | enumerable = enumerable.toList(); 493 | start = enumerable.count() + start; 494 | } 495 | if (start !== 0) { 496 | if (start > 0) { 497 | enumerable = enumerable.skip(start); 498 | } else { 499 | enumerable = enumerable.takeLast(-start); 500 | } 501 | } 502 | if (end !== undefined) { 503 | if (end >= 0) { 504 | enumerable = enumerable.take(end - start); 505 | } else { 506 | enumerable = enumerable.skipLast(-end); 507 | } 508 | } 509 | return enumerable; 510 | } 511 | 512 | /// Returns a new enumerable collection that contains the elements from source with the last nr elements of the source collection omitted. 513 | Enumerable.prototype.skipLast = function (nr: number): Enumerable { 514 | const self: Enumerable = this; 515 | // the generator is using a buffer to cache nr values 516 | // and only yields the values that overflow from it 517 | const gen = function* () { 518 | let nrLeft = nr; 519 | const buffer = Array(nrLeft); 520 | let index = 0; 521 | let offset = 0; 522 | for (const item of self) { 523 | const value = buffer[index - offset]; 524 | buffer[index - offset] = item; 525 | index++; 526 | if (index - offset >= nrLeft) { 527 | offset += nrLeft; 528 | } 529 | if (index > nrLeft) { 530 | yield value; 531 | } 532 | } 533 | buffer.length = 0; 534 | }; 535 | const result = new Enumerable(gen); 536 | 537 | // the count is the original count minus the skipped items and at least 0 538 | result._count = () => Math.max(0, self.count() - nr); 539 | _ensureInternalTryGetAt(this); 540 | result._canSeek = this._canSeek; 541 | // it has an indexer only if the original enumerable can seek 542 | if (this._canSeek) { 543 | result._tryGetAt = index => { 544 | if (index >= result.count()) return null; 545 | return self._tryGetAt!(index); 546 | } 547 | } 548 | return result; 549 | } 550 | 551 | 552 | /// Bypasses elements in a sequence as long as a specified condition is true and then returns the remaining elements. 553 | Enumerable.prototype.skipWhile = function (condition: IFilter): Enumerable { 554 | _ensureFunction(condition); 555 | const self: Enumerable = this; 556 | let skip = true; 557 | const gen = function* () { 558 | let index = 0; 559 | for (const item of self) { 560 | if (skip && !condition(item, index)) { 561 | skip = false; 562 | } 563 | if (!skip) { 564 | yield item; 565 | } 566 | index++; 567 | } 568 | }; 569 | return new Enumerable(gen); 570 | } 571 | 572 | /// Returns a new enumerable collection that contains the last nr elements from source. 573 | Enumerable.prototype.takeLast = function (nr: number): Enumerable { 574 | _ensureInternalTryGetAt(this); 575 | const self: Enumerable = this; 576 | const gen = this._canSeek 577 | // taking the last items is easy if the enumerable can seek 578 | ? function* () { 579 | let nrLeft = nr; 580 | const length = self.count(); 581 | for (let index = length - nrLeft; index < length; index++) { 582 | yield self.elementAt(index); 583 | } 584 | } 585 | // else the generator uses a buffer to fill with values 586 | // and yields them after the entire thing has been iterated 587 | : function* () { 588 | let nrLeft = nr; 589 | let index = 0; 590 | const buffer = Array(nrLeft); 591 | for (const item of self) { 592 | buffer[index % nrLeft] = item; 593 | index++; 594 | } 595 | for (let i = 0; i < nrLeft && i < index; i++) { 596 | yield buffer[(index + i) % nrLeft]; 597 | } 598 | }; 599 | const result = new Enumerable(gen); 600 | 601 | // the count is the minimum between nr and the enumerable count 602 | result._count = () => Math.min(nr, self.count()); 603 | result._canSeek = self._canSeek; 604 | // this can seek only if the original enumerable could seek 605 | if (self._canSeek) { 606 | result._tryGetAt = index => { 607 | if (index < 0 || index >= result.count()) return null; 608 | return self._tryGetAt!(self.count() - nr + index); 609 | }; 610 | } 611 | return result; 612 | } 613 | 614 | /// Returns elements from a sequence as long as a specified condition is true, and then skips the remaining elements. 615 | Enumerable.prototype.takeWhile = function (condition: IFilter): Enumerable { 616 | _ensureFunction(condition); 617 | const self: Enumerable = this; 618 | const gen = function* () { 619 | let index = 0; 620 | for (const item of self) { 621 | if (condition(item, index)) { 622 | yield item; 623 | } else { 624 | break; 625 | } 626 | index++; 627 | } 628 | }; 629 | return new Enumerable(gen); 630 | } 631 | 632 | Enumerable.prototype.toDictionary = function (): never { 633 | throw new Error('use toMap or toObject instead of toDictionary'); 634 | } 635 | 636 | /// creates a map from an Enumerable 637 | Enumerable.prototype.toMap = function (keySelector: ISelector, valueSelector: ISelector = x => x): Map { 638 | _ensureFunction(keySelector); 639 | _ensureFunction(valueSelector); 640 | const result = new Map(); 641 | let index = 0; 642 | for (const item of this) { 643 | result.set(keySelector(item, index), valueSelector(item, index)); 644 | index++; 645 | } 646 | return result; 647 | } 648 | 649 | /// creates an object from an enumerable 650 | Enumerable.prototype.toObject = function (keySelector: ISelector, valueSelector: ISelector = x => x): { [key: string]: any } { 651 | _ensureFunction(keySelector); 652 | _ensureFunction(valueSelector); 653 | const result: { [key: string]: any } = {}; 654 | let index = 0; 655 | for (const item of this) { 656 | result[keySelector(item, index)] = valueSelector(item); 657 | index++; 658 | } 659 | return result; 660 | } 661 | 662 | Enumerable.prototype.toHashSet = function (): never { 663 | throw new Error('use toSet instead of toHashSet'); 664 | } 665 | 666 | /// creates a set from an enumerable 667 | Enumerable.prototype.toSet = function (): Set { 668 | const result = new Set(); 669 | for (const item of this) { 670 | result.add(item); 671 | } 672 | return result; 673 | } 674 | 675 | /// Produces the set union of two sequences. 676 | Enumerable.prototype.union = function (iterable: IterableType, equalityComparer: IEqualityComparer = EqualityComparer.default): Enumerable { 677 | _ensureIterable(iterable); 678 | return this.concat(iterable).distinct(equalityComparer); 679 | } 680 | 681 | /// Applies a specified function to the corresponding elements of two sequences, producing a sequence of the results. 682 | Enumerable.prototype.zip = function (iterable: IterableType, zipper: (item1: any, item2: any, index: number) => any): any { 683 | _ensureIterable(iterable); 684 | if (!zipper) { 685 | zipper = (i1,i2)=>[i1,i2]; 686 | } else { 687 | _ensureFunction(zipper); 688 | } 689 | const self: Enumerable = this; 690 | const gen = function* () { 691 | let index = 0; 692 | const iterator1 = self[Symbol.iterator](); 693 | const iterator2 = Enumerable.from(iterable)[Symbol.iterator](); 694 | let done = false; 695 | do { 696 | const val1 = iterator1.next(); 697 | const val2 = iterator2.next(); 698 | done = !!(val1.done || val2.done); 699 | if (!done) { 700 | yield zipper(val1.value, val2.value, index); 701 | } 702 | index++; 703 | } while (!done); 704 | }; 705 | return new Enumerable(gen); 706 | } 707 | } --------------------------------------------------------------------------------