├── .yarnrc ├── .npmrc ├── .travis.yml ├── tsconfig.esm.json ├── .gitignore ├── src ├── index.ts ├── IterateAction.ts ├── types.ts ├── __tests__ │ ├── functions.spec.ts │ └── Iterator.spec.ts ├── functions.ts └── Iterate.ts ├── typings ├── index.d.ts ├── IterateAction.d.ts ├── types.d.ts ├── functions.d.ts └── Iterate.d.ts ├── .npmignore ├── .editorconfig ├── dist ├── iteratez.js.map └── iteratez.js ├── jest.config.js ├── tsconfig.json ├── LICENSE ├── webpack.config.js ├── tslint.json ├── package.json └── README.md /.yarnrc: -------------------------------------------------------------------------------- 1 | save-prefix false 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | access=public 2 | save-exact=true 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: '6' 3 | cache: yarn 4 | notifications: 5 | email: false 6 | script: 7 | - npm run build 8 | -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "outDir": "lib-esm", 5 | "target": "es2017" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .idea 3 | .DS_Store 4 | node_modules 5 | 6 | coverage 7 | lib 8 | lib-esm 9 | lib-fesm 10 | docs 11 | 12 | ## this is generated by `npm pack` 13 | *.tgz 14 | package 15 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './types'; 3 | export * from './functions'; 4 | export * from './IterateAction'; 5 | export * from './Iterate'; 6 | 7 | import { iterate } from './functions'; 8 | 9 | export default iterate; -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export * from './functions'; 3 | export * from './IterateAction'; 4 | export * from './Iterate'; 5 | import { iterate } from './functions'; 6 | export default iterate; 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.log 3 | **/tsconfig.json 4 | tsconfig.*.json 5 | tslint.json 6 | **/webpack.config.js 7 | 8 | __tests__ 9 | coverage 10 | node_modules 11 | docs 12 | src 13 | 14 | ## this is generated by `npm pack` 15 | *.tgz 16 | package 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # all files 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | max_line_length = 120 13 | 14 | [*.{js,ts}] 15 | quote_type = single 16 | curly_bracket_next_line = false 17 | spaces_around_brackets = inside 18 | indent_brace_style = BSD KNF 19 | 20 | # HTML 21 | [*.html] 22 | quote_type = double -------------------------------------------------------------------------------- /src/IterateAction.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * An action to perform on the source as instructed by the iterator. 4 | */ 5 | export enum IterateAction 6 | { 7 | /** 8 | * Continue iteration. 9 | */ 10 | CONTINUE, 11 | 12 | /** 13 | * Stop iteration. 14 | */ 15 | STOP, 16 | 17 | /** 18 | * Remove the current item if possible, and continue iteration. 19 | */ 20 | REMOVE, 21 | 22 | /** 23 | * Replace the current item with the provided value. 24 | */ 25 | REPLACE 26 | } -------------------------------------------------------------------------------- /typings/IterateAction.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An action to perform on the source as instructed by the iterator. 3 | */ 4 | export declare enum IterateAction { 5 | /** 6 | * Continue iteration. 7 | */ 8 | CONTINUE = 0, 9 | /** 10 | * Stop iteration. 11 | */ 12 | STOP = 1, 13 | /** 14 | * Remove the current item if possible, and continue iteration. 15 | */ 16 | REMOVE = 2, 17 | /** 18 | * Replace the current item with the provided value. 19 | */ 20 | REPLACE = 3 21 | } 22 | -------------------------------------------------------------------------------- /dist/iteratez.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"iteratez.js","sources":["webpack://[name]/webpack/universalModuleDefinition"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine(\"iteratez\", [], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"iteratez\"] = factory();\n\telse\n\t\troot[\"iteratez\"] = factory();\n})(this, function() {\nreturn "],"mappings":"AAAA","sourceRoot":""} -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testURL: 'http://localhost', 3 | roots: [ 4 | '/src', 5 | ], 6 | testRegex: '(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$', 7 | moduleFileExtensions: [ 8 | 'js', 9 | 'ts', 10 | 'tsx', 11 | ], 12 | coveragePathIgnorePatterns: [ 13 | '/node_modules/', 14 | '/lib/', 15 | '/lib-esm/', 16 | '/umd/', 17 | '/src/.*(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$', 18 | ], 19 | coverageThreshold: { 20 | global: { 21 | branches: 80, 22 | functions: 80, 23 | lines: 80, 24 | statements: 85, 25 | }, 26 | }, 27 | preset: 'ts-jest', 28 | testMatch: null, 29 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "esnext", 5 | "target": "es5", 6 | "lib": [ 7 | "dom", 8 | "es2015", 9 | "es2016", 10 | "es2017" 11 | ], 12 | "strict": true, 13 | "strictNullChecks": false, 14 | "allowSyntheticDefaultImports": true, 15 | "suppressImplicitAnyIndexErrors": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "pretty": true, 18 | "sourceMap": true, 19 | "outDir": "lib", 20 | "declaration": true, 21 | "declarationDir": "typings", 22 | "stripInternal": true, 23 | "noUnusedLocals": true, 24 | "downlevelIteration": true 25 | }, 26 | "include": [ 27 | "./src" 28 | ], 29 | "exclude": [ 30 | "node_modules" 31 | ], 32 | "compileOnSave": false, 33 | "buildOnSave": false 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Philip Diffenderfer 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 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const ROOT = path.resolve( __dirname, 'src' ); 4 | const DESTINATION = path.resolve( __dirname, 'dist' ); 5 | 6 | module.exports = { 7 | context: ROOT, 8 | 9 | entry: { 10 | iteratez: './index.ts' 11 | }, 12 | 13 | output: { 14 | filename: '[name].js', 15 | path: DESTINATION, 16 | libraryTarget: 'umd', 17 | library: '[name]', 18 | umdNamedDefine: true, 19 | globalObject: 'this' 20 | }, 21 | 22 | resolve: { 23 | extensions: ['.ts', '.js'], 24 | modules: [ 25 | ROOT, 26 | 'node_modules' 27 | ] 28 | }, 29 | 30 | module: { 31 | rules: [ 32 | { 33 | enforce: 'pre', 34 | test: /\.js$/, 35 | use: 'source-map-loader' 36 | }, 37 | { 38 | enforce: 'pre', 39 | test: /\.ts$/, 40 | exclude: /node_modules/, 41 | use: 'tslint-loader' 42 | }, 43 | { 44 | test: /\.ts$/, 45 | exclude: [ /node_modules/ ], 46 | use: 'awesome-typescript-loader' 47 | } 48 | ] 49 | }, 50 | 51 | devtool: 'cheap-module-source-map', 52 | devServer: {} 53 | }; -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint-config-standard", 4 | "tslint-react", 5 | "tslint-config-prettier" 6 | ], 7 | "rules": { 8 | // tslint-react rules 9 | "jsx-no-lambda": true, 10 | "jsx-no-string-ref": true, 11 | "jsx-self-close": true, 12 | "jsx-boolean-value": [ 13 | true, 14 | "never" 15 | ], 16 | // core ts-lint rules 17 | "await-promise": true, 18 | "no-unused-variable": true, 19 | "forin": false, 20 | "no-bitwise": true, 21 | "no-console": [ 22 | true, 23 | "debug", 24 | "info", 25 | "time", 26 | "timeEnd", 27 | "trace" 28 | ], 29 | "radix": false, 30 | "no-use-before-declare": false, 31 | "member-ordering": false, 32 | "no-construct": true, 33 | "no-debugger": true, 34 | "no-shadowed-variable": true, 35 | "no-string-literal": true, 36 | "no-inferrable-types": false, 37 | "no-unnecessary-initializer": true, 38 | "no-magic-numbers": true, 39 | "no-require-imports": true, 40 | "no-duplicate-super": true, 41 | "no-boolean-literal-compare": true, 42 | "no-namespace": [ 43 | true, 44 | "allow-declarations" 45 | ], 46 | "no-invalid-this": [ 47 | true, 48 | "check-function-in-method" 49 | ], 50 | "ordered-imports": [ 51 | true 52 | ], 53 | "interface-name": [ 54 | false 55 | ], 56 | "newline-before-return": true, 57 | "object-literal-shorthand": true, 58 | "typeof-compare": true, 59 | "arrow-return-shorthand": [ 60 | true 61 | ], 62 | "unified-signatures": true, 63 | "prefer-for-of": true, 64 | "match-default-export-name": true, 65 | "prefer-const": true, 66 | "prefer-method-signature": true, 67 | "ban-types": [ 68 | true, 69 | [ 70 | "Object", 71 | "Avoid using the `Object` type. Did you mean `object`?" 72 | ], 73 | [ 74 | "Function", 75 | "Avoid using the `Function` type. Prefer a specific function type, like `() => void`." 76 | ], 77 | [ 78 | "Boolean", 79 | "Avoid using the `Boolean` type. Did you mean `boolean`?" 80 | ], 81 | [ 82 | "Number", 83 | "Avoid using the `Number` type. Did you mean `number`?" 84 | ], 85 | [ 86 | "String", 87 | "Avoid using the `String` type. Did you mean `string`?" 88 | ], 89 | [ 90 | "Symbol", 91 | "Avoid using the `Symbol` type. Did you mean `symbol`?" 92 | ], 93 | [ 94 | "Array", 95 | "Avoid using the `Array` type. Use 'type[]' instead." 96 | ] 97 | ] 98 | } 99 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iteratez", 3 | "version": "1.2.2", 4 | "description": "A powerful functional iterator, transformer, and mutator - like Underscore.js, except for everything", 5 | "main": "dist/iteratez.js", 6 | "typings": "typings/index.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://www.github.com/ClickerMonkey/iteratez" 10 | }, 11 | "authors": [ 12 | "Philip Diffenderfer " 13 | ], 14 | "license": "MIT", 15 | "engines": { 16 | "node": ">=6.9" 17 | }, 18 | "scripts": { 19 | "start": "webpack-dev-server --port 9000 --inline --progress --profile --colors --watch --content-base src/ --mode development", 20 | "build": "webpack --config webpack.config.js --mode production && cp src/types.ts typings/types.d.ts", 21 | "build.prod": "webpack --config webpack.config.js -p && cp src/types.ts typings/types.d.ts", 22 | "test": "jest", 23 | "test.debug": "node --inspect-brk node_modules/.bin/jest --runInBand", 24 | "cleanup": "shx rm -rf umd lib lib-esm lib-fesm typings coverage ../iteratez-pages/docs", 25 | "prebuild": "npm run cleanup && npm run verify", 26 | "docs": "typedoc -p . --readme README.md --theme minimal --mode file --target 'es5' --excludeNotExported --excludePrivate --ignoreCompilerErrors --exclude \"**/src/**/__tests__/*.*\" --out ../iteratez-pages/docs src/ && cp -R examples ../iteratez-pages/ && cp -R umd ../iteratez-pages/", 27 | "test:watch": "npm test -- --watch", 28 | "test:coverage": "npm test -- --coverage", 29 | "test:only-changed": "npm test -- --bail --onlyChanged", 30 | "verify": "npm test", 31 | "cz": "git-cz", 32 | "release:npm": "npm publish", 33 | "release:preflight": "irish-pub", 34 | "release:preflight:package": "npm pack", 35 | "size": "shx echo \"Gzipped Size:\" && cross-var strip-json-comments --no-whitespace $npm_package_main | gzip-size" 36 | }, 37 | "config": { 38 | "commitizen": { 39 | "path": "./node_modules/cz-conventional-changelog" 40 | }, 41 | "validate-commit-msg": { 42 | "types": "conventional-commit-types", 43 | "maxSubjectLength": 120 44 | } 45 | }, 46 | "lint-staged": { 47 | "src/**/*.{ts,tsx}": [ 48 | "npm run prettier:ts -- --write", 49 | "ts:lint:fix", 50 | "git add" 51 | ] 52 | }, 53 | "devDependencies": { 54 | "@types/jest": "24.0.11", 55 | "@types/node": "11.13.4", 56 | "awesome-typescript-loader": "5.2.1", 57 | "commitizen": "3.0.7", 58 | "cross-var": "1.1.0", 59 | "cz-conventional-changelog": "2.1.0", 60 | "gzip-size-cli": "3.0.0", 61 | "irish-pub": "0.2.0", 62 | "jest": "24.7.1", 63 | "jest-cli": "24.7.1", 64 | "lint-staged": "8.1.5", 65 | "prettier": "1.17.0", 66 | "shx": "0.3.2", 67 | "standard-version": "5.0.2", 68 | "strip-json-comments-cli": "1.0.1", 69 | "ts-jest": "24.0.2", 70 | "tslint": "5.16.0", 71 | "tslint-config-prettier": "1.18.0", 72 | "tslint-config-standard": "8.0.1", 73 | "tslint-loader": "3.5.4", 74 | "tslint-react": "4.0.0", 75 | "typedoc": "0.14.2", 76 | "typescript": "3.4.3", 77 | "uglify-js": "3.5.4", 78 | "uglifyjs-webpack-plugin": "2.1.2", 79 | "validate-commit-msg": "2.14.0", 80 | "webpack": "4.30.0", 81 | "webpack-cli": "3.3.0", 82 | "webpack-config-utils": "2.3.1", 83 | "webpack-dev-server": "3.1.4" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Iterate } from './Iterate'; 3 | 4 | 5 | /** 6 | * The callback which is invoked for each item in the Iterate. The callback 7 | * can call [[Iterate.stop]] at anytime to stop iteration. 8 | * 9 | * @param item The item found in the iterator. 10 | * @param iterator The iterator with the item. 11 | * @returns The result of the callback. 12 | */ 13 | export type IterateCallback = (item: T, key: K, iterator: Iterate) => R; 14 | 15 | /** 16 | * An [[Iterate]] source which handles iterating over items and calls 17 | * [[Iterate.act]] for each item, taking the requested action when possible. 18 | * 19 | * @param callback The function to invoke for each item. 20 | * @param iterator The iterator to check for early exists. 21 | */ 22 | export type IterateSource = (iterator: Iterate) => any; 23 | 24 | /** 25 | * A where to apply duration iteration to only look at certain items when this 26 | * function returns `true`. 27 | * 28 | * @param item The item being iterated. 29 | * @returns `true` if the item should be iterated, otherwise `false`. 30 | */ 31 | export type IterateFilter = (item: T, key?: K) => any; 32 | 33 | /** 34 | * Tests two items for equality. 35 | * 36 | * @param a The first item. 37 | * @param b The second item. 38 | * @returns `true` if the items are considered equal. 39 | */ 40 | export type IterateEquals = (a: T, b: T, aKey?: K, bKey?: K) => boolean; 41 | 42 | /** 43 | * Compares two items. 44 | * - if `a` < `b` then a negative number should be returned. 45 | * - if `a` > `b` then a positive number should be returned. 46 | * - if `a` = `b` then zero should be returned. 47 | * 48 | * @param a The first item. 49 | * @param b The second item. 50 | * @returns A number representing which item is greater. 51 | */ 52 | export type IterateCompare = (a: T, b: T, aKey?: K, bKey?: K) => number; 53 | 54 | /** 55 | * A function which handles a reset on an iterate. 56 | */ 57 | export type IterateReset = (source: S) => any; 58 | 59 | 60 | /** 61 | * A function that looks at the source and if it can provide an iterator 62 | * it returns one. Otherwise false is returned. 63 | * 64 | * @param source The source to test. 65 | */ 66 | export type IterateGenerator = (source: any) => Iterate | false; 67 | 68 | /** 69 | * A type which takes a source and returns a result from a function. 70 | */ 71 | export type IterateFunction = 72 | [S] extends [any] 73 | ? (source: IterateSourceTypeKey, ...args: A) => R 74 | : (source: S, ...args: A) => R; 75 | 76 | /** 77 | * A function which performs a function on a subject and possibly applies a result. 78 | */ 79 | export type IterateFunctionExecute = 80 | [R] extends [void] 81 | ? (subject: Iterate) => any 82 | : [A] extends [] 83 | ? (subject: Iterate, setResult: (result: R) => any) => any 84 | : (subject: Iterate, setResult: (result: R) => any, ...args: A) => any; 85 | 86 | /** 87 | * A function for handling a result of an operation. 88 | */ 89 | export type IterateResult = (result: T) => any; 90 | 91 | /** 92 | * A type which has an entries() function that returns an IterableIterator. 93 | */ 94 | export interface HasEntries 95 | { 96 | entries(): IterableIterator<[K, T]> 97 | } 98 | 99 | /** 100 | * Any of the valid sources natively supported. 101 | * 102 | * You can add to these types by overriding the definitions. See the README. 103 | */ 104 | export type IterateSourceType = 105 | Iterate | 106 | T[] | 107 | Set | 108 | Map | 109 | Iterable | 110 | HasEntries | 111 | { [key: string]: T } | 112 | null | 113 | undefined | 114 | T; 115 | 116 | /** 117 | * Any of the valid sources natively supported with specific keys. 118 | * 119 | * You can add to these types by overriding the definitions. See the README. 120 | */ 121 | export type IterateSourceTypeKey = 122 | Iterate | 123 | (K extends number ? T[] : never) | 124 | (K extends T ? Set : never) | 125 | Map | 126 | (K extends number ? Iterable : never) | 127 | HasEntries | 128 | (K extends string ? { [key: string]: T } : never ) | 129 | null | 130 | undefined | 131 | (K extends number ? T : never); 132 | 133 | /** 134 | * Given a source, attempt to get its stype. 135 | */ 136 | export type GetValueFor = 137 | (S extends Array ? T : never) | 138 | (S extends Iterate ? T : never) | 139 | (S extends Set ? T : never) | 140 | (S extends Map ? T : never) | 141 | (S extends Iterable ? T : never) | 142 | (S extends HasEntries ? T : never) | 143 | (S extends { [key: string]: infer T } ? T : never); 144 | 145 | /** 146 | * Given a source, attempt to get its key. 147 | */ 148 | export type GetKeyFor = 149 | (S extends Iterate ? K : never) | 150 | (S extends Array ? number : never) | 151 | (S extends Set ? T : never) | 152 | (S extends Map ? K : never) | 153 | (S extends Iterable ? number : never) | 154 | (S extends HasEntries ? K : never) | 155 | (S extends { [key: string]: infer T } ? string : never) | 156 | number; 157 | 158 | -------------------------------------------------------------------------------- /typings/types.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Iterate } from './Iterate'; 3 | 4 | 5 | /** 6 | * The callback which is invoked for each item in the Iterate. The callback 7 | * can call [[Iterate.stop]] at anytime to stop iteration. 8 | * 9 | * @param item The item found in the iterator. 10 | * @param iterator The iterator with the item. 11 | * @returns The result of the callback. 12 | */ 13 | export type IterateCallback = (item: T, key: K, iterator: Iterate) => R; 14 | 15 | /** 16 | * An [[Iterate]] source which handles iterating over items and calls 17 | * [[Iterate.act]] for each item, taking the requested action when possible. 18 | * 19 | * @param callback The function to invoke for each item. 20 | * @param iterator The iterator to check for early exists. 21 | */ 22 | export type IterateSource = (iterator: Iterate) => any; 23 | 24 | /** 25 | * A where to apply duration iteration to only look at certain items when this 26 | * function returns `true`. 27 | * 28 | * @param item The item being iterated. 29 | * @returns `true` if the item should be iterated, otherwise `false`. 30 | */ 31 | export type IterateFilter = (item: T, key?: K) => any; 32 | 33 | /** 34 | * Tests two items for equality. 35 | * 36 | * @param a The first item. 37 | * @param b The second item. 38 | * @returns `true` if the items are considered equal. 39 | */ 40 | export type IterateEquals = (a: T, b: T, aKey?: K, bKey?: K) => boolean; 41 | 42 | /** 43 | * Compares two items. 44 | * - if `a` < `b` then a negative number should be returned. 45 | * - if `a` > `b` then a positive number should be returned. 46 | * - if `a` = `b` then zero should be returned. 47 | * 48 | * @param a The first item. 49 | * @param b The second item. 50 | * @returns A number representing which item is greater. 51 | */ 52 | export type IterateCompare = (a: T, b: T, aKey?: K, bKey?: K) => number; 53 | 54 | /** 55 | * A function which handles a reset on an iterate. 56 | */ 57 | export type IterateReset = (source: S) => any; 58 | 59 | 60 | /** 61 | * A function that looks at the source and if it can provide an iterator 62 | * it returns one. Otherwise false is returned. 63 | * 64 | * @param source The source to test. 65 | */ 66 | export type IterateGenerator = (source: any) => Iterate | false; 67 | 68 | /** 69 | * A type which takes a source and returns a result from a function. 70 | */ 71 | export type IterateFunction = 72 | [S] extends [any] 73 | ? (source: IterateSourceTypeKey, ...args: A) => R 74 | : (source: S, ...args: A) => R; 75 | 76 | /** 77 | * A function which performs a function on a subject and possibly applies a result. 78 | */ 79 | export type IterateFunctionExecute = 80 | [R] extends [void] 81 | ? (subject: Iterate) => any 82 | : [A] extends [] 83 | ? (subject: Iterate, setResult: (result: R) => any) => any 84 | : (subject: Iterate, setResult: (result: R) => any, ...args: A) => any; 85 | 86 | /** 87 | * A function for handling a result of an operation. 88 | */ 89 | export type IterateResult = (result: T) => any; 90 | 91 | /** 92 | * A type which has an entries() function that returns an IterableIterator. 93 | */ 94 | export interface HasEntries 95 | { 96 | entries(): IterableIterator<[K, T]> 97 | } 98 | 99 | /** 100 | * Any of the valid sources natively supported. 101 | * 102 | * You can add to these types by overriding the definitions. See the README. 103 | */ 104 | export type IterateSourceType = 105 | Iterate | 106 | T[] | 107 | Set | 108 | Map | 109 | Iterable | 110 | HasEntries | 111 | { [key: string]: T } | 112 | null | 113 | undefined | 114 | T; 115 | 116 | /** 117 | * Any of the valid sources natively supported with specific keys. 118 | * 119 | * You can add to these types by overriding the definitions. See the README. 120 | */ 121 | export type IterateSourceTypeKey = 122 | Iterate | 123 | (K extends number ? T[] : never) | 124 | (K extends T ? Set : never) | 125 | Map | 126 | (K extends number ? Iterable : never) | 127 | HasEntries | 128 | (K extends string ? { [key: string]: T } : never ) | 129 | null | 130 | undefined | 131 | (K extends number ? T : never); 132 | 133 | /** 134 | * Given a source, attempt to get its stype. 135 | */ 136 | export type GetValueFor = 137 | (S extends Array ? T : never) | 138 | (S extends Iterate ? T : never) | 139 | (S extends Set ? T : never) | 140 | (S extends Map ? T : never) | 141 | (S extends Iterable ? T : never) | 142 | (S extends HasEntries ? T : never) | 143 | (S extends { [key: string]: infer T } ? T : never); 144 | 145 | /** 146 | * Given a source, attempt to get its key. 147 | */ 148 | export type GetKeyFor = 149 | (S extends Iterate ? K : never) | 150 | (S extends Array ? number : never) | 151 | (S extends Set ? T : never) | 152 | (S extends Map ? K : never) | 153 | (S extends Iterable ? number : never) | 154 | (S extends HasEntries ? K : never) | 155 | (S extends { [key: string]: infer T } ? string : never) | 156 | number; 157 | 158 | -------------------------------------------------------------------------------- /src/__tests__/functions.spec.ts: -------------------------------------------------------------------------------- 1 | // import { describe, it, expect } from 'jest'; 2 | // tslint:disable-next-line: match-default-export-name 3 | import iz, { Iterate } from '../'; 4 | 5 | 6 | // tslint:disable: no-magic-numbers 7 | 8 | describe('iz', () => { 9 | 10 | it('array', () => 11 | { 12 | const source = [1, 2, 3]; 13 | // Iterate 14 | const a = iz(source); 15 | 16 | expect( a.array() ).toEqual( [1, 2, 3] ); 17 | expect( a.keys().array() ).toEqual( [0, 1, 2] ); 18 | 19 | a.where(x => x % 2 === 0).delete(); 20 | 21 | expect( source ).toEqual( [1, 3] ); 22 | }); 23 | 24 | it('object', () => 25 | { 26 | const source = { 27 | a: 'Apple', 28 | b: 'Banana', 29 | c: 'Cat', 30 | d: 'Dog' 31 | }; 32 | // Iterate 33 | const a = iz(source); 34 | 35 | expect( a.array() ).toEqual( ['Apple', 'Banana', 'Cat', 'Dog'] ); 36 | expect( a.keys().array() ).toEqual( ['a', 'b', 'c', 'd'] ); 37 | 38 | a.where((x, k) => k === 'c').delete(); 39 | 40 | expect( source ).toEqual({ 41 | a: 'Apple', 42 | b: 'Banana', 43 | d: 'Dog' 44 | }); 45 | }); 46 | 47 | it('set', () => 48 | { 49 | const source = new Set([1, 2, 3]); 50 | // Iterate> 51 | const a = iz(source); 52 | 53 | expect( a.array() ).toEqual( [1, 2, 3] ); 54 | expect( a.keys().array() ).toEqual( [1, 2, 3] ); 55 | 56 | a.where(x => x % 2 === 0).delete(); 57 | 58 | expect( [...source.values()] ).toEqual( [1, 3] ); 59 | }); 60 | 61 | it('map', () => 62 | { 63 | const source = new Map([ 64 | ['a', 'Apple'], 65 | ['b', 'Banana'], 66 | ['c', 'Cat'], 67 | ['d', 'Dog'] 68 | ]); 69 | // Iterate> 70 | const a = iz(source); 71 | 72 | expect( a.array() ).toEqual( ['Apple', 'Banana', 'Cat', 'Dog'] ); 73 | expect( a.keys().array() ).toEqual( ['a', 'b', 'c', 'd'] ); 74 | 75 | a.where((x, k) => k === 'c').delete(); 76 | 77 | expect( [...source.entries()] ).toEqual([ 78 | ['a', 'Apple'], 79 | ['b', 'Banana'], 80 | ['d', 'Dog'] 81 | ]); 82 | }); 83 | 84 | it('value', () => 85 | { 86 | const source = 4; 87 | // Iterate 88 | const a = iz(source); 89 | 90 | expect( a.array() ).toEqual( [4] ); 91 | expect( a.keys().array() ).toEqual( [0] ); 92 | 93 | a.delete(); 94 | 95 | expect( a.array() ).toEqual( [] ); 96 | }); 97 | 98 | it('null', () => 99 | { 100 | const source: any = null; 101 | // Iterate<{}, number, any> 102 | const a = iz(source); 103 | 104 | expect( a.array() ).toEqual( [] ); 105 | expect( a.keys().array() ).toEqual( [] ); 106 | 107 | a.delete(); 108 | 109 | expect( a.array() ).toEqual( [] ); 110 | }); 111 | 112 | it('undefined', () => 113 | { 114 | // Iterate 115 | const a = iz(); 116 | 117 | expect( a.array() ).toEqual( [] ); 118 | expect( a.keys().array() ).toEqual( [] ); 119 | 120 | a.delete(); 121 | 122 | expect( a.array() ).toEqual( [] ); 123 | }); 124 | 125 | it('string', () => 126 | { 127 | // Iterate 128 | const a = iz('hello'); 129 | 130 | expect( a.array() ).toEqual( ['h', 'e', 'l', 'l', 'o'] ); 131 | }); 132 | 133 | it('joins type', () => 134 | { 135 | const a = 45; 136 | const b = [1, 2, 3]; 137 | const c = {a: 1, b: 2}; 138 | // Iterate 139 | const joined = Iterate.join(a, b, c); 140 | 141 | expect( joined.array() ).toEqual( [45, 1, 2, 3, 1, 2] ); 142 | }); 143 | 144 | it('joins type key', () => 145 | { 146 | const a = 45; 147 | const b = [1, 2, 3]; 148 | // Iterate 149 | const joined = Iterate.join(a, b); 150 | 151 | expect( joined.array() ).toEqual( [45, 1, 2, 3] ); 152 | }); 153 | 154 | it('joins type key source', () => 155 | { 156 | const a = [45]; 157 | const b = [1, 2, 3]; 158 | // Iterate 159 | const joined = Iterate.join(a, b); 160 | 161 | expect( joined.array() ).toEqual( [45, 1, 2, 3] ); 162 | }); 163 | 164 | it('joins iterators', () => 165 | { 166 | const a = iz([45]); 167 | const b = iz([1, 2, 3]); 168 | // Iterate 169 | const joined = Iterate.join(a, b); 170 | 171 | expect( joined.array() ).toEqual( [45, 1, 2, 3] ); 172 | }); 173 | 174 | it('joins any', () => 175 | { 176 | const a = {x: 'hello'}; 177 | const b = [1, 2]; 178 | // Iterate 179 | const joined = Iterate.join(a, b); 180 | 181 | expect( joined.array() ).toEqual( ['hello', 1, 2] ); 182 | }); 183 | 184 | it('zip numbers', () => 185 | { 186 | // Iterate, IterateSourceType> 187 | const a = Iterate.zip([1, 2, 3], ['a', 'b', 'c']); 188 | 189 | const b = a.skip(1).take(1); 190 | 191 | expect( a.entries() ).toEqual([ 192 | [1, 'a'], 193 | [2, 'b'], 194 | [3, 'c'] 195 | ]); 196 | 197 | expect( b.entries() ).toEqual([[2, 'b']]); 198 | }); 199 | 200 | it('zip mixed', () => 201 | { 202 | // Iterate, IterateSourceType]> 203 | const a = Iterate.zip([1, 2, 3], new Set(['a', 'b'])); 204 | 205 | expect( a.entries() ).toEqual([ 206 | [1, 'a'], 207 | [2, 'b'] 208 | ]); 209 | }); 210 | 211 | }); -------------------------------------------------------------------------------- /typings/functions.d.ts: -------------------------------------------------------------------------------- 1 | import { Iterate } from './Iterate'; 2 | import { IterateCompare, IterateEquals, IterateGenerator } from './types'; 3 | /** 4 | * An array of iterate generators. These are checked in order. If you wish to 5 | * add your own you should do: 6 | * 7 | * `Generators.unshift(s => isSourceLogic ? createIterate : false)` 8 | */ 9 | export declare const Generators: IterateGenerator[]; 10 | /** 11 | * A function which is given a value and finds the best way to iterate over it. 12 | * 13 | * If you wish to add your own iterater to be supported you should add it to 14 | * the begging of [[Generators]]. You can then add a custom definition for this 15 | * function in your project so the overloaded function definition is available. 16 | * 17 | * @param s The source to get an iterator for. 18 | */ 19 | export declare function iterate(iterate: Iterate): Iterate; 20 | export declare function iterate(array: T[]): Iterate; 21 | export declare function iterate(set: Set): Iterate>; 22 | export declare function iterate(map: Map): Iterate>; 23 | export declare function iterate(str: string): Iterate; 24 | export declare function iterate>(iterable: I): Iterate; 25 | export declare function iterate; 27 | }>(hasEntries: E): Iterate; 28 | export declare function iterate(object: { 29 | [key: string]: T; 30 | }): Iterate; 33 | export declare function iterate(empty?: null): Iterate; 34 | export declare function iterate(value: T): Iterate; 35 | /** 36 | * Determines whether the given variable is a function. 37 | */ 38 | export declare function isFunction(x: any): x is (...args: any[]) => any; 39 | /** 40 | * A helper comparison function for two unknown variables. If they both 41 | * aren't the correct type the are considered equal. If one doesn't have the 42 | * correct type then the `nullsFirst` variable is used to determine which 43 | * value goes first. If both variables are valid the the given `compare` 44 | * function is used taking into account the `ascending` order option. 45 | * 46 | * @param ascending If valid values should be in ascending order (default). 47 | * @param nullsFirst If invalid values should be ordered first. 48 | * @param a The first value. 49 | * @param b The second value. 50 | * @param correctType Verifies whether a value is valid. 51 | * @param compare Compares two valid values. 52 | */ 53 | export declare function compare(ascending: boolean, nullsFirst: boolean, a: any, b: any, correctType: (x: any) => any, comparator: IterateCompare): number; 54 | /** 55 | * A helper equals function for two unknown variables. If they both aren't 56 | * the correct type they are considered equal. If one doesn't have the 57 | * correct type they are considered unequal. If they both are valid then 58 | * the given equality logic is used. 59 | * 60 | * @param a The first value. 61 | * @param b The second value. 62 | * @param correctType Verifies whether a value is valid. 63 | * @param equals Compares two valid values for equality. 64 | */ 65 | export declare function equals(a: any, b: any, correctType: (x: any) => any, equality: IterateEquals): boolean; 66 | /** 67 | * The default comparison function. If the types are not comparable, an 68 | * error is thrown notifying the user that they need to set their own 69 | * comparator. 70 | * 71 | * @param a The first value to compare. 72 | * @param b The second value. 73 | */ 74 | export declare function defaultCompare(a: any, b: any, forEquals?: boolean): number; 75 | /** 76 | * Creates a number comparator. 77 | * 78 | * @param ascending If the numbers should be in ascending order. 79 | * @param nullsFirst If non-numbers values should be ordered first. 80 | */ 81 | export declare function getNumberComparator(ascending?: boolean, nullsFirst?: boolean): IterateCompare; 82 | /** 83 | * Creates a string comparator. 84 | * 85 | * @param sensitive If equality logic should be case sensitive. 86 | * @param ascending If the strings should be in ascending order. 87 | * @param nullsFirst If non-strings values should be ordered first. 88 | */ 89 | export declare function getStringComparator(sensitive?: boolean, ascending?: boolean, nullsFirst?: boolean): IterateCompare; 90 | /** 91 | * Creates a date comparator. 92 | * 93 | * @param ascending If the dates should be in ascending order (oldest first). 94 | * @param nullsFirst If non-date values should be ordered first. 95 | */ 96 | export declare function getDateComparator(ascending?: boolean, nullsFirst?: boolean): IterateCompare; 97 | /** 98 | * Creates a date equality function. 99 | * 100 | * @param equalityTimespan This defines which timespan to dates can lie on 101 | * to be considered equal. By default they have to have the same 102 | * millisecond. If you want dates equal by the following timespans use 103 | * these values: Second = 1000, Minute = 60000, Hour = 3600000, 104 | * Day = 86400000, Week = 604800000 (approx) 105 | * @param utc If when comparing timespans, if we should look at their UTC 106 | * date/time OR if they should be changed to their relative times based 107 | * on their timezone. 108 | */ 109 | export declare function getDateEquality(equalityTimespan?: number, utc?: boolean): IterateEquals; 110 | -------------------------------------------------------------------------------- /src/functions.ts: -------------------------------------------------------------------------------- 1 | import { Iterate } from './Iterate'; 2 | import { IterateCompare, IterateEquals, IterateGenerator } from './types'; 3 | 4 | 5 | /** 6 | * An array of iterate generators. These are checked in order. If you wish to 7 | * add your own you should do: 8 | * 9 | * `Generators.unshift(s => isSourceLogic ? createIterate : false)` 10 | */ 11 | export const Generators: IterateGenerator[] = [ 12 | (s) => s instanceof Iterate ? s : false, 13 | (s) => Array.isArray(s) ? Iterate.array(s) : false, 14 | (s) => s instanceof Set ? Iterate.set(s) : false, 15 | (s) => s instanceof Map ? Iterate.map(s) : false, 16 | (s) => s && s[Symbol.iterator] ? Iterate.iterable(s) : false, 17 | (s) => s && s.entries ? Iterate.hasEntries(s) : false, 18 | (s) => s === undefined || s === null ? Iterate.empty() : false, 19 | (s) => typeof s === 'object' ? Iterate.object(s) : false, 20 | (s) => Iterate.array([s]) 21 | ]; 22 | 23 | /** 24 | * A function which is given a value and finds the best way to iterate over it. 25 | * 26 | * If you wish to add your own iterater to be supported you should add it to 27 | * the begging of [[Generators]]. You can then add a custom definition for this 28 | * function in your project so the overloaded function definition is available. 29 | * 30 | * @param s The source to get an iterator for. 31 | */ 32 | export function iterate (iterate: Iterate): Iterate 33 | export function iterate (array: T[]): Iterate 34 | export function iterate (set: Set): Iterate> 35 | export function iterate (map: Map): Iterate> 36 | export function iterate (str: string): Iterate 37 | export function iterate> (iterable: I): Iterate 38 | export function iterate }> (hasEntries: E): Iterate 39 | export function iterate (object: { [key: string]: T} ): Iterate 40 | export function iterate (empty?: null): Iterate 41 | export function iterate (value: T): Iterate 42 | export function iterate (s: any): Iterate 43 | { 44 | for (const generator of Generators) 45 | { 46 | const generated = generator(s); 47 | 48 | if (generated !== false) 49 | { 50 | return generated; 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * Determines whether the given variable is a function. 57 | */ 58 | export function isFunction(x: any): x is (...args: any[]) => any 59 | { 60 | return typeof x === 'function'; 61 | } 62 | 63 | /** 64 | * A helper comparison function for two unknown variables. If they both 65 | * aren't the correct type the are considered equal. If one doesn't have the 66 | * correct type then the `nullsFirst` variable is used to determine which 67 | * value goes first. If both variables are valid the the given `compare` 68 | * function is used taking into account the `ascending` order option. 69 | * 70 | * @param ascending If valid values should be in ascending order (default). 71 | * @param nullsFirst If invalid values should be ordered first. 72 | * @param a The first value. 73 | * @param b The second value. 74 | * @param correctType Verifies whether a value is valid. 75 | * @param compare Compares two valid values. 76 | */ 77 | export function compare (ascending: boolean, nullsFirst: boolean, a: any, b: any, correctType: (x: any) => any, comparator: IterateCompare): number 78 | { 79 | const typeA = !correctType(a); 80 | const typeB = !correctType(b); 81 | 82 | if (typeA !== typeB) 83 | { 84 | // One is invalid 85 | return (nullsFirst ? typeA : typeB) ? -1 : 1; 86 | } 87 | else if (typeA) 88 | { 89 | // Both are invalid 90 | return 0; 91 | } 92 | 93 | // Neither are invalid 94 | return ascending ? comparator(a, b) : comparator(b, a); 95 | } 96 | 97 | /** 98 | * A helper equals function for two unknown variables. If they both aren't 99 | * the correct type they are considered equal. If one doesn't have the 100 | * correct type they are considered unequal. If they both are valid then 101 | * the given equality logic is used. 102 | * 103 | * @param a The first value. 104 | * @param b The second value. 105 | * @param correctType Verifies whether a value is valid. 106 | * @param equals Compares two valid values for equality. 107 | */ 108 | export function equals (a: any, b: any, correctType: (x: any) => any, equality: IterateEquals): boolean 109 | { 110 | const typeA = !correctType(a); 111 | const typeB = !correctType(b); 112 | 113 | if (typeA !== typeB) 114 | { 115 | // One is invalid 116 | return false; 117 | } 118 | else if (typeA) 119 | { 120 | // Both are invalid 121 | return true; 122 | } 123 | 124 | // Neither are invalid 125 | return equality(a, b); 126 | } 127 | 128 | /** 129 | * The default comparison function. If the types are not comparable, an 130 | * error is thrown notifying the user that they need to set their own 131 | * comparator. 132 | * 133 | * @param a The first value to compare. 134 | * @param b The second value. 135 | */ 136 | export function defaultCompare (a: any, b: any, forEquals: boolean = false): number 137 | { 138 | const anull = (a === null || a === undefined); 139 | const bnull = (b === null || b === undefined); 140 | 141 | if (anull !== bnull) 142 | { 143 | return anull ? 1 : -1; 144 | } 145 | else if (anull) 146 | { 147 | return 0; 148 | } 149 | 150 | const atype = typeof a; 151 | const btype = typeof b; 152 | 153 | if (atype !== btype) 154 | { 155 | if (forEquals) 156 | { 157 | return -1; 158 | } 159 | 160 | throw new Error('You must implement your own comparator for unknown or mixed values.'); 161 | } 162 | 163 | if (atype === 'string') 164 | { 165 | return a.localeCompare(b); 166 | } 167 | if (atype === 'number') 168 | { 169 | return a - b; 170 | } 171 | if (atype === 'boolean') 172 | { 173 | return (a ? 1 : 0) - (b ? 1 : 0); 174 | } 175 | if (a instanceof Date && b instanceof Date) 176 | { 177 | return a.getTime() - b.getTime(); 178 | } 179 | 180 | if (!forEquals) 181 | { 182 | throw new Error('You must implement your own comparator for unknown or mixed values.'); 183 | } 184 | 185 | if (a instanceof Array) 186 | { 187 | let d = a.length - b.length; 188 | 189 | if (d !== 0) 190 | { 191 | return d; 192 | } 193 | 194 | for (let i = 0; i < a.length; i++) 195 | { 196 | d = defaultCompare(a[i], b[i], forEquals); 197 | 198 | if (d !== 0) 199 | { 200 | return d; 201 | } 202 | } 203 | 204 | return 0; 205 | } 206 | 207 | if (atype === 'object') 208 | { 209 | for (const prop in a) 210 | { 211 | if (!(prop in b)) 212 | { 213 | return -1; 214 | } 215 | } 216 | 217 | for (const prop in b) 218 | { 219 | if (!(prop in a)) 220 | { 221 | return -1; 222 | } 223 | 224 | const d = defaultCompare(a[prop], b[prop], forEquals); 225 | 226 | if (d !== 0) 227 | { 228 | return d; 229 | } 230 | } 231 | } 232 | 233 | return 0; 234 | } 235 | 236 | /** 237 | * Creates a number comparator. 238 | * 239 | * @param ascending If the numbers should be in ascending order. 240 | * @param nullsFirst If non-numbers values should be ordered first. 241 | */ 242 | export function getNumberComparator (ascending: boolean = true, nullsFirst: boolean = false): IterateCompare 243 | { 244 | const isType = (x: any) => typeof x === 'number' && isFinite(x); 245 | const comparator: IterateCompare = (a, b) => a - b; 246 | 247 | return (a, b) => compare(ascending, nullsFirst, a, b, isType, comparator); 248 | } 249 | 250 | /** 251 | * Creates a string comparator. 252 | * 253 | * @param sensitive If equality logic should be case sensitive. 254 | * @param ascending If the strings should be in ascending order. 255 | * @param nullsFirst If non-strings values should be ordered first. 256 | */ 257 | export function getStringComparator (sensitive: boolean = true, ascending: boolean = true, nullsFirst: boolean = false): IterateCompare 258 | { 259 | const isType = (x: any) => typeof x === 'string'; 260 | const comparator: IterateCompare = sensitive 261 | ? (a, b) => a.localeCompare(b) 262 | : (a, b) => a.toLowerCase().localeCompare(b.toLowerCase()) 263 | 264 | return (a, b) => compare(ascending, nullsFirst, a, b, isType, comparator); 265 | } 266 | 267 | /** 268 | * Creates a date comparator. 269 | * 270 | * @param ascending If the dates should be in ascending order (oldest first). 271 | * @param nullsFirst If non-date values should be ordered first. 272 | */ 273 | export function getDateComparator (ascending: boolean = true, nullsFirst: boolean = false): IterateCompare 274 | { 275 | const isType = (x: any) => x instanceof Date; 276 | const comparator: IterateCompare = (a, b) => a.getTime() - b.getTime(); 277 | 278 | return (a, b) => compare(ascending, nullsFirst, a, b, isType, comparator); 279 | } 280 | 281 | /** 282 | * Creates a date equality function. 283 | * 284 | * @param equalityTimespan This defines which timespan to dates can lie on 285 | * to be considered equal. By default they have to have the same 286 | * millisecond. If you want dates equal by the following timespans use 287 | * these values: Second = 1000, Minute = 60000, Hour = 3600000, 288 | * Day = 86400000, Week = 604800000 (approx) 289 | * @param utc If when comparing timespans, if we should look at their UTC 290 | * date/time OR if they should be changed to their relative times based 291 | * on their timezone. 292 | */ 293 | export function getDateEquality (equalityTimespan: number = 1, utc: boolean = true): IterateEquals 294 | { 295 | const MILLIS_IN_MINUTE = 60000; 296 | 297 | const isType = (x: any) => x instanceof Date; 298 | const getTime = utc 299 | ? (a: Date) => a.getTime() 300 | : (a: Date) => a.getTime() + a.getTimezoneOffset() * MILLIS_IN_MINUTE; 301 | const equality: IterateEquals = (a, b) => (getTime(a) % equalityTimespan) === (getTime(b) % equalityTimespan); 302 | 303 | return (a, b) => equals(a, b, isType, equality); 304 | } -------------------------------------------------------------------------------- /dist/iteratez.js: -------------------------------------------------------------------------------- 1 | !function(t,r){"object"==typeof exports&&"object"==typeof module?module.exports=r():"function"==typeof define&&define.amd?define("iteratez",[],r):"object"==typeof exports?exports.iteratez=r():t.iteratez=r()}(this,function(){return function(t){var r={};function e(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,e),o.l=!0,o.exports}return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{enumerable:!0,get:n})},e.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},e.t=function(t,r){if(1&r&&(t=e(t)),8&r)return t;if(4&r&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(e.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&r&&"string"!=typeof t)for(var o in t)e.d(n,o,function(r){return t[r]}.bind(null,o));return n},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,r){return Object.prototype.hasOwnProperty.call(t,r)},e.p="",e(e.s=0)}([function(t,r,e){"use strict";var n;e.r(r),function(t){t[t.CONTINUE=0]="CONTINUE",t[t.STOP=1]="STOP",t[t.REMOVE=2]="REMOVE",t[t.REPLACE=3]="REPLACE"}(n||(n={}));var o=function(t){var r="function"==typeof Symbol&&t[Symbol.iterator],e=0;return r?r.call(t):{next:function(){return t&&e>=t.length&&(t=void 0),{value:t&&t[e++],done:!t}}}},i=function(t,r){var e="function"==typeof Symbol&&t[Symbol.iterator];if(!e)return t;var n,o,i=e.call(t),u=[];try{for(;(void 0===r||r-- >0)&&!(n=i.next()).done;)u.push(n.value)}catch(t){o={error:t}}finally{try{n&&!n.done&&(e=i.return)&&e.call(i)}finally{if(o)throw o.error}}return u},u=function(){for(var t=[],r=0;r0?t:e});return t?(t(e),this):e},t.prototype.changes=function(t,r,e,u){var a,c,s=this;this.history||(this.history=new Map);var f=new Map(this.history);this.each(function(r,o,i){var a=u?u(r,o,i):r;s.history.has(a)?e(r,o,i):(t(r,o,i),i.result!==n.REMOVE&&s.history.set(a,[o,r])),f.delete(a)});try{for(var p=o(f.values()),h=p.next();!h.done;h=p.next()){var l=i(h.value,2),v=l[0];r(l[1],v,this)}}catch(t){a={error:t}}finally{try{h&&!h.done&&(c=p.return)&&c.call(p)}finally{if(a)throw a.error}}return this},t.prototype.delete=function(){return this.each(function(t,r,e){return e.remove()})},t.prototype.extract=function(r){var e=[];this.each(function(t,r,n){return e.push([r,t])&&n.remove()});var n=t.entries(e);return r?(r(n),this):n},t.prototype.overwrite=function(t){return this.each(function(r,e,n){return n.replace(t)})},t.prototype.update=function(t){return this.each(function(r,e,n){return n.replace(t(r,e))})},t.prototype.fork=function(t){return t(this),this},t.prototype.split=function(t,r){var e=this.where(t),n=this.not(t);return r?(r(e,n),this):{pass:e,fail:n}},t.prototype.unzip=function(t){var r=this.keys(),e=this.values();return t?(t(r,e),this):{keys:r,values:e}},t.prototype.keys=function(){var r=this;return new t(function(t){var e=0;r.each(function(r,o,i){switch(t.act(o,e++)){case n.STOP:i.stop();break;case n.REMOVE:i.remove();break;case n.REPLACE:}})}).onReset(this.handleReset)},t.prototype.values=function(){var r=this;return new t(function(t){r.each(function(r,e,o){var i=0;switch(t.act(r,i++)){case n.STOP:o.stop();break;case n.REMOVE:o.remove();break;case n.REPLACE:o.replace(t.replaceWith)}})},this).onReset(this.handleReset)},t.prototype.view=function(r,e,o,i){var u=this;return new t(function(t){var a=r();u.each(function(r,u,c){if(e(a,r,u)){switch(t.act(r,u)){case n.STOP:c.stop();break;case n.REMOVE:c.remove();break;case n.REPLACE:c.replace(t.replaceWith)}o&&o(a,r,u,c)}else i&&i(a,r,u,c)})},this).onReset(this.handleReset)},t.prototype.take=function(r){return r<=0?t.empty():this.view(function(){return{amount:r}},function(t){return t.amount>0},function(t){return t.amount--},function(t,r,e,n){return n.stop()})},t.prototype.skip=function(t){return this.view(function(){return{skipped:0}},function(r){return r.skipped>=t},function(t){return t.skipped++},function(t){return t.skipped++})},t.prototype.drop=function(t){return this.reverse().skip(t).reverse()},t.prototype.append=function(){for(var r=[],e=0;e0})},t.prototype.gte=function(t,r){var e=this;return this.view(function(){return e.getComparator(r)},function(r,e,n){return r(e,t,n)>=0})},t.prototype.lt=function(t,r){var e=this;return this.view(function(){return e.getComparator(r)},function(r,e,n){return r(e,t,n)<0})},t.prototype.lte=function(t,r){var e=this;return this.view(function(){return e.getComparator(r)},function(r,e,n){return r(e,t,n)<=0})},t.prototype.exclude=function(t,r){var e=this;return this.view(function(){return f(t).withEquality(e.getEquality(r))},function(t,r){return!t.contains(r)})},t.prototype.intersect=function(t,r){var e=this;return this.view(function(){return f(t).withEquality(e.getEquality(r))},function(t,r){return t.contains(r)})},t.prototype.unique=function(t){var r=this;return this.view(function(){return{existing:[],isEqual:r.getEquality(t)}},function(t,r,e){var n=t.existing,o=t.isEqual;return-1===n.findIndex(function(t){var n=i(t,2),u=n[0],a=n[1];return o(a,r,u,e)})},function(t,r,e){return t.existing.push([e,r])})},t.prototype.duplicates=function(t,r){var e=this;return void 0===t&&(t=!1),this.view(function(){return{existing:[],once:[],isEqual:e.getEquality(r)}},function(r,e,n){var o=r.existing,u=r.once,a=r.isEqual,c=o.findIndex(function(t){var r=i(t,2),o=r[0],u=r[1];return a(u,e,o,n)}),s=-1!==c;return s?(u[c]&&t&&(s=!1),u[c]=!0):o.push([n,e]),s})},t.prototype.readonly=function(){var r=this;return new t(function(t){r.each(function(r,e,o){t.act(r,e)===n.STOP&&o.stop()})},this).onReset(this.handleReset)},t.prototype.copy=function(){return t.entries(this.entries())},t.prototype.viewResolved=function(r){var e=this;return new t(function(t){var o=e.entries(),i=[],u=[],a=[],c=!1;if(r(o,function(r,e,o){var s=t.act(r,e);return s!==n.REPLACE&&s!==n.REMOVE||(c=!0,a[o]=r,i[o]=s,u[o]=t.replaceWith),s}),c){var s=0;e.each(function(t,r,e){switch(i[s]){case n.REMOVE:t===a[s]&&e.remove();break;case n.REPLACE:t===a[s]&&e.replace(u[s])}s++})}},this).onReset(this.handleReset)},t.prototype.sorted=function(t){var r=this;return this.viewResolved(function(e,u){var a,c,s=r.getComparator(t),f=e.map(function(t,r){var e=i(t,2);return{key:e[0],value:e[1],index:r}});f.sort(function(t,r){return s(t.value,r.value)});try{for(var p=o(f),h=p.next();!h.done;h=p.next()){var l=h.value,v=l.key;if(u(l.value,v,l.index)===n.STOP)return}}catch(t){a={error:t}}finally{try{h&&!h.done&&(c=p.return)&&c.call(p)}finally{if(a)throw a.error}}})},t.prototype.shuffle=function(t){void 0===t&&(t=1);var r=function(t,r,e){var n=t[r];t[r]=t[e],t[e]=n};return this.viewResolved(function(e,o){for(var u=[],a=e.length,c=0;c=0;e--){var o=i(t[e],2),u=o[0];if(r(o[1],u,e)===n.STOP)return}})},t.prototype.transform=function(r,e){var o=this;return void 0===e&&(e=null),new t(function(t){o.each(function(o,i,u){var a=r(o,i,u);if(void 0!==a)switch(t.act(a,i)){case n.STOP:u.stop();break;case n.REMOVE:u.remove();break;case n.REPLACE:e&&u.replace(e(t.replaceWith,a,o,i))}})}).onReset(this.handleReset)},t.prototype.each=function(t){return this.result=void 0,this.callback=t,this.action=n.CONTINUE,this.source(this),this.callback=null,this},t.prototype.withResult=function(t){return this.result&&t(this.result),this},t.prototype[Symbol.iterator]=function(){return this.array().values()},t.entries=function(r){return void 0===r&&(r=[]),new t(function(t){for(var e=0;e=r.length)a.stop();else switch(t.act(o,r[i])){case n.STOP:return;case n.REMOVE:a.remove(),e.push(i);break;case n.REPLACE:a.replace(t.replaceWith)}i++}),e.length>0){var a=0;o.each(function(t,r,n){a===e[0]?(n.remove(),e.shift()):0===e.length&&n.stop(),a++})}}).onReset(function(t){var r=i(t,2),e=r[0],n=r[1];o=f(e),u=f(n)})},t.hasEntries=function(r,e,o){return new t(function(t){for(var u=r.entries(),a=u.next();!a.done;a=u.next()){var c=i(a.value,2),s=c[0],f=c[1];switch(t.act(f,s)){case n.STOP:return;case n.REMOVE:e&&e(r,s,f);break;case n.REPLACE:o&&o(r,s,f,t.replaceWith)}}}).onReset(function(t){return r=t})},t.map=function(r){return void 0===r&&(r=new Map),t.hasEntries(r,function(t,r){return t.delete(r)},function(t,r,e,n){return t.set(r,n)})},t.set=function(r){return void 0===r&&(r=new Set),t.hasEntries(r,function(t,r){return t.delete(r)},function(t,e,n,o){return r.delete(n)&&r.add(o)})},t.iterable=function(r){return new t(function(t){for(var e=r[Symbol.iterator](),o=0,i=e.next();!i.done&&t.act(i.value,o)!==n.STOP;i=e.next(),o++);}).onReset(function(t){return r=t})},t.object=function(r,e){return void 0===e&&(e=!0),new t(function(t){for(var o in r)if(!e||r.hasOwnProperty(o))switch(t.act(r[o],o)){case n.STOP:return;case n.REMOVE:delete r[o];break;case n.REPLACE:r[o]=t.replaceWith}}).onReset(function(t){return r=t})},t.linked=function(r,e,o,i,u){return u||(u=function(t){return r(t)}),function(a,c){return void 0===c&&(c=!0),new t(function(t){for(var s=a,f=e(a);f&&f!==a;){var p=e(f),h=!1;switch(t.act(r(f),u(f))){case n.STOP:return;case n.REMOVE:if(o)o(f,s),h=!0;else if(c)throw new Error("remove is required for linked list iteration");break;case n.REPLACE:if(i)i(f,t.replaceWith);else if(c)throw new Error("replace is required for linked list iteration")}h||(s=f),f=p}}).onReset(function(t){return a=t})}},t.tree=function(r,e,o,i){i||(i=function(t){return r(t)});var u=t.empty(),a=function(r){var n=e(r);return n?Array.isArray(n)?t.array(n):n:u},c=function(t,e,u,a,c){switch(e.act(r(t),i(t))){case n.STOP:return c&&c.stop(),!1;case n.REPLACE:if(o)o(t,e.replaceWith);else if(u)throw new Error("replaceValue is required when replacing a value in a tree");break;case n.REMOVE:if(c)c.remove();else if(u&&a)throw new Error("remove is not supported for breadth-first iteration")}return!0},s=function(t,r,e,n){return!!c(t,r,e,!1,n)&&!a(t).each(function(t,n,o){return s(t,r,e,o)}).isStopped()};return function(r,e,n){return void 0===e&&(e=!0),void 0===n&&(n=!0),new t(function(t){return e?s(r,t,n):function(t,r,e){var n=[];for(n.push(t);n.length>0;){var o=n.shift();if(!c(o,r,e,!0))break;a(o).array(n)}}(r,t,n)}).onReset(function(t){return r=t})}},t.join=function(){for(var r=[],e=0;e=t.length&&(t=void 0),{value:t&&t[e++],done:!t}}}},s=[function(t){return t instanceof a&&t},function(t){return!!Array.isArray(t)&&a.array(t)},function(t){return t instanceof Set&&a.set(t)},function(t){return t instanceof Map&&a.map(t)},function(t){return!(!t||!t[Symbol.iterator])&&a.iterable(t)},function(t){return!(!t||!t.entries)&&a.hasEntries(t)},function(t){return null==t&&a.empty()},function(t){return"object"==typeof t&&a.object(t)},function(t){return a.array([t])}];function f(t){var r,e;try{for(var n=c(s),o=n.next();!o.done;o=n.next()){var i=(0,o.value)(t);if(!1!==i)return i}}catch(t){r={error:t}}finally{try{o&&!o.done&&(e=n.return)&&e.call(n)}finally{if(r)throw r.error}}}function p(t){return"function"==typeof t}function h(t,r,e,n,o,i){var u=!o(e),a=!o(n);return u!==a?(r?u:a)?-1:1:u?0:t?i(e,n):i(n,e)}function l(t,r,e,n){var o=!e(t);return o===!e(r)&&(!!o||n(t,r))}function v(t,r,e){void 0===e&&(e=!1);var n=null==t;if(n!==(null==r))return n?1:-1;if(n)return 0;var o=typeof t;if(o!==typeof r){if(e)return-1;throw new Error("You must implement your own comparator for unknown or mixed values.")}if("string"===o)return t.localeCompare(r);if("number"===o)return t-r;if("boolean"===o)return(t?1:0)-(r?1:0);if(t instanceof Date&&r instanceof Date)return t.getTime()-r.getTime();if(!e)throw new Error("You must implement your own comparator for unknown or mixed values.");if(t instanceof Array){if(0!==(a=t.length-r.length))return a;for(var i=0;i 126 | let source = iz(new Set()); // Set 127 | let source = iz(ReadonlyArray | ReadonlyMap | ReadonlySet | Int8Array | ...) // something that has entries() function 128 | let source = Iterate.tree( ... )(head); 129 | let source = Iterate.linked( ... )(head); 130 | let source = yourSource.yourIteratorGenerator(); 131 | 132 | // ============ ITERATION ============ 133 | 134 | // Stop 135 | source.each((value, key, iter) => { 136 | if (someCondition(value)) { 137 | iter.stop(42) 138 | } 139 | }).withResult((result) => { 140 | // result = 42 141 | // function only called with previous iteration stopped with a value 142 | }); 143 | 144 | // Remove 145 | // - if the source is a sequential collection, it's removed from the sequence (array, object, etc) 146 | // - if the source is a tree, it removes it from the tree including it's children 147 | // - otherwise, up to the custom source 148 | source.each((value, key, iter) => { 149 | if (someCondition(value)) { 150 | iter.remove(); 151 | } 152 | }); 153 | 154 | // Replace 155 | source.each((value, key, iter) => { 156 | if (someCondition(value)) { 157 | iter.replace(replacement); 158 | } 159 | }); 160 | 161 | // ============ Operations ============ 162 | // These are at the end of a chain of views 163 | 164 | let empty = source.empty(); // boolean 165 | let has = source.has(); // boolean 166 | let contains = source.contains(2); // boolean 167 | let first = source.first(); // T 168 | let last = source.last(); // T 169 | let count = source.count(); // number 170 | let array = source.array(); // T[] 171 | let array = source.array(dest); // T[] 172 | let set = source.set(); // Set 173 | let object = source.object(value => value.id); // { [value.id]: value } 174 | let object = source.object(value => value.id, dest); 175 | let entries = source.entries(): // Array<[K, T]> 176 | let map = source.map(); // Map 177 | let group = source.group(value => value.age); // { [age]: T[] } 178 | let reduced = source.reduce(R, (T, R) => R); // R 179 | let min = source.min(); // T 180 | let max = source.max(); // T 181 | let copy = source.copy(): // Iterate 182 | let that = source.changes(onAdd, onRemove, onPresent); // this 183 | 184 | // ============ Mutations ============ 185 | // These are at the end of a chain of views and they 186 | // take the values in the current iterator and affects the 187 | // underlying source. 188 | 189 | source.delete(); // removes all values in iterator 190 | source.where(x => x.id).delete(); // remove values without an ID 191 | 192 | source.extract(); // does a delete and returns a new iterator with the removed values 193 | 194 | source.overwrite(42); // replaces all values in iterator 195 | source.where(x => x > 34).overwrite(12); // replace all numbers over 34 with 12 196 | 197 | source.update(x => x * 2); // multiply all numbers by 2 198 | 199 | // ============ Views ============ 200 | // These are chainable, at the end if you call an operation it performs 201 | // it only on the values in the iterator at that point. If you call 202 | // a mutation then it changes the underlying source but only on the 203 | // values in the view. 204 | 205 | source.where(x => x.age > 0); // values that past test 206 | source.not(x => x.age > 0); // values that don't pass test 207 | source.transform(x => x.name); // values transformed to a new type 208 | source.reverse(); // values in reverse 209 | source.exclude(anotherSource); // not shared values 210 | source.intersect(anotherSource); // shared values 211 | source.sorted(comparator?); // sorted by a comparator 212 | source.shuffle(times?); // randomly orders 213 | source.unique(equality?); // unique values only 214 | source.duplicates(onlyOnce?); // duplicate values only 215 | source.readonly(); // all subsequent mutations are ignored 216 | source.keys(); // just the keys (index based), delete mutation works 217 | source.values(); // just the values (index based) 218 | source.take(10); // first 10 values 219 | source.skip(5); // after first 5 values 220 | source.drop(3); // ignore last 3 221 | source.append(anotherSource); // union of two 222 | source.prepend(anotherSource); // union in reverse order 223 | source.gt(value, comparator?); // all values greater than value 224 | source.gte(value, comparator?); // all values greater/equal to value 225 | source.lt(value, comparator?); // all values less than value 226 | source.lte(value, comparator?); // all values less/equal to value 227 | source.fork(f => f.where(x => !!x.male).delete()); // fork operation 228 | source.split(x => x.male); // { pass, fail } 229 | source.split(x => x.male, (pass, fail) => {}): // two iterators 230 | source.unzip(); // { keys, values } 231 | source.unzip((keys, values) => {}); // two iterators 232 | 233 | // ============ Logic ============ 234 | 235 | // comparator is used for max/min/sorted/gt/gte/lt/lte 236 | // also will set withEquality if not specified 237 | source.withComparator((a, b) => number); 238 | 239 | // equality check used for contains/exclude/intersect/unique/duplicates 240 | source.withEquality((a, b) => boolean); 241 | 242 | // Pre-defined logic 243 | source.numbers(ascending?, nullsFirst?); // number logic 244 | source.strings(sensitive?, ascending?, nullsFirst?); // string logic 245 | source.dates(equalityTimespan?, utc?, ascending?, nullsFirst?); // date logic 246 | source.desc(); // reverse comparison logic 247 | 248 | // ============ Reset ============ 249 | source.reset(['a', 'new', 'source', 'to', 'iterate']); 250 | 251 | // ============ Examples ============ 252 | // Views ending with an operation or mutation. 253 | 254 | source.duplicates().has(); // has duplicates? 255 | source.duplicates().delete(); // remove duplicates 256 | source.where(x => x.age < 18).extract(); // remove < 18yo 257 | source.sorted().skip(5).take(10).array(); // sort, get 5->15 as array 258 | 259 | // Map to a new iterator, but support replacement 260 | source.transform( 261 | // transforms values to new type 262 | value => value.name, 263 | // if replace is called un a subsequent iteration, how do we take the transformed value and apply it back to the original value? 264 | (replaceWith, current, value) => { 265 | value.name = replaceWith; 266 | } 267 | ).each((name, key, iter) => { 268 | // Make all names uppercase in the most obtuse way possible 269 | iter.replace(name.toUpperCase()); 270 | }); 271 | 272 | // Iterate with a callback 273 | source.each((value, key, iter) => { 274 | // iter.remove(); 275 | // iter.stop(withResult); 276 | // iter.replace(newValue); 277 | }); 278 | 279 | 280 | // ============ Linked List ============ 281 | // You can have any structure you wish 282 | interface Node { 283 | value: T 284 | next?: Node 285 | } 286 | 287 | const linkedIterator = Iterate.linked, string>( 288 | // get value from a node 289 | (node) => node.value, 290 | // get next node 291 | (node) => node.next, 292 | // remove the node (optional) 293 | (node, prev) => prev.next = node.next, 294 | // replace value (optional) 295 | (node, value) => node.value = value, 296 | // if you want a key different than the value, specify this 297 | (node) => node.value.length 298 | ); 299 | 300 | const head: Node = ...; 301 | 302 | // for all nodes which have a value that passes the regex... 303 | // - sort them by value (asending), remove the top 5 304 | // - convert the unremoved unsorted nodes into an array 305 | linkedIterator(head) 306 | .where(x => /regex/.test(x)) 307 | .fork(f => f.strings().sorted().take(5).delete()) 308 | .array(); 309 | 310 | 311 | // ============ Tree ============ 312 | interface Node { 313 | value: T 314 | children?: Node[] 315 | } 316 | 317 | // You create an iterator ready for nodes. 318 | const treeIterator = Iterate.tree, string>( 319 | // get a value from a node 320 | (node) => node.value, 321 | // get children from a node (can return an array, another iterator, undefined, or null) 322 | (node) => node.children, 323 | // if replace is called, apply the value (this is optional) 324 | (node, value) => node.value = value 325 | ); 326 | 327 | const head: Node = ... 328 | 329 | // Iterate depth-first and convert to an array 330 | const depthFirstList = treeIterator(head).array(); 331 | 332 | // Iterate breadth-first and convert to an array 333 | const breadthFirstList = treeIterator(head, false).array(); 334 | 335 | ``` 336 | 337 | ## Reusable Function 338 | You can define a function which takes an iterator and performs any 339 | number of operations. You can optionally have it return a result. 340 | 341 | ```typescript 342 | // Iterate.func 343 | // - T = value type 344 | // - R = function return type 345 | // - A = array of parameter types 346 | // - K = desired key type (restricts the source that can be passed to the function) 347 | // - S = desired source (type passed to function must match this type) 348 | 349 | // A function without a result. If a word contains an a, uppercase the word 350 | const fn = Iterate.func( 351 | source => source 352 | .where(x => x.indexOf('a') !== -1) 353 | .update(x => x.toUpperCase()) 354 | ); 355 | 356 | // Any iterable source that has strings as values can be passed to function 357 | const a = ['apple', 'bit', 'cat']; 358 | fn(a); 359 | // a = [APPLE, bit, CAT] 360 | 361 | // A function with a result. If a word contains an a, uppercase it. Return the 362 | // number of changed words. The counting has to happen before the update 363 | // since the update would make no values pass the where condition. 364 | const fn = Iterate.func( 365 | (source, setResult) => source 366 | .where(x => x.indexOf('a') !== -1) 367 | .count(setResult) 368 | .update(x => x.toUpperCase()) 369 | ); 370 | 371 | const a = ['apple', 'bit', 'cat']; 372 | const b = fn(a); // 2 373 | // a = [APPLE, bit, CAT] 374 | 375 | // A function can have special paramters passed to it. 376 | // Given an array of people, I want a subset of that array given 377 | // a limit and offset. 378 | const getPage = Iterate.func( 379 | (source, setResult, offset, limit) => source 380 | .skip(offset) 381 | .take(limit) 382 | .array(setResult) 383 | ); 384 | 385 | const persons: Person[] = ...; 386 | const page = getPage(persons, 5, 10); 387 | // page = at most 10 people starting at index 5 388 | ``` 389 | 390 | ## Custom Iterators 391 | You can add your own iterators to pick up your own types. If you are not using 392 | TypeScript you can ignore and remove the types. 393 | 394 | ```typescript 395 | // Lets assume we have a type which is a [Date, number] tuple 396 | // which represents a range of dates. 397 | 398 | type DateRange = [Date, number]; 399 | 400 | // First we create an iterator given the Range 401 | // The generic arguments for Iterate represent: 402 | // - T = the value being iterated 403 | // - K = the key type 404 | // - S = the source type being iterated 405 | function getDateIterator ([start, max]: DateRange) 406 | { 407 | return new Iterate(iter => 408 | { 409 | // This function is called when an operation or mutation is called on iter 410 | // You should iterate over your values and respond to the action requested 411 | // In this example, if iterateNode returns false, stop all iteration 412 | const curr = new Date(start.getTime()); 413 | 414 | for (let key = 0; key < max; key++) 415 | { 416 | // Dates are not immutable, don't want the iterator to mess this up. 417 | const value = new Date(curr.getTime()); 418 | 419 | switch (iter.act(value, key)) 420 | { 421 | // stop all iteration 422 | case IterateAction.STOP: 423 | return; 424 | 425 | // remove this value, and subsequentally all children from tree 426 | case IterateAction.REMOVE: 427 | // doesn't apply here, this is a dynamic set 428 | break; 429 | 430 | // replace the value 431 | case IterateAction.REPLACE: 432 | // doesn't apply here, this is a dynamic set 433 | break; 434 | } 435 | 436 | // the next date in the sequence 437 | curr.setDate(curr.getDate() + 1); 438 | } 439 | }); 440 | } 441 | 442 | // Now that we have an iterator generator, if we wanted to we could 443 | // autmatically have the library detect my custom type and call my custom 444 | // iterator. 445 | import { Generators } from 'iteratez'; 446 | 447 | // We need to detect our custom type. s is DateRange is just syntactical sugar 448 | function isDateRange(s: any): s is DateRange { 449 | return Array.isArray(s) 450 | && s.length === 2 451 | && s[0] instanceof Date 452 | && typeof s[1] === 'number' 453 | ; 454 | } 455 | 456 | // Add a generator detection to the beginning of the list 457 | // It must return false if it's not a valid source. 458 | Generators.unshift(s => isDateRange(s) ? getDateIterator(s) : false); 459 | 460 | // Now when we do this... 461 | const dateIterator = iz([new Date(), 10]); 462 | 463 | // We have our iterator and can do anything we want with it. 464 | const dates = dateIterator.array(); 465 | 466 | // BONUS! 467 | // If we want the iz function to return a type Iterator (like Iterate) 468 | // we can add our own declaration file like this: 469 | 470 | // 471 | import { Iterate } from 'iteratez'; 472 | 473 | declare module 'iteratez' 474 | { 475 | // add the function overload 476 | export function iterate (range: DateRange): Iterate 477 | } 478 | // 479 | 480 | // then instead of this everywhere: 481 | import { iz } from 'iteratez'; 482 | 483 | const dateIterator = iz([new Date(), 3]); // Iterate magic! 484 | ``` 485 | -------------------------------------------------------------------------------- /src/__tests__/Iterator.spec.ts: -------------------------------------------------------------------------------- 1 | // import { describe, it, expect } from 'jest'; 2 | import { Iterate } from '../Iterate'; 3 | 4 | // tslint:disable: no-magic-numbers 5 | 6 | describe('Iterate', () => { 7 | 8 | it('remove', () => 9 | { 10 | const a: number[] = [1, 2, 3, 4]; 11 | 12 | Iterate.array( a ) 13 | .each((n, k, iterator) => { 14 | if (n % 2 === 0) { 15 | iterator.remove(); 16 | } 17 | }) 18 | ; 19 | 20 | expect( a ).toEqual( [1, 3] ); 21 | }); 22 | 23 | it('reset', () => 24 | { 25 | const a = [1, 2, 3]; 26 | const b = [4, 5, 6, 7, 8]; 27 | const c = Iterate.array(a); 28 | 29 | expect( c.count() ).toEqual( 3 ); 30 | 31 | expect( c.reset( b ).count() ).toEqual( 5 ); 32 | 33 | c.skip(3).delete(); 34 | 35 | expect( a ).toEqual( [1, 2, 3] ); 36 | expect( b ).toEqual( [4, 5, 6] ); 37 | }); 38 | 39 | it('changes', () => 40 | { 41 | const isEven = (x: number) => x % 2 === 0; 42 | const a = [1, 2, 3, 4, 5, 6]; 43 | 44 | const c = Iterate.array(a).where(isEven); 45 | 46 | const added0: number[] = []; 47 | const removed0: number[] = []; 48 | const present0: number[] = []; 49 | 50 | c.changes( 51 | x => added0.push(x), 52 | x => removed0.push(x), 53 | x => present0.push(x) 54 | ); 55 | 56 | expect( added0 ).toEqual( [2, 4, 6] ); 57 | expect( removed0 ).toEqual( [] ); 58 | expect( present0 ).toEqual( [] ); 59 | 60 | a.splice(1, 1); 61 | a.push(8); 62 | 63 | const added1: number[] = []; 64 | const removed1: number[] = []; 65 | const present1: number[] = []; 66 | 67 | c.changes( 68 | x => added1.push(x), 69 | x => removed1.push(x), 70 | x => present1.push(x) 71 | ); 72 | 73 | expect( added1 ).toEqual( [8] ); 74 | expect( removed1 ).toEqual( [2] ); 75 | expect( present1 ).toEqual( [4, 6] ); 76 | }); 77 | 78 | it('empty', () => 79 | { 80 | expect( Iterate.array( [] ).empty() ).toBeTruthy(); 81 | expect( Iterate.array( [1, 2] ).empty() ).toBeFalsy(); 82 | expect( Iterate.object( {} ).empty() ).toBeTruthy(); 83 | expect( Iterate.object( {x: 2} ).empty() ).toBeFalsy(); 84 | }) 85 | 86 | it('empty where', () => 87 | { 88 | const isEven = (x: number) => x % 2 === 0; 89 | 90 | expect( Iterate.array( [] ).where( isEven ).empty() ).toBeTruthy(); 91 | expect( Iterate.array( [1, 3] ).where( isEven ).empty() ).toBeTruthy(); 92 | expect( Iterate.array( [1, 2] ).where( isEven ).empty() ).toBeFalsy(); 93 | expect( Iterate.object( {} ).where( isEven ).empty() ).toBeTruthy(); 94 | expect( Iterate.object( {x: 1} ).where( isEven ).empty() ).toBeTruthy(); 95 | expect( Iterate.object( {x: 1, y: 2} ).where( isEven ).empty() ).toBeFalsy(); 96 | expect( Iterate.object( {x: 1, y: 3} ).where( isEven ).empty() ).toBeTruthy(); 97 | }) 98 | 99 | it('has', () => 100 | { 101 | expect( Iterate.array([]).has() ).toBeFalsy(); 102 | expect( Iterate.empty().has() ).toBeFalsy(); 103 | expect( Iterate.array([1]).has() ).toBeTruthy(); 104 | expect( Iterate.object({}).has() ).toBeFalsy(); 105 | expect( Iterate.object({x: true}).has() ).toBeTruthy(); 106 | }); 107 | 108 | it('has where', () => 109 | { 110 | const isEven = (x: number) => x % 2 === 0; 111 | 112 | expect( Iterate.array([1]).where(isEven).has() ).toBeFalsy(); 113 | expect( Iterate.array([1, 2]).where(isEven).has() ).toBeTruthy(); 114 | expect( Iterate.object({x: 1 }).where(isEven).has() ).toBeFalsy(); 115 | expect( Iterate.object({x: 1, y: 2}).where(isEven).has() ).toBeTruthy(); 116 | }); 117 | 118 | it('hasDuplicates', () => 119 | { 120 | expect( Iterate.array([]).duplicates().has() ).toBeFalsy(); 121 | expect( Iterate.object({}).duplicates().has() ).toBeFalsy(); 122 | expect( Iterate.empty().duplicates().has() ).toBeFalsy(); 123 | expect( Iterate.array([1, 2, 3, 4, 5]).duplicates().has() ).toBeFalsy(); 124 | expect( Iterate.array([1, 2, 3, 4, 5, 1]).duplicates().has() ).toBeTruthy(); 125 | expect( Iterate.array([1, 1]).duplicates().has() ).toBeTruthy(); 126 | expect( Iterate.array([0, 1, 1]).duplicates().has() ).toBeTruthy(); 127 | }); 128 | 129 | it('contains', () => 130 | { 131 | expect( Iterate.array([1, 2, 3, 4]).contains(1) ).toBeTruthy(); 132 | expect( Iterate.array([1, 2, 3, 4]).contains(5) ).toBeFalsy(); 133 | }); 134 | 135 | it('first', () => 136 | { 137 | expect( Iterate.array([1, 2, 3]).first() ).toEqual(1); 138 | expect( Iterate.array([1, 2, 3]).reverse().first() ).toEqual(3); 139 | expect( Iterate.array([2, 3, 1]).numbers().sorted().first() ).toEqual(1); 140 | }); 141 | 142 | it('last', () => 143 | { 144 | expect( Iterate.array([1, 2, 3]).last() ).toEqual(3); 145 | expect( Iterate.array([1, 2, 3]).reverse().last() ).toEqual(1); 146 | expect( Iterate.array([2, 3, 1]).numbers().sorted().last() ).toEqual(3); 147 | }); 148 | 149 | it('count', () => 150 | { 151 | expect( Iterate.array([1, 2, 3]).count() ).toEqual(3); 152 | expect( Iterate.array([1, 2, 3]).where(n => n % 2 === 0).count() ).toEqual(1); 153 | }); 154 | 155 | it('min', () => 156 | { 157 | expect( Iterate.array([1, 0, 3, 2]).numbers().min() ).toEqual(0); 158 | expect( Iterate.array([1, 0, 3, 2]).numbers().desc().min() ).toEqual(3); 159 | }); 160 | 161 | it('max', () => 162 | { 163 | expect( Iterate.array([1, 0, 3, 2]).numbers().max() ).toEqual(3); 164 | expect( Iterate.array([1, 0, 3, 2]).numbers().desc().max() ).toEqual(0); 165 | }); 166 | 167 | it('numbers', () => 168 | { 169 | const a = Iterate.array(['a', 1, 3, null, undefined, 6, 4]); 170 | 171 | expect( a.numbers(true, true).sorted().array() ).toEqual( ['a', null, undefined, 1, 3, 4, 6] ); 172 | expect( a.numbers(false, true).sorted().array() ).toEqual( ['a', null, undefined, 6, 4, 3, 1] ); 173 | expect( a.numbers(true, false).sorted().array() ).toEqual( [1, 3, 4, 6, 'a', null, undefined] ); 174 | expect( a.numbers(false, false).sorted().array() ).toEqual( [6, 4, 3, 1, 'a', null, undefined] ); 175 | }); 176 | 177 | it('strings', () => 178 | { 179 | const a = Iterate.array([1, 'a', 'c', null, undefined, 'g', 'e']); 180 | 181 | expect( a.strings(true, true, true).sorted().array() ).toEqual( [1, null, undefined, 'a', 'c', 'e', 'g'] ); 182 | expect( a.strings(true, false, true).sorted().array() ).toEqual( [1, null, undefined, 'g', 'e', 'c', 'a'] ); 183 | expect( a.strings(true, true, false).sorted().array() ).toEqual( ['a', 'c', 'e', 'g', 1, null, undefined] ); 184 | expect( a.strings(true, false, false).sorted().array() ).toEqual( ['g', 'e', 'c', 'a', 1, null, undefined] ); 185 | }); 186 | 187 | it('delete', () => 188 | { 189 | const a = Iterate.array([1, 2, 3, 4]); 190 | a.delete(); 191 | expect( a.array() ).toEqual( [] ); 192 | 193 | const b = Iterate.array([1, 2, 0, 3]); 194 | b.where(x => x % 2 === 0).delete(); 195 | expect (b.array() ).toEqual( [1, 3] ); 196 | 197 | const c = Iterate.array([1, 1, 2, 3, 4, 1]); 198 | c.duplicates(false).delete(); 199 | expect( c.array() ).toEqual( [1, 2, 3, 4] ); 200 | }); 201 | 202 | it('overwrite', () => 203 | { 204 | const a = Iterate.array([1, 2, 3, 4]); 205 | a.overwrite(0); 206 | expect( a.array() ).toEqual( [0, 0, 0, 0] ); 207 | 208 | const b = Iterate.array([1, 2, 0, 3]); 209 | b.where(x => x % 2 === 0).overwrite(0); 210 | expect (b.array() ).toEqual( [1, 0, 0, 3] ); 211 | 212 | const c = Iterate.array([1, 1, 2, 3, 4, 1]); 213 | c.duplicates(false).overwrite(0); 214 | expect( c.array() ).toEqual( [1, 0, 2, 3, 4, 0] ); 215 | }); 216 | 217 | it('update', () => 218 | { 219 | const a = ['Apple', 'Banana']; 220 | 221 | Iterate.array(a).update(x => x.toLowerCase()) 222 | 223 | expect( a ).toEqual( ['apple', 'banana'] ); 224 | }); 225 | 226 | it('reverse', () => 227 | { 228 | const a: number[] = [1, 2, 3, 4]; 229 | const out: number[] = Iterate.array( a ).reverse().array(); 230 | 231 | expect( out ).toEqual( [4, 3, 2, 1] ); 232 | }) 233 | 234 | it('reduce', () => 235 | { 236 | const a: number[] = [1, 2, 3, 4, 5]; 237 | const sum: number = Iterate.array( a ).reduce( 0, (value, current) => current + value ); 238 | 239 | expect( sum ).toBe( 15 ); 240 | }) 241 | 242 | it('where', () => 243 | { 244 | const a: number[] = [1, 2, 3, 4]; 245 | const isEven = (x: number) => x % 2 === 0; 246 | const even: number[] = Iterate.array( a ).where( isEven ).array(); 247 | 248 | expect( even ).toEqual( [2, 4] ); 249 | }) 250 | 251 | it('not', () => 252 | { 253 | const a: number[] = [1, 2, 3, 4]; 254 | const isEven = (x: number) => x % 2 === 0; 255 | const even: number[] = Iterate.array( a ).not( isEven ).array(); 256 | 257 | expect( even ).toEqual( [1, 3] ); 258 | }) 259 | 260 | it('transform', () => 261 | { 262 | const a: number[] = [1, 2, 3, 4]; 263 | const transformed: string[] = Iterate.array( a ) 264 | .transform(value => 'x' + value) 265 | .array(); 266 | 267 | expect( transformed ).toEqual( ['x1', 'x2', 'x3', 'x4'] ); 268 | }) 269 | 270 | it('join', () => 271 | { 272 | const a: number[] = [1, 2, 3]; 273 | const b = {x: 4, y: 5, z: 6}; 274 | const c: number[] = Iterate.join( Iterate.array( a ), Iterate.object( b ) ).array(); 275 | 276 | expect( c ).toEqual( [1, 2, 3, 4, 5, 6] ); 277 | }) 278 | 279 | it('skip', () => 280 | { 281 | const a: number[] = [1, 2, 3, 4, 5]; 282 | const b: number[] = Iterate.array( a ).skip( 2 ).array(); 283 | 284 | expect( b ).toEqual( [3, 4, 5] ); 285 | 286 | expect( Iterate.array([1, 5, 2, 3, 4, 6]).where(x => x % 2).skip(1).array() ).toEqual( [5, 3] ); 287 | }) 288 | 289 | it('take', () => 290 | { 291 | const a: number[] = [1, 2, 3, 4]; 292 | const b: number[] = Iterate.array( a ).take( 3 ).array(); 293 | 294 | expect( b ).toEqual( [1, 2, 3] ); 295 | 296 | expect( Iterate.array([1, 5, 2, 3, 4]).numbers().sorted().take(3).array() ).toEqual( [1, 2, 3] ); 297 | }) 298 | 299 | it('drop', () => 300 | { 301 | const a: number[] = [1, 2, 3, 4]; 302 | const b: number[] = [1, 2, 3, 4, 5, 6]; 303 | 304 | expect( Iterate.array(a).drop(2).array() ).toEqual([1, 2]); 305 | 306 | expect( Iterate.array(b).drop(2).array() ).toEqual([1, 2, 3, 4]); 307 | }); 308 | 309 | it('skip take', () => 310 | { 311 | const a: number[] = [1, 2, 3, 4, 5, 6]; 312 | const b: number[] = Iterate.array( a ).skip( 2 ).take( 2 ).array(); 313 | 314 | expect( b ).toEqual( [3, 4] ); 315 | }) 316 | 317 | it('skip take append', () => 318 | { 319 | const a: number[] = [1, 2, 3, 4, 5]; 320 | const iter = Iterate.array( a ); 321 | const b: number[] = iter.skip( 3 ).append(iter.take( 1 )).array(); 322 | 323 | expect( b ).toEqual( [4, 5, 1] ); 324 | }) 325 | 326 | it('skip take prepend', () => 327 | { 328 | const a: number[] = [1, 2, 3, 4, 5]; 329 | const iter = Iterate.array( a ); 330 | const b: number[] = iter.skip( 3 ).prepend(iter.take( 1 )).array(); 331 | 332 | expect( b ).toEqual( [1, 4, 5] ); 333 | }) 334 | 335 | it('for...of', () => 336 | { 337 | const iter = Iterate.array([1, 2, 3, 4, 5]); 338 | const iterated = []; 339 | 340 | const iterable = iter[Symbol.iterator](); 341 | 342 | for (let next = iterable.next(); !next.done; next = iterable.next()) 343 | { 344 | iterated.push(next.value); 345 | } 346 | 347 | /* Add back once Visual Studio TS version matches project 348 | for (const num of iter) { 349 | iterated.push(num); 350 | } 351 | */ 352 | 353 | expect(iterated).toEqual([1, 2, 3, 4, 5]); 354 | }); 355 | 356 | it('gt', () => 357 | { 358 | expect( Iterate.array([1, 2, 3, 4, 5]).numbers().gt(3).array() ).toEqual( [4, 5] ); 359 | }); 360 | 361 | it('gte', () => 362 | { 363 | expect( Iterate.array([1, 2, 3, 4, 5]).numbers().gte(3).array() ).toEqual( [3, 4, 5] ); 364 | }); 365 | 366 | it('lt', () => 367 | { 368 | expect( Iterate.array([1, 2, 3, 4, 5]).numbers().lt(3).array() ).toEqual( [1, 2] ); 369 | }); 370 | 371 | it('lte', () => 372 | { 373 | expect( Iterate.array([1, 2, 3, 4, 5]).numbers().lte(3).array() ).toEqual( [1, 2, 3] ); 374 | }); 375 | 376 | it('exclude', () => 377 | { 378 | expect( Iterate.array([1, 2, 3, 4]).exclude(Iterate.array([2, 4])).array() ).toEqual( [1, 3] ); 379 | }); 380 | 381 | it('intersect', () => 382 | { 383 | expect( Iterate.array([1, 2, 3, 4]).intersect(Iterate.array([2, 4, 5])).array() ).toEqual( [2, 4] ); 384 | }); 385 | 386 | it('unique', () => 387 | { 388 | expect( Iterate.array([1, 2, 3, 2, 1, 4, 2, 5]).unique().array() ).toEqual( [1, 2, 3, 4, 5] ); 389 | }); 390 | 391 | it('sorted', () => 392 | { 393 | const a = Iterate.array([1, 6, 2, 3, 8, 7, 0, 3]); 394 | const aSorted = a.numbers().sorted(); 395 | 396 | expect( aSorted.array() ).toEqual( [0, 1, 2, 3, 3, 6, 7, 8] ); 397 | expect( aSorted.take(3).array() ).toEqual( [0, 1, 2] ); 398 | aSorted.take(3).delete(); 399 | expect( a.array() ).toEqual( [6, 3, 8, 7, 3] ); 400 | 401 | const b = Iterate.array([1, 6, 2, 3, 8, 7, 0, 3, 7, 6]); 402 | b.numbers().sorted().skip(5).overwrite(0); 403 | expect( b.array() ).toEqual( [1, 0, 2, 3, 0, 0, 0, 3, 0, 0] ); 404 | }); 405 | 406 | it('shuffle', () => 407 | { 408 | const a = [1, 2, 3, 4, 5, 6, 7]; 409 | const b = Iterate.array(a); 410 | 411 | expect( b.shuffle().array() ).not.toEqual(a); 412 | }) 413 | 414 | it('fork', () => 415 | { 416 | const a = Iterate.array([1, 2, 3, 4, 5, 6, 7, 8]); 417 | const b: number[] = []; 418 | 419 | a.where(x => x > 3) 420 | .fork(f => f.where(x => x % 3 === 0).delete() ) 421 | .fork(f => f.where(x => x % 3 === 2).delete() ) 422 | .array(b); 423 | 424 | expect( a.array() ).toEqual( [1, 2, 3, 4, 7] ); 425 | expect( b ).toEqual( [4, 7] ); 426 | }); 427 | 428 | it('split', () => 429 | { 430 | const a = Iterate.array([1, 2, 3, 4, 5, 6, 7, 8]); 431 | const b: number[] = []; 432 | const c: number[] = []; 433 | 434 | a.split(x => x % 2 === 0, (p, f) => { 435 | p.array(b); 436 | f.array(c); 437 | }); 438 | 439 | expect( b ).toEqual( [2, 4, 6, 8] ); 440 | expect( c ).toEqual( [1, 3, 5, 7] ); 441 | 442 | const { pass, fail } = a.split(x => x % 2 === 0); 443 | 444 | expect( pass.array() ).toEqual( [2, 4, 6, 8] ); 445 | expect( fail.array() ).toEqual( [1, 3, 5, 7] ); 446 | }); 447 | 448 | it('unzip', () => 449 | { 450 | const a = Iterate.object({ 451 | a: 1, 452 | b: 2, 453 | c: 3, 454 | d: 4 455 | }); 456 | const b: string[] = []; 457 | const c: number[] = []; 458 | 459 | a.unzip((k, v) => k.array(b) && v.array(c)); 460 | 461 | expect( b ).toEqual( ['a', 'b', 'c', 'd'] ); 462 | expect( c ).toEqual( [1, 2, 3, 4] ); 463 | 464 | const { keys, values } = a.unzip(); 465 | 466 | expect( keys.array() ).toEqual( ['a', 'b', 'c', 'd'] ); 467 | expect( values.array() ).toEqual( [1, 2, 3, 4] ); 468 | 469 | a.unzip((k, v) => { 470 | k.where(x => x === 'b').delete(); 471 | v.where(x => x === 3).delete(); 472 | }); 473 | 474 | expect( a.entries() ).toEqual( [['a', 1], ['d', 4]] ); 475 | }); 476 | 477 | it('zip', () => 478 | { 479 | const zip1 = Iterate.zip(Iterate.array([1, 2, 3]), Iterate.array(['a', 'b', 'c'])); 480 | 481 | expect( zip1.entries() ).toEqual( [[1, 'a'], [2, 'b'], [3, 'c']] ); 482 | 483 | const zip2 = Iterate.zip(Iterate.array([1, 2, 3]), Iterate.array(['a', 'b'])); 484 | 485 | expect( zip2.entries() ).toEqual( [[1, 'a'], [2, 'b']] ); 486 | 487 | const zip3 = Iterate.zip(Iterate.array([1, 2]), Iterate.array(['a', 'b', 'c'])); 488 | 489 | expect( zip3.entries() ).toEqual( [[1, 'a'], [2, 'b']] ); 490 | 491 | const keys = Iterate.array([1, 2, 3, 4, 5, 6]); 492 | const vals = Iterate.array(['a', 'b', 'c', 'd', 'e', 'f']); 493 | const zipped = Iterate.zip(keys, vals); 494 | 495 | zipped.where((value, key) => key % 2 === 0).delete(); 496 | 497 | expect( keys.array() ).toEqual( [1, 3, 5] ) ; 498 | expect( vals.array() ).toEqual( ['a', 'c', 'e'] ); 499 | 500 | expect( zipped.entries() ).toEqual( [[1, 'a'], [3, 'c'], [5, 'e']] ); 501 | }); 502 | 503 | it('array', () => 504 | { 505 | expect( Iterate.array([]).array() ).toEqual( [] ); 506 | expect( Iterate.array([1, 2]).array() ).toEqual( [1, 2] ); 507 | expect( Iterate.object({x: 1, y: 4}).array() ).toEqual( [1, 4] ); 508 | }); 509 | 510 | it('object', () => 511 | { 512 | expect( Iterate.array([]).object(x => x) ).toEqual( {} ); 513 | expect( Iterate.array([1, 2]).object(x => x) ).toEqual( {1: 1, 2: 2} ); 514 | }); 515 | 516 | it('set', () => 517 | { 518 | expect( Iterate.array([]).set() ).toEqual( new Set([]) ); 519 | expect( Iterate.array([1, 2]).set() ).toEqual( new Set([1, 2]) ); 520 | }); 521 | 522 | it('map', () => 523 | { 524 | expect( Iterate.array([]).map() ).toEqual( new Map() ); 525 | expect( Iterate.array([1, 2]).map() ).toEqual( new Map([[0, 1], [1, 2]]) ); 526 | expect( Iterate.object({x: 1, y: 2}).map() ).toEqual( new Map([['x', 1], ['y', 2]]) ); 527 | }); 528 | 529 | it('keys', () => 530 | { 531 | expect( Iterate.object({x: 1, y: 2, z: 3}).keys().array() ).toEqual( ['x', 'y', 'z'] ); 532 | expect( Iterate.object({x: 1, y: 2, z: 3}).fork(f => f.keys().take(1).delete()).array() ).toEqual( [2, 3] ); 533 | }); 534 | 535 | it('group', () => 536 | { 537 | const groups = Iterate.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).group(x => x % 3); 538 | 539 | expect( groups ).toEqual({ 540 | 0: [0, 3, 6, 9], 541 | 1: [1, 4, 7], 542 | 2: [2, 5, 8] 543 | }); 544 | }); 545 | 546 | it('iterable', () => 547 | { 548 | expect( Iterate.iterable([1, 2, 3]).array() ).toEqual( [1, 2, 3] ); 549 | expect( Iterate.iterable(new Set([1, 2, 3])).array() ).toEqual( [1, 2, 3] ); 550 | }); 551 | 552 | it('readonly', () => 553 | { 554 | const a = [1, 2, 3, 4]; 555 | const b = Iterate.array(a); 556 | 557 | const c = b.where(x => x % 2 === 0).delete(); 558 | 559 | expect( c.array() ).toEqual( [] ); 560 | expect( a ).toEqual( [1, 3] ); 561 | 562 | const aa = [1, 2, 3, 4]; 563 | const bb = Iterate.array(aa).readonly(); 564 | 565 | const cc = bb.where(x => x % 2 === 0).delete(); 566 | 567 | expect( cc.array() ).toEqual( [2, 4] ); 568 | expect( aa ).toEqual( [1, 2, 3, 4] ); 569 | }); 570 | 571 | it('copy', () => 572 | { 573 | const a = [1, 2, 3, 4]; 574 | const b = Iterate.array(a); 575 | 576 | b.copy().delete(); 577 | 578 | expect( b.array() ).toEqual( [1, 2, 3, 4] ); 579 | expect( a ).toEqual( [1, 2, 3, 4] ); 580 | 581 | expect( b.copy().array() ).toEqual( [1, 2, 3, 4] ); 582 | 583 | b.delete(); 584 | 585 | expect( b.array() ).toEqual( [] ); 586 | expect( a ).toEqual( [] ); 587 | }); 588 | 589 | it('func', () => 590 | { 591 | // A function which †akes strings and finds all the ones with a's, change's 592 | // them to upper case and returns the number of changed. 593 | const fn = Iterate.func( 594 | (subject, setResult) => subject 595 | .where(x => x.indexOf('a') !== -1) 596 | .count(setResult) 597 | .update(x => x.toUpperCase()) 598 | ); 599 | 600 | const a = [ 601 | 'apple', 602 | 'bit', 603 | 'cat', 604 | 'dog', 605 | 'elephant', 606 | 'frog' 607 | ]; 608 | 609 | expect( fn(a) ).toEqual( 3 ); 610 | 611 | expect( a ).toEqual([ 612 | 'APPLE', 613 | 'bit', 614 | 'CAT', 615 | 'dog', 616 | 'ELEPHANT', 617 | 'frog' 618 | ]); 619 | 620 | const fn2 = Iterate.func( 621 | (source, setResult) => source 622 | .where(x => x.indexOf('a') !== -1) 623 | .count(setResult) 624 | .update(x => x.toUpperCase()) 625 | ); 626 | 627 | const a2 = ['apple', 'bit', 'cat']; 628 | const b2 = fn2(a2); 629 | 630 | expect( b2 ).toEqual(2); 631 | 632 | // Restrict function source type 633 | const fn3 = Iterate.func( 634 | (source, setResult) => source 635 | .count(setResult) 636 | ); 637 | 638 | expect( fn3('hello') ).toEqual(5); 639 | expect( fn3(new Map([[1, 'a']])) ).toBe(1); 640 | 641 | // expect( fn3({x: 'number'}) ).toEqual(5); 642 | }); 643 | 644 | it('func parameters', () => 645 | { 646 | const fn = Iterate.func( 647 | (subject, setResult, letter) => subject 648 | .fork(f => f 649 | .where(x => x.indexOf(letter) !== -1) 650 | .update(x => x.toUpperCase())) 651 | .array(setResult) 652 | ); 653 | 654 | expect( fn(['apple', 'bit'], 'a') ).toEqual(['APPLE', 'bit']); 655 | expect( fn(['apple', 'bit'], 'b') ).toEqual(['apple', 'BIT']); 656 | }); 657 | 658 | it('default comparator', () => 659 | { 660 | expect( Iterate.array([8, 7, 3, 4, 5, 6]).skip(2).max() ).toEqual( 6 ); 661 | }) 662 | 663 | interface Node { 664 | value: T; 665 | next?: Node; 666 | } 667 | 668 | const linkedIterator = Iterate.linked, string>( 669 | (node) => node.value, 670 | (node) => node.next, 671 | (node, prev) => prev.next = node.next, 672 | (node, value) => node.value = value 673 | ); 674 | 675 | const getLinkedList = (): Node => { 676 | return { 677 | value: 'HEAD', 678 | next: { 679 | value: 'a', 680 | next: { 681 | value: 'b', 682 | next: { 683 | value: 'c', 684 | next: { 685 | value: 'd', 686 | next: { 687 | value: 'e' 688 | } 689 | } 690 | } 691 | } 692 | } 693 | }; 694 | }; 695 | 696 | it('linked', () => 697 | { 698 | const list = getLinkedList(); 699 | const listIterator = linkedIterator(list); 700 | 701 | expect( listIterator.array() ).toEqual( ['a', 'b', 'c', 'd', 'e'] ); 702 | expect( listIterator.where(L => L === 'a' || L === 'd').array() ).toEqual( ['a', 'd'] ); 703 | 704 | listIterator.where(L => L === 'a' || L === 'd').delete(); 705 | 706 | expect( listIterator.array() ).toEqual( ['b', 'c', 'e'] ); 707 | 708 | listIterator.delete(); 709 | 710 | expect( listIterator.array() ).toEqual( [] ); 711 | }); 712 | 713 | interface TreeNode { 714 | value: T; 715 | children?: TreeNode[]; 716 | } 717 | 718 | const treeIterator = Iterate.tree, string>( 719 | node => node.value, 720 | node => node.children, 721 | (node, value) => node.value = value 722 | ); 723 | 724 | const getTree = (): TreeNode => { 725 | return { 726 | value: 'Harry', 727 | children: [{ 728 | value: 'Michael', 729 | children: [{ 730 | value: 'Robert' 731 | }, { 732 | value: 'Philip', 733 | children: [{ 734 | value: 'Mackenzie' 735 | }, { 736 | value: 'Mason' 737 | }] 738 | }, { 739 | value: 'Joseph', 740 | children: [{ 741 | value: 'Miles' 742 | }] 743 | }, { 744 | value: 'Katlyn' 745 | }, { 746 | value: 'Alyssa' 747 | }] 748 | }, { 749 | value: 'Donald' 750 | }] 751 | }; 752 | }; 753 | 754 | it('tree depth', () => 755 | { 756 | const tree = getTree(); 757 | const list = treeIterator(tree, true).array(); 758 | 759 | expect(list).toEqual(['Harry', 'Michael', 'Robert', 'Philip', 'Mackenzie', 'Mason', 'Joseph', 'Miles', 'Katlyn', 'Alyssa', 'Donald']); 760 | }); 761 | 762 | it('tree breadth', () => 763 | { 764 | const tree = getTree(); 765 | const list = treeIterator(tree, false).array(); 766 | 767 | expect(list).toEqual(['Harry', 'Michael', 'Donald', 'Robert', 'Philip', 'Joseph', 'Katlyn', 'Alyssa', 'Mackenzie', 'Mason', 'Miles']); 768 | }); 769 | 770 | it('tree delete', () => 771 | { 772 | const tree = getTree(); 773 | 774 | treeIterator(tree).where(name => name.indexOf('e') !== -1).delete(); 775 | 776 | const list = treeIterator(tree).array(); 777 | 778 | expect(list).toEqual(['Harry', 'Donald']); 779 | }); 780 | 781 | it('tree replace', () => 782 | { 783 | const tree = getTree(); 784 | 785 | treeIterator(tree).each((name, key, iter) => iter.replace(name.toLowerCase())); 786 | 787 | const list = treeIterator(tree).array(); 788 | 789 | expect(list).toEqual(['harry', 'michael', 'robert', 'philip', 'mackenzie', 'mason', 'joseph', 'miles', 'katlyn', 'alyssa', 'donald']); 790 | }); 791 | 792 | }) 793 | -------------------------------------------------------------------------------- /typings/Iterate.d.ts: -------------------------------------------------------------------------------- 1 | import { IterateAction } from "./IterateAction"; 2 | import { GetKeyFor, GetValueFor, HasEntries, IterateCallback, IterateCompare, IterateEquals, IterateFilter, IterateFunction, IterateFunctionExecute, IterateReset, IterateResult, IterateSource, IterateSourceType, IterateSourceTypeKey } from "./types"; 3 | /** 4 | * A class that allows an iteratable source to be iterated any number of times. 5 | * 6 | * There are 3 types of functions in an Iterate: 7 | * - **Operation**: produces a result from the values in the iterator. 8 | * - **View**: produces a new iterator, the original iterator is not affected. 9 | * - **Mutation**: modifies the source based on the values in the current iterator. 10 | * 11 | * The **View** functions do not iterate over the source, the iterator they 12 | * return does not iterate over the source until an **Operation** or 13 | * **Mutation** function are called on it. 14 | * 15 | * **Operations** 16 | * - `empty`: Determines whether the view contains no values. 17 | * - `has`: Determines whether the view contains any values. 18 | * - `contains`: Determines if the view contains a specific value. 19 | * - `first`: Gets the first value in the view. 20 | * - `last`: Gets the last value in the view. 21 | * - `count`: Counds the number of values in the view. 22 | * - `array`: Builds an array of the values in the view. 23 | * - `set`: Builds a Set of the values in the view. 24 | * - `object`: Builds an object of the values in the view. 25 | * - `entries`: Builds an array of `[key, value]` in the view. 26 | * - `map`: Builds a Map of the values and keys in the view. 27 | * - `group`: Builds an object of value arrays grouped by a value derived from each value. 28 | * - `reduce`: Reduces the values in the view down to a single value. 29 | * - `min`: Returns the minimum value in the view. 30 | * - `max`: Returns the maximum value in the view. 31 | * - `each`: Invokes a function for each value in the view. 32 | * - `copy`: Copies the values in the view and returns a new iterator. 33 | * - `changes`: Notifies you when values are added, removed, or still present on an iterator since the last time called. 34 | * 35 | * **Mutations** 36 | * - `delete`: Removes values in the view from the source. 37 | * - `overwrite`: Replaces values in the view from the source. 38 | * - `extract`: Removes values in the view from the source and returns a new iterator with the removed values. 39 | * 40 | * **Views** 41 | * Returns an iterator... 42 | * - `where`: for a subset of the values. 43 | * - `not`: for a subset of the values (opposite of where). 44 | * - `transform`: that transforms the values to another type. 45 | * - `reverse`: that iterates over the values in reverse order. 46 | * - `exclude`: that excludes values found in another iterator. 47 | * - `intersect`: that has common values in another iterator. 48 | * - `sorted`: that is sorted based on some comparison. 49 | * - `shuffle`: that is randomly ordered. 50 | * - `unique`: that has only unique values. 51 | * - `duplicates`: that has all the duplicate values. 52 | * - `readonly`: that ignores mutations. 53 | * - `keys`: only for the keys of the values (replace not supported). 54 | * - `values`: only for the values of the values (new key is index based). 55 | * - `take`: that only iterates over the first X values. 56 | * - `skip`: that skips the first X values. 57 | * - `drop`: that drops off the last X values. 58 | * - `append`: that is the original iterator + one or more iterators specified. 59 | * - `prepend`: that is one or more iterators specified + the original iterator. 60 | * - `gt`: that only has values greater than a value. 61 | * - `gte`: that only has values greater than or equal to a value. 62 | * - `lt`: that only has values less than a value. 63 | * - `lte`: that only has values less than or equal to a value. 64 | * - `fork`: that is this, but allows a function to perform fork operations 65 | * - `split`: Splits the values into two iterators (pass/fail) based on a condition. 66 | * - `unzip`: Splits the view into two iterates (keys/values). 67 | * 68 | * The following functions are used to control comparison logic 69 | * 70 | * - `numbers`: Set number comparison logic to the iterator. 71 | * - `strings`: Set string comparison logic to the iterator. 72 | * - `dates`: Set date comparison logic to the iterator. 73 | * - `desc`: Reverses the comparison logic. 74 | * - `withEquality`: Set a custom equality function. 75 | * - `withComparator`: Set a custom comparison function. 76 | * 77 | * The following function lets you change the source. 78 | * 79 | * - `reset`: Specify a new source to iterate over. 80 | * 81 | * The following static functions exist to help iterate simple sources: 82 | * 83 | * - `array`: Iterates an array. 84 | * - `object`: Iterates the properties of an object, optionally just the properties explicitly set on the object. 85 | * - `tree`: Iterates trees. 86 | * - `linked`: Iterates linked-lists. 87 | * - `map`: Iterates Maps 88 | * - `set`: Iterates Sets 89 | * - `hasEntries`: Iterates any object which has the `entries()` iterator. 90 | * - `empty`: Iterates nothing. 91 | * - `iterable`: Iterates any collection that implements iterable. 92 | * - `join`: Returns an iterator that iterates over one or more iterators. 93 | * - `entries`: Iterates an array of `[key, value]` entries. 94 | * - `zip`: Combines a key iterator and value iterator into one. 95 | * 96 | * @typeparam T The type of value being iterated. 97 | */ 98 | export declare class Iterate { 99 | /** 100 | * A result of the iteration passed to [[Iterate.stop]]. 101 | */ 102 | result: any; 103 | /** 104 | * The last action (if any) called on this iterator. 105 | */ 106 | action: IterateAction; 107 | /** 108 | * The value to replace with the current value. 109 | */ 110 | replaceWith: T; 111 | /** 112 | * The current callback passed to the iterator. 113 | */ 114 | callback: IterateCallback; 115 | /** 116 | * The source of iterable values. This allows the iteration over any type of 117 | * structure. The source must call the callback for each value and its 118 | * recommended that the source checks the [[Iterate.iterating]] flag after 119 | * each callback invokation. 120 | */ 121 | private source; 122 | /** 123 | * The equality checker to use for this iterator and subsequent views. 124 | */ 125 | private equality; 126 | /** 127 | * The comparator to use for this iterator and subsequent views. 128 | */ 129 | private comparator; 130 | /** 131 | * The function to invoke to passing a new source for iteration. 132 | */ 133 | private handleReset; 134 | /** 135 | * Creates a new Iterate given a source. 136 | * 137 | * @param source The source of values to iterator. 138 | */ 139 | constructor(source: IterateSource, parent?: Iterate); 140 | /** 141 | * The function which receives a new source to reset iteration. 142 | * 143 | * @package handleReset The function which takes the new source. 144 | */ 145 | onReset(handleReset: IterateReset): this; 146 | /** 147 | * Returns whether the iterator at this point supports a reset. 148 | */ 149 | canReset(): boolean; 150 | /** 151 | * Sets a new source for iteration if supported. If the iterator doesn't 152 | * support resetting the source then an error will be thrown when `strict` 153 | * is true. 154 | * 155 | * @param source The new source for iteration. 156 | * @param strict If an error should be thrown if the iterator can't be reset. 157 | */ 158 | reset(source: S, strict?: boolean): this; 159 | /** 160 | * Returns a clone of this iterator with the same source. This is necessary 161 | * if you want to iterate all or a portion of the source while already 162 | * iterating it (like a nested loop). 163 | */ 164 | clone(): Iterate; 165 | /** 166 | * Passes the given value to the iterator callback and returns the action 167 | * requested at this point in iteration. 168 | * 169 | * @param value The current value being iterated. 170 | */ 171 | act(value: T, key: K): IterateAction; 172 | /** 173 | * Stops iteration and optionally sets the result of the iteration. 174 | * 175 | * @param result The result of the iteration. 176 | */ 177 | stop(result?: any): this; 178 | /** 179 | * Returns whether iteration was stopped by the user. 180 | */ 181 | isStopped(): boolean; 182 | /** 183 | * Stops iteration and optionally sets the result of the iteration. 184 | * 185 | * @param result The result of the iteration. 186 | */ 187 | replace(replaceWith: T): this; 188 | /** 189 | * Signals to the iterator source that the current value wants to be removed. 190 | */ 191 | remove(): this; 192 | /** 193 | * Sets the equality logic for this iterator and subsequent views. 194 | * 195 | * @param equality A function to compare two values for equality. 196 | */ 197 | withEquality(equality: IterateEquals): this; 198 | /** 199 | * Sets the comparison logic for this iterator and subsequent views. If this 200 | * iterator does not have an equality check, this will also use the 201 | * comparator for the equality logic. 202 | * 203 | * @param comparator A function which compares tow values. 204 | */ 205 | withComparator(comparator: IterateCompare): this; 206 | /** 207 | * Applies the logic to the iterator. 208 | * 209 | * @param comparator The comparison logic. 210 | * @param equality The equality logic if the comparison logic won't suffice. 211 | */ 212 | private withLogic; 213 | /** 214 | * Applies number equality and comparison logic to this iterator and 215 | * subsequent ones. 216 | * 217 | * @param ascending If the numbers should be in ascending order. 218 | * @param nullsFirst If non-number values should be ordered first. 219 | */ 220 | numbers(ascending?: boolean, nullsFirst?: boolean): this; 221 | /** 222 | * Applies string equality and comparison logic to this iterator and 223 | * subsequent ones. 224 | * 225 | * @param sensitive If equality logic should be case sensitive. 226 | * @param ascending If the strings should be in ascending order. 227 | * @param nullsFirst If non-strings values should be ordered first. 228 | */ 229 | strings(sensitive?: boolean, ascending?: boolean, nullsFirst?: boolean): this; 230 | /** 231 | * Applies date equality and comparison logic to this iterator and 232 | * subsequent ones. 233 | * 234 | * @param equalityTimespan This defines which timespan to dates can lie on 235 | * to be considered equal. By default they have to have the same 236 | * millisecond. If you want dates equal by the following timespans use 237 | * these values: Second = 1000, Minute = 60000, Hour = 3600000, 238 | * Day = 86400000, Week = 604800000 (approx) 239 | * @param utc If when comparing timespans, if we should look at their UTC 240 | * date/time OR if they should be changed to their relative times based 241 | * on their timezone. 242 | * @param ascending If the dates should be in ascending order (oldest first). 243 | * @param nullsFirst If non-date values should be ordered first. 244 | */ 245 | dates(equalityTimespan?: number, utc?: boolean, ascending?: boolean, nullsFirst?: boolean): this; 246 | /** 247 | * Reverses the comparator on this iterator and subsequent views. If this 248 | * iterator does not have a comparator this has no affect. 249 | * 250 | * @param comparator An override for any existing comparison logic. 251 | */ 252 | desc(comparator?: IterateCompare): this; 253 | /** 254 | * Gets the equality logic desired, optionally overriding the one specified 255 | * on this iterator. 256 | * 257 | * @param equalityOverride Equality logic to use if provided. 258 | */ 259 | getEquality(equalityOverride?: IterateEquals): IterateEquals; 260 | /** 261 | * Gets the comparison logic desired, optionally overriding the one specified 262 | * on this iterator. If one cannot be determined an error is thrown. 263 | * 264 | * @param comparatorOverride Comparison logic to use if provided. 265 | */ 266 | getComparator(comparatorOverride?: IterateCompare): IterateCompare; 267 | /** 268 | * An operation that determines whether this iterator is empty. 269 | * 270 | * @param setResult A function to pass the result to. 271 | */ 272 | empty(): boolean; 273 | empty(setResult: IterateResult): this; 274 | /** 275 | * An operation that determines whether this iterator has a value. 276 | * 277 | * @param setResult A function to pass the result to. 278 | */ 279 | has(): boolean; 280 | has(setResult: IterateResult): this; 281 | /** 282 | * An operation that determines whether this iterator has the given value. 283 | * 284 | * @param value The value to search for. 285 | * @param setResult A function to pass the result to. 286 | */ 287 | contains(value: T): boolean; 288 | contains(value: T, setResult: IterateResult): this; 289 | /** 290 | * An operation that counts the number of values in the iterator. 291 | * 292 | * @param setResult A function to pass the count to. 293 | */ 294 | count(): number; 295 | count(setResult: IterateResult): this; 296 | /** 297 | * An operation that returns the first value in the iterator. 298 | * 299 | * @param setResult A function to pass the first value to. 300 | */ 301 | first(): T; 302 | first(setResult: IterateResult): this; 303 | /** 304 | * An operation that returns the last value in the iterator. 305 | * 306 | * @param setResult A function to pass the last value to. 307 | */ 308 | last(): T; 309 | last(setResult: IterateResult): this; 310 | /** 311 | * An operation that builds an array of values from the source. 312 | * 313 | * @param out The array to place the values in. 314 | * @param setResult A function to pass the array to. 315 | */ 316 | array(out?: T[]): T[]; 317 | array(out: T[], setResult: IterateResult): this; 318 | array(setResult: IterateResult): this; 319 | /** 320 | * An operation that builds an array of [key, value] entries from this view. 321 | * 322 | * @param out The array to place the entries in. 323 | * @param setResult A function to pass the entries to. 324 | */ 325 | entries(out?: Array<[K, T]>): Array<[K, T]>; 326 | entries(out: Array<[K, T]>, setResult: IterateResult>): this; 327 | entries(setResult: IterateResult>): this; 328 | /** 329 | * An operation that builds an object of values from the iterator keyed by a 330 | * result returned by a `getKey` function. 331 | * 332 | * @param getKey The function which returns the key of the object. 333 | * @param out The object to place the values in. 334 | * @param setResult A function to pass the object to. 335 | */ 336 | object(getKey: (value: T) => keyof O, out?: O): O; 339 | object(getKey: (value: T) => keyof O, out: O, setResult: IterateResult): this; 342 | object(getKey: (value: T) => keyof O, setResult: IterateResult): this; 345 | /** 346 | * An operation that builds a Set of values from the source. 347 | * 348 | * @param out The Set to place the values in. 349 | * @param setResult A function to pass the set to. 350 | */ 351 | set(out?: Set): Set; 352 | set(out: Set, setResult: IterateResult>): this; 353 | set(setResult: IterateResult>): this; 354 | /** 355 | * An operation that builds a Map of key-value pairs from the source. 356 | * 357 | * @param out The Map to place the values in. 358 | * @param setResult A function to pass the map to. 359 | */ 360 | map(out?: Map): Map; 361 | map(out: Map, setResult: IterateResult>): this; 362 | map(setResult: IterateResult>): this; 363 | /** 364 | * An operation that returns an object with arrays of values where the 365 | * property of the object is a key returned by a function. 366 | * 367 | * @param by A function to get the key from a value. 368 | * @param out The object to add groups to. 369 | * @param setResult A function to pass the groups to. 370 | */ 371 | group(by: (value: T) => any, out?: G): G; 374 | group(by: (value: T) => any, out: G, setResult: IterateResult): this; 377 | group(by: (value: T) => any, setResult: IterateResult): this; 380 | /** 381 | * An operation that reduces all the values in the source to a single value 382 | * given the initial value and a function to convert a value and the current 383 | * reduced value 384 | * 385 | * @param initial The initial value to pass to the `reducer` function the 386 | * first time. 387 | * @param reducer A function which takes a value in the iterator and the 388 | * current reduced value and returns a new reduced value. 389 | * @param setResult A function to pass the reduced value to. 390 | */ 391 | reduce(initial: R, reducer: (value: T, reduced: R) => R): R; 392 | reduce(initial: R, reducer: (value: T, reduced: R) => R, setResult: IterateResult): this; 393 | /** 394 | * An operation that returns the minimum value in this iterator. If this 395 | * iterator is empty null is returned. 396 | * 397 | * @param setResult A function to pass the minimum value to. 398 | */ 399 | min(): T; 400 | min(setResult: IterateResult): this; 401 | /** 402 | * An operation that returns the maximum value in this iterator. If this 403 | * iterator is empty null is returned. 404 | * 405 | * @param setResult A function to pass the maximum value to. 406 | */ 407 | max(): T; 408 | max(setResult: IterateResult): this; 409 | /** 410 | * A map of key-value pairs stored from the last time `changes` was invoked. 411 | * 412 | * The keys are the value in the iterator or a dynamically created value 413 | * returned by the `getIdentifier` function. If that function is provided 414 | * once it must always be provided to ensure correct change detection. 415 | */ 416 | protected history: Map; 417 | /** 418 | * An operation which determines which changes have occurred in the source 419 | * since the last time the changes operation was called. The changes 420 | * operation needs to be called on the same exact iterator instance to 421 | * properly track changes. You should avoid sharing an iterator or using 422 | * reset for an iterator that you're using to track changes. 423 | * 424 | * Optionally you can provide a `getIdentifier` function which can convert 425 | * a value into a more optimal value for comparison. The value returned 426 | * will be compared by reference so a scalar value (number, string, etc) 427 | * is ideal but other identifiers can be returned as long as they are 428 | * the same reference and not dynamically generated. 429 | * 430 | * The first time this operation is performed all the values in the iterator 431 | * will be passed through the `onAdd` function. 432 | * 433 | * The `onRemove` function is only called at the very end of the changes 434 | * logic. 435 | * 436 | * @param onAdd The function to invoke for each value added since the 437 | * last `changes` operation, 438 | * @param onRemove The function to invoke for each value removed since the 439 | * last `changes` operation. This function is called zero or more times 440 | * at the end of the changes logic. 441 | * @param onPresent The function to invoke for each value that was in the 442 | * iterator before and is still in the iterator. 443 | * @param getIdentifier A function to use to create a simpler way to identify 444 | * a value. The simpler the value returned the better the performance 445 | * of the changes logic. If this function is passed once, it should be 446 | * passed everytime or the results of this function will not be accurate. 447 | */ 448 | changes(onAdd: IterateCallback, onRemove: IterateCallback, onPresent: IterateCallback, getIdentifier?: IterateCallback): this; 449 | /** 450 | * A mutation which removes values in this iterator from the source. 451 | */ 452 | delete(): this; 453 | /** 454 | * A mutation which removes values in this iterator from the source and 455 | * returns a new iterator with the removed values. 456 | */ 457 | extract(): Iterate>; 458 | extract(setResult: IterateResult>>): this; 459 | /** 460 | * A mutation which replaces values in this view with a single given value. 461 | * 462 | * @param replacement The value to replace for all the values in this iterator. 463 | */ 464 | overwrite(replacement: T): this; 465 | /** 466 | * A mutation which replaces values in this view with a dynamically created one. 467 | * 468 | * @param updater A function which given a value and key returns a replacement value. 469 | */ 470 | update(updater: (value: T, key: K) => T): this; 471 | /** 472 | * Forks this view into another and returns a reference to this view. 473 | * This allows chaining of multiple views which each perform a different 474 | * operation or mutation. Forks are executed sequentially, so if one fork 475 | * performs mutations the subsequent forks will see the mutated values. 476 | * 477 | * @param forker A function which takes the iterator at this point and 478 | * performs any mutations and operations. 479 | */ 480 | fork(forker: (fork: this) => any): this; 481 | /** 482 | * Provides split views of the values in this iterator, one iterator gets 483 | * passed values and the other iterator gets the failed values. 484 | * 485 | * You can pass a function as a second argument which recieves two iterators 486 | * for pass and fail respectively. This will be returned in that scenario. 487 | * 488 | * If you don't pass a second function an object will be returned with two 489 | * properties: pass and fail. 490 | */ 491 | split(pass: IterateFilter): { 492 | pass: Iterate; 493 | fail: Iterate; 494 | }; 495 | split(pass: IterateFilter, handle: (pass: Iterate, fail: Iterate) => any): this; 496 | /** 497 | * Unzips the view into a keys and values views. 498 | * 499 | * You can pass a function as a second argument which recieves two iterators 500 | * for keys and values respectively. This will be returned in that scenario. 501 | * 502 | * If you don't pass a second function an object will be returned with two 503 | * properties: keys and values. 504 | */ 505 | unzip(): { 506 | keys: Iterate; 507 | values: Iterate; 508 | }; 509 | unzip(handle: (keys: Iterate, values: Iterate) => any): this; 510 | /** 511 | * Returns a view of just the keys in this view. Any mutations done to the 512 | * keys view affect the underlying source. 513 | */ 514 | keys(): Iterate; 515 | /** 516 | * Returns a view of just the values in this view. Any mutations done to the 517 | * values view affect the underlying source. 518 | */ 519 | values(): Iterate; 520 | /** 521 | * Returns a view of this iterator. This allows other views to be more DRY. 522 | * 523 | * @param getData Get any necessary data needed for the view. 524 | * @param shouldAct Based on the data and the value, should we act on it? 525 | * @param afterAct What to do if the value was acted on. 526 | * @param afterSkip What to do if the value was NOT acted on. 527 | */ 528 | view(getData: () => D, shouldAct: (data: D, value: T, key: K) => any, afterAct?: (data: D, value: T, key: K, iter: Iterate) => void, afterSkip?: (data: D, value: T, key: K, iter: Iterate) => void): Iterate; 529 | /** 530 | * Returns a view that only returns a maximum number of values. 531 | * 532 | * @param amount The maximum number of values to return. 533 | */ 534 | take(amount: number): Iterate; 535 | /** 536 | * Returns a view that skips the given number of values from the values 537 | * in this iterator. 538 | * 539 | * @param amount The number of values to skip. 540 | */ 541 | skip(amount: number): Iterate; 542 | /** 543 | * Returns a view that drops the given number of values from the end of the 544 | * values in this iterator. 545 | * 546 | * @param amount The number of values to drop from the end. 547 | */ 548 | drop(amount: number): Iterate; 549 | /** 550 | * Returns a view thats values are the values in this iterator followed 551 | * by the values in the given iterators. 552 | * 553 | * @param iterators The iterators to append after this one. 554 | */ 555 | append(...sources: S[]): Iterate; 556 | append(...sources: IterateSourceTypeKey[]): Iterate; 557 | append(...sources: IterateSourceType[]): Iterate; 558 | append(...sources: IterateSourceType[]): Iterate; 559 | /** 560 | * Returns a view thats values are the values in the given iterators 561 | * followed by the values in this iterator. 562 | * 563 | * @param iterators The iterators to prepend before this one. 564 | */ 565 | prepend(...sources: S[]): Iterate; 566 | prepend(...sources: IterateSourceTypeKey[]): Iterate; 567 | prepend(...sources: IterateSourceType[]): Iterate; 568 | prepend(...sources: IterateSourceType[]): Iterate; 569 | /** 570 | * Returns a view of values in this iterator which pass a `where` function. 571 | * 572 | * @param where The function which determines if a value should be iterated. 573 | */ 574 | where(where: IterateFilter): Iterate; 575 | /** 576 | * Returns a view of values in this iterator which do NOT pass a `not` function. 577 | * 578 | * @param not The function which determines if a value should be iterated. 579 | */ 580 | not(not: IterateFilter): Iterate; 581 | /** 582 | * Returns a view where all values are greater than the given value. 583 | * If a comparator is not on this iterator or provided an error is thrown. 584 | * 585 | * @param threshold The value to compare against. 586 | * @param comparator An override for any existing comparison logic. 587 | */ 588 | gt(threshold: T, comparator?: IterateCompare): Iterate; 589 | /** 590 | * Returns a view where all values are greater than or equal to the given value. 591 | * If a comparator is not on this iterator or provided an error is thrown. 592 | * 593 | * @param threshold The value to compare against. 594 | * @param comparator An override for any existing comparison logic. 595 | */ 596 | gte(threshold: T, comparator?: IterateCompare): Iterate; 597 | /** 598 | * Returns a view where all values are less than the given value. 599 | * If a comparator is not on this iterator or provided an error is thrown. 600 | * 601 | * @param threshold The value to compare against. 602 | * @param comparator An override for any existing comparison logic. 603 | */ 604 | lt(threshold: T, comparator?: IterateCompare): Iterate; 605 | /** 606 | * Returns a view where all values are less than or equal to the given value. 607 | * If a comparator is not on this iterator or provided an error is thrown. 608 | * 609 | * @param threshold The value to compare against. 610 | * @param comparator An override for any existing comparison logic. 611 | */ 612 | lte(threshold: T, comparator?: IterateCompare): Iterate; 613 | /** 614 | * Returns a view of this iterator which does not include the values in the 615 | * given iterator. 616 | * 617 | * @param source The source of values to exclude. 618 | * @param equality An override for any existing equality logic. 619 | */ 620 | exclude(source: IterateSourceType, equality?: IterateEquals): Iterate; 621 | /** 622 | * Returns a view which has values which are in this iterator and the given 623 | * iterator. 624 | * 625 | * @param source The source of values to intersect with. 626 | * @param equality An override for any existing equality logic. 627 | */ 628 | intersect(source: IterateSourceType, equality?: IterateEquals): Iterate; 629 | /** 630 | * Returns a view which only contains unique values. 631 | * 632 | * @param equality An override for any existing equality logic. 633 | */ 634 | unique(equality?: IterateEquals): Iterate; 635 | /** 636 | * Returns a view which only contains values that have duplicates in this 637 | * iterator. For any values that occur more than twice you can exclude them 638 | * from the resulting view by passing `true` to `onlyOnce`. 639 | * 640 | * @param onlyOnce If the view should contain unique or all duplicates. 641 | * @param equality An override for any existing equality logic. 642 | */ 643 | duplicates(onlyOnce?: boolean, equality?: IterateEquals): Iterate; 644 | /** 645 | * Returns a readonly view where mutations have no affect. 646 | */ 647 | readonly(): Iterate; 648 | /** 649 | * Returns a copy of the values in this view as a new iterator. 650 | */ 651 | copy(): Iterate>; 652 | /** 653 | * Returns a view which requires a fully resolved array of values. The view 654 | * must keep track of the original value index in order to ensure removals 655 | * and replaces can be performed on the source. 656 | * 657 | * @param onResolve 658 | */ 659 | viewResolved(onResolve: (values: Array<[K, T]>, handleAct: (value: T, key: K, index: number) => IterateAction) => void): Iterate; 660 | /** 661 | * Returns a view which has the values sorted. 662 | * 663 | * @param comparator An override for any existing comparison logic. 664 | */ 665 | sorted(comparator?: IterateCompare): Iterate; 666 | /** 667 | * Returns an view of values in this iterator and presents them in a random order. 668 | */ 669 | shuffle(passes?: number): Iterate; 670 | /** 671 | * Returns an view of values in this iterator and presents them in reverse. 672 | */ 673 | reverse(): Iterate; 674 | /** 675 | * Returns an iterator where this iterator is the source and the returned 676 | * iterator is built from transformed values pulled from values in the source 677 | * of this iterator. 678 | * 679 | * @param transformer The function which transforms a value to another type. 680 | * @param untransformer The function which untransforms a value when replace is called. 681 | */ 682 | transform(transformer: IterateCallback, untransformer?: (replaceWith: W, current: W, value: T, key: K) => T): Iterate; 683 | /** 684 | * Invokes the callback for each value in the source of this iterator. The 685 | * second argument in the callback is the reference to this iterator and 686 | * [[Iterate.stop]] can be called at anytime to cease iteration. 687 | * 688 | * @param callback The function to invoke for each value in this iterator. 689 | */ 690 | each(callback: IterateCallback): this; 691 | /** 692 | * Passes the result of the iteration to the given function if a truthy 693 | * result was passed to [[Iterate.stop]]. 694 | * 695 | * @param getResult The function to pass the result to if it exists. 696 | */ 697 | withResult(getResult: (result: any) => any): this; 698 | /** 699 | * This allows for...of loops to be used on this iterator. 700 | */ 701 | [Symbol.iterator](): Iterator; 702 | /** 703 | * Returns an iterator for the given array. 704 | * 705 | * @param values The array of values to iterate. 706 | * @returns A new iterator for the given array. 707 | */ 708 | static entries(values?: Array<[K, T]>): Iterate>; 709 | /** 710 | * Returns an iterator for the given array. 711 | * 712 | * @param values The array of values to iterate. 713 | * @returns A new iterator for the given array. 714 | */ 715 | static array(values?: T[]): Iterate; 716 | /** 717 | * Returns an iterator for the keys and values specified. If the key and 718 | * value iterators don't have the same number of values, the returned iterator 719 | * will have the maximum pairs possible (which is the lesser of the number 720 | * of keys and values). 721 | * 722 | * If the returned iterator is mutated the given keys and values iterators 723 | * will be mutated as well. If you want to avoid that, pass in readonly 724 | * key/value iterators. 725 | * 726 | * @param keys The iterator to obtain the keys from. 727 | * @param values The iterator to obtain the values from. 728 | */ 729 | static zip, S, T extends GetValueFor>(keySource: J, valueSource: S): Iterate; 730 | /** 731 | * Returns an iterator for any object which has an entries() iterable. 732 | * 733 | * @param hasEntries The object with the entries() iterable. 734 | * @param onRemove The function that should handle removing a key/value. 735 | * @param onReplace The function that should handle replacing a value. 736 | */ 737 | static hasEntries>(hasEntries?: E, onRemove?: (entries: E, key: K, value: T) => any, onReplace?: (entries: E, key: K, value: T, newValue: T) => any): Iterate; 738 | /** 739 | * Returns an iterator for the given Map. 740 | * 741 | * @param values The Map of key-value pairs to iterate. 742 | * @returns A new iterator for the given Map. 743 | */ 744 | static map(values?: Map): Iterate>; 745 | /** 746 | * Returns an iterator for the given Set. 747 | * 748 | * @param values The Set of values to iterate. 749 | * @returns A new iterator for the given Set. 750 | */ 751 | static set(values?: Set): Iterate>; 752 | /** 753 | * Returns an iterator for any iterable. Because iterables don't support 754 | * 755 | * @param values The iterable collection. 756 | * @returns A new iterator for the given set. 757 | */ 758 | static iterable(values: Iterable): Iterate>; 759 | /** 760 | * Returns an iterator for the given object optionally checking the 761 | * `hasOwnProperty` function on the given object. 762 | * 763 | * @param values The object to iterate. 764 | * @param hasOwnProperty If `hasOwnProperty` should be checked. 765 | * @returns A new iterator for the given object. 766 | */ 767 | static object(values: { 768 | [key: string]: T; 769 | }, hasOwnProperty?: boolean): Iterate; 772 | /** 773 | * Returns a function for iterating over a linked-list. You pass a node to 774 | * the function returned (and whether it should be strict) and an iterator 775 | * will be returned. 776 | * 777 | * @param getValue A function which gets a value from a node. 778 | * @param getNext A function which returns the next node. When at the end 779 | * list null or undefined should be returned. 780 | * @param remove A function which handles a remove request. If this is not 781 | * specified and a remove is called and `strict` is true an error will be 782 | * thrown. 783 | * @param replaceValue A function which applies a value to a node. If this is 784 | * not specified and a replace is called and `strict` is true an error 785 | * will be thrown. 786 | */ 787 | static linked(getValue: (node: N) => T, getNext: (node: N) => N | undefined | null, remove?: (node: N, prev: N) => any, replaceValue?: (node: N, value: T) => any, getKey?: (node: N) => K): (previousNode?: N, strict?: boolean) => Iterate; 788 | /** 789 | * Returns a function for iterating over a tree. You pass a node to the 790 | * function returned (and whether it should perform a depth-first or 791 | * breadth-first traversal) and an iterator will be returned. 792 | * 793 | * @param getValue A function which gets a value from a node. 794 | * @param getChildren A function which returns an array of child nodes or an 795 | * iterator which can return the children. 796 | * @param replaceValue A function which applies a value to a node. 797 | */ 798 | static tree(getValue: (node: N) => T, getChildren: (node: N) => N[] | Iterate | undefined | null, replaceValue?: (node: N, value: T) => any, getKey?: (node: N) => K): (startingNode?: N, depthFirst?: boolean, strict?: boolean) => Iterate; 799 | /** 800 | * Joins all the given sources into a single iterator where the values 801 | * returned are in the same order as passed to this function. If any values 802 | * are removed from the returned iterator they will be removed from the given 803 | * iterator if it supports removal. 804 | * 805 | * @param sources The sources to get iterators for to join. 806 | * @returns A new iterator for the given sources. 807 | */ 808 | static join(...sources: Iterate[]): Iterate; 809 | static join, K extends GetKeyFor>(...sources: S[]): Iterate; 810 | static join(...sources: IterateSourceTypeKey[]): Iterate; 811 | static join(...sources: IterateSourceType[]): Iterate; 812 | static join(...sources: IterateSourceType[]): Iterate; 813 | /** 814 | * Returns a new iterator with no values. 815 | * 816 | * @returns A new iterator with no values. 817 | */ 818 | static empty(): Iterate; 819 | /** 820 | * Generates a reusable function which takes a source and performs a 821 | * pre-defined set of views, operations, and mutations. 822 | * 823 | * @param execute The function which performs the function. 824 | */ 825 | static func(execute: IterateFunctionExecute): IterateFunction; 826 | } 827 | -------------------------------------------------------------------------------- /src/Iterate.ts: -------------------------------------------------------------------------------- 1 | import { defaultCompare, getDateComparator, getDateEquality, getNumberComparator, getStringComparator, isFunction, iterate } from './functions'; 2 | import { IterateAction } from "./IterateAction"; 3 | import { GetKeyFor, GetValueFor, HasEntries, IterateCallback, IterateCompare, IterateEquals, IterateFilter, IterateFunction, IterateFunctionExecute, IterateReset, IterateResult, IterateSource, IterateSourceType, IterateSourceTypeKey } from "./types"; 4 | 5 | 6 | /** 7 | * A class that allows an iteratable source to be iterated any number of times. 8 | * 9 | * There are 3 types of functions in an Iterate: 10 | * - **Operation**: produces a result from the values in the iterator. 11 | * - **View**: produces a new iterator, the original iterator is not affected. 12 | * - **Mutation**: modifies the source based on the values in the current iterator. 13 | * 14 | * The **View** functions do not iterate over the source, the iterator they 15 | * return does not iterate over the source until an **Operation** or 16 | * **Mutation** function are called on it. 17 | * 18 | * **Operations** 19 | * - `empty`: Determines whether the view contains no values. 20 | * - `has`: Determines whether the view contains any values. 21 | * - `contains`: Determines if the view contains a specific value. 22 | * - `first`: Gets the first value in the view. 23 | * - `last`: Gets the last value in the view. 24 | * - `count`: Counds the number of values in the view. 25 | * - `array`: Builds an array of the values in the view. 26 | * - `set`: Builds a Set of the values in the view. 27 | * - `object`: Builds an object of the values in the view. 28 | * - `entries`: Builds an array of `[key, value]` in the view. 29 | * - `map`: Builds a Map of the values and keys in the view. 30 | * - `group`: Builds an object of value arrays grouped by a value derived from each value. 31 | * - `reduce`: Reduces the values in the view down to a single value. 32 | * - `min`: Returns the minimum value in the view. 33 | * - `max`: Returns the maximum value in the view. 34 | * - `each`: Invokes a function for each value in the view. 35 | * - `copy`: Copies the values in the view and returns a new iterator. 36 | * - `changes`: Notifies you when values are added, removed, or still present on an iterator since the last time called. 37 | * 38 | * **Mutations** 39 | * - `delete`: Removes values in the view from the source. 40 | * - `overwrite`: Replaces values in the view from the source. 41 | * - `extract`: Removes values in the view from the source and returns a new iterator with the removed values. 42 | * 43 | * **Views** 44 | * Returns an iterator... 45 | * - `where`: for a subset of the values. 46 | * - `not`: for a subset of the values (opposite of where). 47 | * - `transform`: that transforms the values to another type. 48 | * - `reverse`: that iterates over the values in reverse order. 49 | * - `exclude`: that excludes values found in another iterator. 50 | * - `intersect`: that has common values in another iterator. 51 | * - `sorted`: that is sorted based on some comparison. 52 | * - `shuffle`: that is randomly ordered. 53 | * - `unique`: that has only unique values. 54 | * - `duplicates`: that has all the duplicate values. 55 | * - `readonly`: that ignores mutations. 56 | * - `keys`: only for the keys of the values (replace not supported). 57 | * - `values`: only for the values of the values (new key is index based). 58 | * - `take`: that only iterates over the first X values. 59 | * - `skip`: that skips the first X values. 60 | * - `drop`: that drops off the last X values. 61 | * - `append`: that is the original iterator + one or more iterators specified. 62 | * - `prepend`: that is one or more iterators specified + the original iterator. 63 | * - `gt`: that only has values greater than a value. 64 | * - `gte`: that only has values greater than or equal to a value. 65 | * - `lt`: that only has values less than a value. 66 | * - `lte`: that only has values less than or equal to a value. 67 | * - `fork`: that is this, but allows a function to perform fork operations 68 | * - `split`: Splits the values into two iterators (pass/fail) based on a condition. 69 | * - `unzip`: Splits the view into two iterates (keys/values). 70 | * 71 | * The following functions are used to control comparison logic 72 | * 73 | * - `numbers`: Set number comparison logic to the iterator. 74 | * - `strings`: Set string comparison logic to the iterator. 75 | * - `dates`: Set date comparison logic to the iterator. 76 | * - `desc`: Reverses the comparison logic. 77 | * - `withEquality`: Set a custom equality function. 78 | * - `withComparator`: Set a custom comparison function. 79 | * 80 | * The following function lets you change the source. 81 | * 82 | * - `reset`: Specify a new source to iterate over. 83 | * 84 | * The following static functions exist to help iterate simple sources: 85 | * 86 | * - `array`: Iterates an array. 87 | * - `object`: Iterates the properties of an object, optionally just the properties explicitly set on the object. 88 | * - `tree`: Iterates trees. 89 | * - `linked`: Iterates linked-lists. 90 | * - `map`: Iterates Maps 91 | * - `set`: Iterates Sets 92 | * - `hasEntries`: Iterates any object which has the `entries()` iterator. 93 | * - `empty`: Iterates nothing. 94 | * - `iterable`: Iterates any collection that implements iterable. 95 | * - `join`: Returns an iterator that iterates over one or more iterators. 96 | * - `entries`: Iterates an array of `[key, value]` entries. 97 | * - `zip`: Combines a key iterator and value iterator into one. 98 | * 99 | * @typeparam T The type of value being iterated. 100 | */ 101 | export class Iterate 102 | { 103 | 104 | /** 105 | * A result of the iteration passed to [[Iterate.stop]]. 106 | */ 107 | public result: any = null; 108 | 109 | /** 110 | * The last action (if any) called on this iterator. 111 | */ 112 | public action: IterateAction; 113 | 114 | /** 115 | * The value to replace with the current value. 116 | */ 117 | public replaceWith: T; 118 | 119 | /** 120 | * The current callback passed to the iterator. 121 | */ 122 | public callback: IterateCallback; 123 | 124 | /** 125 | * The source of iterable values. This allows the iteration over any type of 126 | * structure. The source must call the callback for each value and its 127 | * recommended that the source checks the [[Iterate.iterating]] flag after 128 | * each callback invokation. 129 | */ 130 | private source: IterateSource; 131 | 132 | /** 133 | * The equality checker to use for this iterator and subsequent views. 134 | */ 135 | private equality: IterateEquals; 136 | 137 | /** 138 | * The comparator to use for this iterator and subsequent views. 139 | */ 140 | private comparator: IterateCompare; 141 | 142 | /** 143 | * The function to invoke to passing a new source for iteration. 144 | */ 145 | private handleReset: IterateReset; 146 | 147 | /** 148 | * Creates a new Iterate given a source. 149 | * 150 | * @param source The source of values to iterator. 151 | */ 152 | public constructor (source: IterateSource, parent?: Iterate) 153 | { 154 | this.source = source; 155 | 156 | if (parent) 157 | { 158 | this.equality = parent.equality; 159 | this.comparator = parent.comparator; 160 | } 161 | } 162 | 163 | /** 164 | * The function which receives a new source to reset iteration. 165 | * 166 | * @package handleReset The function which takes the new source. 167 | */ 168 | public onReset (handleReset: IterateReset): this 169 | { 170 | this.handleReset = handleReset; 171 | 172 | return this; 173 | } 174 | 175 | /** 176 | * Returns whether the iterator at this point supports a reset. 177 | */ 178 | public canReset (): boolean 179 | { 180 | return !!this.handleReset; 181 | } 182 | 183 | /** 184 | * Sets a new source for iteration if supported. If the iterator doesn't 185 | * support resetting the source then an error will be thrown when `strict` 186 | * is true. 187 | * 188 | * @param source The new source for iteration. 189 | * @param strict If an error should be thrown if the iterator can't be reset. 190 | */ 191 | public reset (source: S, strict: boolean = true): this 192 | { 193 | if (this.handleReset) 194 | { 195 | this.handleReset(source); 196 | } 197 | else if (strict) 198 | { 199 | throw new Error('This iterator does not support reset.'); 200 | } 201 | 202 | return this; 203 | } 204 | 205 | /** 206 | * Returns a clone of this iterator with the same source. This is necessary 207 | * if you want to iterate all or a portion of the source while already 208 | * iterating it (like a nested loop). 209 | */ 210 | public clone (): Iterate 211 | { 212 | return new Iterate( this.source, this ); 213 | } 214 | 215 | /** 216 | * Passes the given value to the iterator callback and returns the action 217 | * requested at this point in iteration. 218 | * 219 | * @param value The current value being iterated. 220 | */ 221 | public act (value: T, key: K): IterateAction 222 | { 223 | this.action = IterateAction.CONTINUE; 224 | this.replaceWith = null; 225 | 226 | this.callback( value, key, this ); 227 | 228 | return this.action; 229 | } 230 | 231 | /** 232 | * Stops iteration and optionally sets the result of the iteration. 233 | * 234 | * @param result The result of the iteration. 235 | */ 236 | public stop (result?: any): this 237 | { 238 | this.result = result; 239 | this.action = IterateAction.STOP; 240 | 241 | return this; 242 | } 243 | 244 | /** 245 | * Returns whether iteration was stopped by the user. 246 | */ 247 | public isStopped (): boolean 248 | { 249 | return this.action === IterateAction.STOP; 250 | } 251 | 252 | /** 253 | * Stops iteration and optionally sets the result of the iteration. 254 | * 255 | * @param result The result of the iteration. 256 | */ 257 | public replace (replaceWith: T): this 258 | { 259 | this.replaceWith = replaceWith; 260 | this.action = IterateAction.REPLACE; 261 | 262 | return this; 263 | } 264 | 265 | /** 266 | * Signals to the iterator source that the current value wants to be removed. 267 | */ 268 | public remove (): this 269 | { 270 | this.action = IterateAction.REMOVE; 271 | 272 | return this; 273 | } 274 | 275 | /** 276 | * Sets the equality logic for this iterator and subsequent views. 277 | * 278 | * @param equality A function to compare two values for equality. 279 | */ 280 | public withEquality (equality: IterateEquals): this 281 | { 282 | this.equality = equality; 283 | 284 | return this; 285 | } 286 | 287 | /** 288 | * Sets the comparison logic for this iterator and subsequent views. If this 289 | * iterator does not have an equality check, this will also use the 290 | * comparator for the equality logic. 291 | * 292 | * @param comparator A function which compares tow values. 293 | */ 294 | public withComparator (comparator: IterateCompare): this 295 | { 296 | this.comparator = comparator; 297 | 298 | if (!this.equality) 299 | { 300 | this.equality = (a, b) => comparator(a, b) === 0; 301 | } 302 | 303 | return this; 304 | } 305 | 306 | /** 307 | * Applies the logic to the iterator. 308 | * 309 | * @param comparator The comparison logic. 310 | * @param equality The equality logic if the comparison logic won't suffice. 311 | */ 312 | private withLogic (comparator: IterateCompare, equality?: IterateEquals): this 313 | { 314 | this.comparator = comparator as unknown as IterateCompare; 315 | this.equality = (equality || ((a: A, b: A, aKey: K, bKey: K) => comparator(a, b, aKey, bKey) === 0)) as unknown as IterateEquals; 316 | 317 | return this; 318 | } 319 | 320 | /** 321 | * Applies number equality and comparison logic to this iterator and 322 | * subsequent ones. 323 | * 324 | * @param ascending If the numbers should be in ascending order. 325 | * @param nullsFirst If non-number values should be ordered first. 326 | */ 327 | public numbers (ascending: boolean = true, nullsFirst: boolean = false): this 328 | { 329 | return this.withLogic(getNumberComparator(ascending, nullsFirst)); 330 | } 331 | 332 | /** 333 | * Applies string equality and comparison logic to this iterator and 334 | * subsequent ones. 335 | * 336 | * @param sensitive If equality logic should be case sensitive. 337 | * @param ascending If the strings should be in ascending order. 338 | * @param nullsFirst If non-strings values should be ordered first. 339 | */ 340 | public strings (sensitive: boolean = true, ascending: boolean = true, nullsFirst: boolean = false): this 341 | { 342 | return this.withLogic(getStringComparator(sensitive, ascending, nullsFirst)); 343 | } 344 | 345 | /** 346 | * Applies date equality and comparison logic to this iterator and 347 | * subsequent ones. 348 | * 349 | * @param equalityTimespan This defines which timespan to dates can lie on 350 | * to be considered equal. By default they have to have the same 351 | * millisecond. If you want dates equal by the following timespans use 352 | * these values: Second = 1000, Minute = 60000, Hour = 3600000, 353 | * Day = 86400000, Week = 604800000 (approx) 354 | * @param utc If when comparing timespans, if we should look at their UTC 355 | * date/time OR if they should be changed to their relative times based 356 | * on their timezone. 357 | * @param ascending If the dates should be in ascending order (oldest first). 358 | * @param nullsFirst If non-date values should be ordered first. 359 | */ 360 | public dates (equalityTimespan: number = 1, utc: boolean = true, ascending: boolean = true, nullsFirst: boolean = false): this 361 | { 362 | return this.withLogic(getDateComparator(ascending, nullsFirst), getDateEquality(equalityTimespan, utc)); 363 | } 364 | 365 | /** 366 | * Reverses the comparator on this iterator and subsequent views. If this 367 | * iterator does not have a comparator this has no affect. 368 | * 369 | * @param comparator An override for any existing comparison logic. 370 | */ 371 | public desc (comparator?: IterateCompare): this 372 | { 373 | const compare = comparator || this.comparator; 374 | 375 | this.comparator = (a, b, aKey, bKey) => compare(b, a, bKey, aKey); 376 | 377 | return this; 378 | } 379 | 380 | /** 381 | * Gets the equality logic desired, optionally overriding the one specified 382 | * on this iterator. 383 | * 384 | * @param equalityOverride Equality logic to use if provided. 385 | */ 386 | public getEquality (equalityOverride?: IterateEquals): IterateEquals 387 | { 388 | return equalityOverride || this.equality || ((a, b) => defaultCompare(a, b, true) === 0); 389 | } 390 | 391 | /** 392 | * Gets the comparison logic desired, optionally overriding the one specified 393 | * on this iterator. If one cannot be determined an error is thrown. 394 | * 395 | * @param comparatorOverride Comparison logic to use if provided. 396 | */ 397 | public getComparator (comparatorOverride?: IterateCompare): IterateCompare 398 | { 399 | return comparatorOverride || this.comparator || ((a, b) => defaultCompare(a, b, false)); 400 | } 401 | 402 | /** 403 | * An operation that determines whether this iterator is empty. 404 | * 405 | * @param setResult A function to pass the result to. 406 | */ 407 | public empty (): boolean 408 | public empty (setResult: IterateResult): this 409 | public empty (setResult?: IterateResult): boolean | this 410 | { 411 | const result = !this.each((value, key, iterator) => iterator.stop()).isStopped(); 412 | 413 | return setResult ? (setResult(result), this) : result; 414 | } 415 | 416 | /** 417 | * An operation that determines whether this iterator has a value. 418 | * 419 | * @param setResult A function to pass the result to. 420 | */ 421 | public has (): boolean 422 | public has (setResult: IterateResult): this 423 | public has (setResult?: IterateResult): boolean | this 424 | { 425 | const result = this.each((value, key, iterator) => iterator.stop()).isStopped(); 426 | 427 | return setResult ? (setResult(result), this) : result; 428 | } 429 | 430 | /** 431 | * An operation that determines whether this iterator has the given value. 432 | * 433 | * @param value The value to search for. 434 | * @param setResult A function to pass the result to. 435 | */ 436 | public contains (value: T): boolean 437 | public contains (value: T, setResult: IterateResult): this 438 | public contains (value: T, setResult?: IterateResult): boolean | this 439 | { 440 | const equality = this.getEquality(); 441 | const result = this.where((other, otherKey) => equality(other, value, otherKey)).has(); 442 | 443 | return setResult ? (setResult(result), this) : result; 444 | } 445 | 446 | /** 447 | * An operation that counts the number of values in the iterator. 448 | * 449 | * @param setResult A function to pass the count to. 450 | */ 451 | public count (): number 452 | public count (setResult: IterateResult): this 453 | public count (setResult?: IterateResult): number | this 454 | { 455 | let result: number = 0; 456 | 457 | this.each(() => result++); 458 | 459 | return setResult ? (setResult(result), this) : result; 460 | } 461 | 462 | /** 463 | * An operation that returns the first value in the iterator. 464 | * 465 | * @param setResult A function to pass the first value to. 466 | */ 467 | public first (): T 468 | public first (setResult: IterateResult): this 469 | public first (setResult?: IterateResult): T | this 470 | { 471 | const result = this.each((value, key, iterator) => iterator.stop(value)).result; 472 | 473 | return setResult ? (setResult(result), this) : result; 474 | } 475 | 476 | /** 477 | * An operation that returns the last value in the iterator. 478 | * 479 | * @param setResult A function to pass the last value to. 480 | */ 481 | public last (): T 482 | public last (setResult: IterateResult): this 483 | public last (setResult?: IterateResult): T | this 484 | { 485 | let result: T = null; 486 | 487 | this.each(value => result = value); 488 | 489 | return setResult ? (setResult(result), this) : result; 490 | } 491 | 492 | /** 493 | * An operation that builds an array of values from the source. 494 | * 495 | * @param out The array to place the values in. 496 | * @param setResult A function to pass the array to. 497 | */ 498 | public array (out?: T[]): T[] 499 | public array (out: T[], setResult: IterateResult): this 500 | public array (setResult: IterateResult): this 501 | public array (outOrResult: T[] | IterateResult = [], onResult?: IterateResult): T[] | this 502 | { 503 | const result = isFunction(outOrResult) ? [] : outOrResult; 504 | const setResult = isFunction(outOrResult) ? outOrResult : onResult; 505 | 506 | this.each(value => result.push( value )); 507 | 508 | return setResult ? (setResult(result), this) : result; 509 | } 510 | 511 | /** 512 | * An operation that builds an array of [key, value] entries from this view. 513 | * 514 | * @param out The array to place the entries in. 515 | * @param setResult A function to pass the entries to. 516 | */ 517 | public entries (out?: Array<[K, T]>): Array<[K, T]> 518 | public entries (out: Array<[K, T]>, setResult: IterateResult>): this 519 | public entries (setResult: IterateResult>): this 520 | public entries (outOrResult: Array<[K, T]> | IterateResult> = [], onResult?: IterateResult>): Array<[K, T]> | this 521 | { 522 | const result = isFunction(outOrResult) ? [] : outOrResult; 523 | const setResult = isFunction(outOrResult) ? outOrResult : onResult; 524 | 525 | this.each((value, key) => result.push([key, value])); 526 | 527 | return setResult ? (setResult(result), this) : result; 528 | } 529 | 530 | /** 531 | * An operation that builds an object of values from the iterator keyed by a 532 | * result returned by a `getKey` function. 533 | * 534 | * @param getKey The function which returns the key of the object. 535 | * @param out The object to place the values in. 536 | * @param setResult A function to pass the object to. 537 | */ 538 | public object (getKey: (value: T) => keyof O, out?: O): O 539 | public object (getKey: (value: T) => keyof O, out: O, setResult: IterateResult): this 540 | public object (getKey: (value: T) => keyof O, setResult: IterateResult): this 541 | public object (getKey: (value: T) => keyof O, outOrResult: O | IterateResult = Object.create(null), onResult?: IterateResult): O | this 542 | { 543 | const result = isFunction(outOrResult) ? Object.create(null) : outOrResult; 544 | const setResult = isFunction(outOrResult) ? outOrResult : onResult; 545 | 546 | this.each(value => result[ getKey( value ) as string ] = value); 547 | 548 | return setResult ? (setResult(result), this) : result; 549 | } 550 | 551 | /** 552 | * An operation that builds a Set of values from the source. 553 | * 554 | * @param out The Set to place the values in. 555 | * @param setResult A function to pass the set to. 556 | */ 557 | public set (out?: Set): Set 558 | public set (out: Set, setResult: IterateResult>): this 559 | public set (setResult: IterateResult>): this 560 | public set (outOrResult: Set | IterateResult> = new Set(), onResult?: IterateResult>): Set | this 561 | { 562 | const result = isFunction(outOrResult) ? new Set() : outOrResult; 563 | const setResult = isFunction(outOrResult) ? outOrResult : onResult; 564 | 565 | this.each(value => result.add( value )); 566 | 567 | return setResult ? (setResult(result), this) : result; 568 | } 569 | 570 | /** 571 | * An operation that builds a Map of key-value pairs from the source. 572 | * 573 | * @param out The Map to place the values in. 574 | * @param setResult A function to pass the map to. 575 | */ 576 | public map (out?: Map): Map 577 | public map (out: Map, setResult: IterateResult>): this 578 | public map (setResult: IterateResult>): this 579 | public map (outOrResult: Map | IterateResult> = new Map(), onResult?: IterateResult>): Map | this 580 | { 581 | const result = isFunction(outOrResult) ? new Map() : outOrResult; 582 | const setResult = isFunction(outOrResult) ? outOrResult : onResult; 583 | 584 | this.each((value, key) => result.set(key, value)); 585 | 586 | return setResult ? (setResult(result), this) : result; 587 | } 588 | 589 | /** 590 | * An operation that returns an object with arrays of values where the 591 | * property of the object is a key returned by a function. 592 | * 593 | * @param by A function to get the key from a value. 594 | * @param out The object to add groups to. 595 | * @param setResult A function to pass the groups to. 596 | */ 597 | public group (by: (value: T) => any, out?: G): G 598 | public group (by: (value: T) => any, out: G, setResult: IterateResult): this 599 | public group (by: (value: T) => any, setResult: IterateResult): this 600 | public group (by: (value: T) => any, outOrResult: G | IterateResult = Object.create(null), onResult?: IterateResult): G | this 601 | { 602 | const result = isFunction(outOrResult) ? Object.create(null) : outOrResult; 603 | const setResult = isFunction(outOrResult) ? outOrResult : onResult; 604 | 605 | this.each(value => 606 | { 607 | const key = by(value); 608 | 609 | if (key in result) 610 | { 611 | result[key].push(value); 612 | } 613 | else 614 | { 615 | result[key] = [value]; 616 | } 617 | }); 618 | 619 | return setResult ? (setResult(result), this) : result; 620 | } 621 | 622 | /** 623 | * An operation that reduces all the values in the source to a single value 624 | * given the initial value and a function to convert a value and the current 625 | * reduced value 626 | * 627 | * @param initial The initial value to pass to the `reducer` function the 628 | * first time. 629 | * @param reducer A function which takes a value in the iterator and the 630 | * current reduced value and returns a new reduced value. 631 | * @param setResult A function to pass the reduced value to. 632 | */ 633 | public reduce (initial: R, reducer: (value: T, reduced: R) => R): R 634 | public reduce (initial: R, reducer: (value: T, reduced: R) => R, setResult: IterateResult): this 635 | public reduce (initial: R, reducer: (value: T, reduced: R) => R, setResult?: IterateResult): R | this 636 | { 637 | let reduced: R = initial; 638 | 639 | this.each(value => reduced = reducer( value, reduced )); 640 | 641 | return setResult ? (setResult(reduced), this) : reduced; 642 | } 643 | 644 | /** 645 | * An operation that returns the minimum value in this iterator. If this 646 | * iterator is empty null is returned. 647 | * 648 | * @param setResult A function to pass the minimum value to. 649 | */ 650 | public min (): T 651 | public min (setResult: IterateResult): this 652 | public min (setResult?: IterateResult): T | this 653 | { 654 | const compare = this.getComparator(); 655 | const result = this.reduce(null, (value, min) => min === null || compare(value, min) < 0 ? value : min); 656 | 657 | return setResult ? (setResult(result), this) : result; 658 | } 659 | 660 | /** 661 | * An operation that returns the maximum value in this iterator. If this 662 | * iterator is empty null is returned. 663 | * 664 | * @param setResult A function to pass the maximum value to. 665 | */ 666 | public max (): T 667 | public max (setResult: IterateResult): this 668 | public max (setResult?: IterateResult): T | this 669 | { 670 | const compare = this.getComparator(); 671 | const result = this.reduce(null, (value, max) => max === null || compare(value, max) > 0 ? value : max); 672 | 673 | return setResult ? (setResult(result), this) : result; 674 | } 675 | 676 | /** 677 | * A map of key-value pairs stored from the last time `changes` was invoked. 678 | * 679 | * The keys are the value in the iterator or a dynamically created value 680 | * returned by the `getIdentifier` function. If that function is provided 681 | * once it must always be provided to ensure correct change detection. 682 | */ 683 | protected history: Map; 684 | 685 | /** 686 | * An operation which determines which changes have occurred in the source 687 | * since the last time the changes operation was called. The changes 688 | * operation needs to be called on the same exact iterator instance to 689 | * properly track changes. You should avoid sharing an iterator or using 690 | * reset for an iterator that you're using to track changes. 691 | * 692 | * Optionally you can provide a `getIdentifier` function which can convert 693 | * a value into a more optimal value for comparison. The value returned 694 | * will be compared by reference so a scalar value (number, string, etc) 695 | * is ideal but other identifiers can be returned as long as they are 696 | * the same reference and not dynamically generated. 697 | * 698 | * The first time this operation is performed all the values in the iterator 699 | * will be passed through the `onAdd` function. 700 | * 701 | * The `onRemove` function is only called at the very end of the changes 702 | * logic. 703 | * 704 | * @param onAdd The function to invoke for each value added since the 705 | * last `changes` operation, 706 | * @param onRemove The function to invoke for each value removed since the 707 | * last `changes` operation. This function is called zero or more times 708 | * at the end of the changes logic. 709 | * @param onPresent The function to invoke for each value that was in the 710 | * iterator before and is still in the iterator. 711 | * @param getIdentifier A function to use to create a simpler way to identify 712 | * a value. The simpler the value returned the better the performance 713 | * of the changes logic. If this function is passed once, it should be 714 | * passed everytime or the results of this function will not be accurate. 715 | */ 716 | public changes ( 717 | onAdd: IterateCallback, 718 | onRemove: IterateCallback, 719 | onPresent: IterateCallback, 720 | getIdentifier?: IterateCallback): this 721 | { 722 | if (!this.history) 723 | { 724 | this.history = new Map(); 725 | } 726 | 727 | const notRemoved = new Map(this.history); 728 | 729 | this.each((value, key, iterator) => 730 | { 731 | const mapKey = getIdentifier ? getIdentifier(value, key, iterator) : value; 732 | 733 | if (this.history.has(mapKey)) 734 | { 735 | onPresent(value, key, iterator); 736 | } 737 | else 738 | { 739 | onAdd(value, key, iterator); 740 | 741 | if (iterator.result !== IterateAction.REMOVE) 742 | { 743 | this.history.set(mapKey, [key, value]); 744 | } 745 | } 746 | 747 | notRemoved.delete(mapKey); 748 | }); 749 | 750 | for (const [key, value] of notRemoved.values()) 751 | { 752 | onRemove(value, key, this); 753 | } 754 | 755 | return this; 756 | } 757 | 758 | /** 759 | * A mutation which removes values in this iterator from the source. 760 | */ 761 | public delete (): this 762 | { 763 | return this.each((value, key, iterator) => iterator.remove()); 764 | } 765 | 766 | /** 767 | * A mutation which removes values in this iterator from the source and 768 | * returns a new iterator with the removed values. 769 | */ 770 | public extract (): Iterate> 771 | public extract (setResult: IterateResult>>): this 772 | public extract (setResult?: IterateResult>>): Iterate> | this 773 | { 774 | const extracted: Array<[K, T]> = []; 775 | 776 | this.each((value, key, iterator) => extracted.push([key, value]) && iterator.remove()); 777 | 778 | const result = Iterate.entries(extracted); 779 | 780 | return setResult ? (setResult(result), this) : result; 781 | } 782 | 783 | /** 784 | * A mutation which replaces values in this view with a single given value. 785 | * 786 | * @param replacement The value to replace for all the values in this iterator. 787 | */ 788 | public overwrite (replacement: T): this 789 | { 790 | return this.each((value, key, iterator) => iterator.replace(replacement)); 791 | } 792 | 793 | /** 794 | * A mutation which replaces values in this view with a dynamically created one. 795 | * 796 | * @param updater A function which given a value and key returns a replacement value. 797 | */ 798 | public update (updater: (value: T, key: K) => T): this 799 | { 800 | return this.each((value, key, iterator) => iterator.replace(updater(value, key))); 801 | } 802 | 803 | /** 804 | * Forks this view into another and returns a reference to this view. 805 | * This allows chaining of multiple views which each perform a different 806 | * operation or mutation. Forks are executed sequentially, so if one fork 807 | * performs mutations the subsequent forks will see the mutated values. 808 | * 809 | * @param forker A function which takes the iterator at this point and 810 | * performs any mutations and operations. 811 | */ 812 | public fork (forker: (fork: this) => any): this 813 | { 814 | forker(this); 815 | 816 | return this; 817 | } 818 | 819 | /** 820 | * Provides split views of the values in this iterator, one iterator gets 821 | * passed values and the other iterator gets the failed values. 822 | * 823 | * You can pass a function as a second argument which recieves two iterators 824 | * for pass and fail respectively. This will be returned in that scenario. 825 | * 826 | * If you don't pass a second function an object will be returned with two 827 | * properties: pass and fail. 828 | */ 829 | public split (pass: IterateFilter): { pass: Iterate, fail: Iterate }; 830 | public split (pass: IterateFilter, handle: (pass: Iterate, fail: Iterate) => any): this 831 | public split (by: IterateFilter, handle?: (pass: Iterate, fail: Iterate) => any): any 832 | { 833 | const pass = this.where(by); 834 | const fail = this.not(by); 835 | 836 | if (handle) 837 | { 838 | handle(pass, fail); 839 | 840 | return this; 841 | } 842 | 843 | return { pass, fail }; 844 | } 845 | 846 | /** 847 | * Unzips the view into a keys and values views. 848 | * 849 | * You can pass a function as a second argument which recieves two iterators 850 | * for keys and values respectively. This will be returned in that scenario. 851 | * 852 | * If you don't pass a second function an object will be returned with two 853 | * properties: keys and values. 854 | */ 855 | public unzip (): { keys: Iterate, values: Iterate }; 856 | public unzip (handle: (keys: Iterate, values: Iterate) => any): this 857 | public unzip (handle?: (keys: Iterate, values: Iterate) => any): any 858 | { 859 | const keys = this.keys(); 860 | const values = this.values(); 861 | 862 | if (handle) 863 | { 864 | handle(keys, values); 865 | 866 | return this; 867 | } 868 | 869 | return { keys, values }; 870 | } 871 | 872 | /** 873 | * Returns a view of just the keys in this view. Any mutations done to the 874 | * keys view affect the underlying source. 875 | */ 876 | public keys (): Iterate 877 | { 878 | return new Iterate(next => 879 | { 880 | let index = 0; 881 | 882 | this.each((value, key, prev) => 883 | { 884 | switch (next.act( key, index++ )) 885 | { 886 | case IterateAction.STOP: 887 | prev.stop(); 888 | break; 889 | case IterateAction.REMOVE: 890 | prev.remove(); 891 | break; 892 | case IterateAction.REPLACE: 893 | // not supported 894 | break; 895 | } 896 | }); 897 | }).onReset(this.handleReset); 898 | } 899 | 900 | /** 901 | * Returns a view of just the values in this view. Any mutations done to the 902 | * values view affect the underlying source. 903 | */ 904 | public values (): Iterate 905 | { 906 | return new Iterate(next => 907 | { 908 | this.each((value, key, prev) => 909 | { 910 | let index = 0; 911 | 912 | switch (next.act( value, index++ )) 913 | { 914 | case IterateAction.STOP: 915 | prev.stop(); 916 | break; 917 | case IterateAction.REMOVE: 918 | prev.remove(); 919 | break; 920 | case IterateAction.REPLACE: 921 | prev.replace( next.replaceWith ); 922 | break; 923 | } 924 | }); 925 | 926 | }, this).onReset(this.handleReset); 927 | } 928 | 929 | /** 930 | * Returns a view of this iterator. This allows other views to be more DRY. 931 | * 932 | * @param getData Get any necessary data needed for the view. 933 | * @param shouldAct Based on the data and the value, should we act on it? 934 | * @param afterAct What to do if the value was acted on. 935 | * @param afterSkip What to do if the value was NOT acted on. 936 | */ 937 | public view( 938 | getData: () => D, 939 | shouldAct: (data: D, value: T, key: K) => any, 940 | afterAct?: (data: D, value: T, key: K, iter: Iterate) => void, 941 | afterSkip?: (data: D, value: T, key: K, iter: Iterate) => void): Iterate 942 | { 943 | return new Iterate(next => 944 | { 945 | const data = getData(); 946 | 947 | this.each((value, key, prev) => 948 | { 949 | if (shouldAct(data, value, key)) 950 | { 951 | switch (next.act( value, key )) 952 | { 953 | case IterateAction.STOP: 954 | prev.stop(); 955 | break; 956 | case IterateAction.REMOVE: 957 | prev.remove(); 958 | break; 959 | case IterateAction.REPLACE: 960 | prev.replace( next.replaceWith ); 961 | break; 962 | } 963 | 964 | if (afterAct) 965 | { 966 | afterAct(data, value, key, prev); 967 | } 968 | } 969 | else if (afterSkip) 970 | { 971 | afterSkip(data, value, key, prev); 972 | } 973 | }); 974 | 975 | }, this).onReset(this.handleReset); 976 | } 977 | 978 | /** 979 | * Returns a view that only returns a maximum number of values. 980 | * 981 | * @param amount The maximum number of values to return. 982 | */ 983 | public take (amount: number): Iterate 984 | { 985 | if (amount <= 0) 986 | { 987 | return Iterate.empty(); 988 | } 989 | 990 | return this.view<{ amount: number }>( 991 | () => ({ amount }), 992 | (data) => data.amount > 0, 993 | (data) => data.amount--, 994 | (data, value, key, iter) => iter.stop() 995 | ); 996 | } 997 | 998 | /** 999 | * Returns a view that skips the given number of values from the values 1000 | * in this iterator. 1001 | * 1002 | * @param amount The number of values to skip. 1003 | */ 1004 | public skip (amount: number): Iterate 1005 | { 1006 | return this.view<{ skipped: number }>( 1007 | () => ({ skipped: 0 }), 1008 | (data) => data.skipped >= amount, 1009 | (data) => data.skipped++, 1010 | (data) => data.skipped++ 1011 | ); 1012 | } 1013 | 1014 | /** 1015 | * Returns a view that drops the given number of values from the end of the 1016 | * values in this iterator. 1017 | * 1018 | * @param amount The number of values to drop from the end. 1019 | */ 1020 | public drop (amount: number): Iterate 1021 | { 1022 | return this.reverse().skip(amount).reverse(); 1023 | } 1024 | 1025 | /** 1026 | * Returns a view thats values are the values in this iterator followed 1027 | * by the values in the given iterators. 1028 | * 1029 | * @param iterators The iterators to append after this one. 1030 | */ 1031 | public append (...sources: S[]): Iterate; 1032 | public append (...sources: IterateSourceTypeKey[]): Iterate; 1033 | public append (...sources: IterateSourceType[]): Iterate; 1034 | public append (...sources: IterateSourceType[]): Iterate; 1035 | public append (...sources: any[]): Iterate 1036 | { 1037 | return Iterate.join( this, ...sources ).onReset(this.handleReset); 1038 | } 1039 | 1040 | /** 1041 | * Returns a view thats values are the values in the given iterators 1042 | * followed by the values in this iterator. 1043 | * 1044 | * @param iterators The iterators to prepend before this one. 1045 | */ 1046 | public prepend (...sources: S[]): Iterate; 1047 | public prepend (...sources: IterateSourceTypeKey[]): Iterate; 1048 | public prepend (...sources: IterateSourceType[]): Iterate; 1049 | public prepend (...sources: IterateSourceType[]): Iterate; 1050 | public prepend (...sources: any[]): Iterate 1051 | { 1052 | return Iterate.join( ...sources, this ).onReset(this.handleReset); 1053 | } 1054 | 1055 | /** 1056 | * Returns a view of values in this iterator which pass a `where` function. 1057 | * 1058 | * @param where The function which determines if a value should be iterated. 1059 | */ 1060 | public where (where: IterateFilter): Iterate 1061 | { 1062 | return this.view( 1063 | () => null, 1064 | (data, value, key) => where(value, key) 1065 | ); 1066 | } 1067 | 1068 | /** 1069 | * Returns a view of values in this iterator which do NOT pass a `not` function. 1070 | * 1071 | * @param not The function which determines if a value should be iterated. 1072 | */ 1073 | public not (not: IterateFilter): Iterate 1074 | { 1075 | return this.view( 1076 | () => null, 1077 | (data, value, key) => !not(value, key) 1078 | ); 1079 | } 1080 | 1081 | /** 1082 | * Returns a view where all values are greater than the given value. 1083 | * If a comparator is not on this iterator or provided an error is thrown. 1084 | * 1085 | * @param threshold The value to compare against. 1086 | * @param comparator An override for any existing comparison logic. 1087 | */ 1088 | public gt (threshold: T, comparator?: IterateCompare): Iterate 1089 | { 1090 | return this.view>( 1091 | () => this.getComparator(comparator), 1092 | (compare, value, key) => compare(value, threshold, key) > 0 1093 | ); 1094 | } 1095 | 1096 | /** 1097 | * Returns a view where all values are greater than or equal to the given value. 1098 | * If a comparator is not on this iterator or provided an error is thrown. 1099 | * 1100 | * @param threshold The value to compare against. 1101 | * @param comparator An override for any existing comparison logic. 1102 | */ 1103 | public gte (threshold: T, comparator?: IterateCompare): Iterate 1104 | { 1105 | return this.view>( 1106 | () => this.getComparator(comparator), 1107 | (compare, value, key) => compare(value, threshold, key) >= 0 1108 | ); 1109 | } 1110 | 1111 | /** 1112 | * Returns a view where all values are less than the given value. 1113 | * If a comparator is not on this iterator or provided an error is thrown. 1114 | * 1115 | * @param threshold The value to compare against. 1116 | * @param comparator An override for any existing comparison logic. 1117 | */ 1118 | public lt (threshold: T, comparator?: IterateCompare): Iterate 1119 | { 1120 | return this.view>( 1121 | () => this.getComparator(comparator), 1122 | (compare, value, key) => compare(value, threshold, key) < 0 1123 | ); 1124 | } 1125 | 1126 | /** 1127 | * Returns a view where all values are less than or equal to the given value. 1128 | * If a comparator is not on this iterator or provided an error is thrown. 1129 | * 1130 | * @param threshold The value to compare against. 1131 | * @param comparator An override for any existing comparison logic. 1132 | */ 1133 | public lte (threshold: T, comparator?: IterateCompare): Iterate 1134 | { 1135 | return this.view>( 1136 | () => this.getComparator(comparator), 1137 | (compare, value, key) => compare(value, threshold, key) <= 0 1138 | ); 1139 | } 1140 | 1141 | /** 1142 | * Returns a view of this iterator which does not include the values in the 1143 | * given iterator. 1144 | * 1145 | * @param source The source of values to exclude. 1146 | * @param equality An override for any existing equality logic. 1147 | */ 1148 | public exclude (source: IterateSourceType, equality?: IterateEquals): Iterate; 1149 | public exclude (source: any, equality?: IterateEquals): Iterate 1150 | { 1151 | return this.view>( 1152 | () => iterate(source).withEquality(this.getEquality(equality)), 1153 | (values, value) => !values.contains(value) 1154 | ); 1155 | } 1156 | 1157 | /** 1158 | * Returns a view which has values which are in this iterator and the given 1159 | * iterator. 1160 | * 1161 | * @param source The source of values to intersect with. 1162 | * @param equality An override for any existing equality logic. 1163 | */ 1164 | public intersect (source: IterateSourceType, equality?: IterateEquals): Iterate; 1165 | public intersect (source: any, equality?: IterateEquals): Iterate 1166 | { 1167 | return this.view>( 1168 | () => iterate(source).withEquality(this.getEquality(equality)), 1169 | (values, value) => values.contains(value) 1170 | ); 1171 | } 1172 | 1173 | /** 1174 | * Returns a view which only contains unique values. 1175 | * 1176 | * @param equality An override for any existing equality logic. 1177 | */ 1178 | public unique (equality?: IterateEquals): Iterate 1179 | { 1180 | return this.view<{ existing: Array<[K, T]>, isEqual: IterateEquals }>( 1181 | () => ({ existing: [], isEqual: this.getEquality(equality) }), 1182 | ({existing, isEqual}, value, key) => existing.findIndex(([existKey, exist]) => isEqual(exist, value, existKey, key)) === -1, 1183 | ({existing}, value, key) => existing.push([key, value]) 1184 | ); 1185 | } 1186 | 1187 | /** 1188 | * Returns a view which only contains values that have duplicates in this 1189 | * iterator. For any values that occur more than twice you can exclude them 1190 | * from the resulting view by passing `true` to `onlyOnce`. 1191 | * 1192 | * @param onlyOnce If the view should contain unique or all duplicates. 1193 | * @param equality An override for any existing equality logic. 1194 | */ 1195 | public duplicates (onlyOnce: boolean = false, equality?: IterateEquals): Iterate 1196 | { 1197 | return this.view<{ existing: Array<[K, T]>, once: boolean[], isEqual: IterateEquals }>( 1198 | () => ({ existing: [], once: [], isEqual: this.getEquality(equality) }), 1199 | ({existing, once, isEqual}, value, key) => { 1200 | const index = existing.findIndex(([existKey, exist]) => isEqual(exist, value, existKey, key)); 1201 | let act = index !== -1; 1202 | 1203 | if (act) { 1204 | if (once[index] && onlyOnce) { 1205 | act = false; 1206 | } 1207 | once[index] = true; 1208 | } else { 1209 | existing.push([key, value]); 1210 | } 1211 | 1212 | return act; 1213 | } 1214 | ); 1215 | } 1216 | 1217 | /** 1218 | * Returns a readonly view where mutations have no affect. 1219 | */ 1220 | public readonly (): Iterate 1221 | { 1222 | return new Iterate(next => 1223 | { 1224 | this.each((value, key, prev) => 1225 | { 1226 | if (next.act( value, key ) === IterateAction.STOP) 1227 | { 1228 | prev.stop() 1229 | } 1230 | }); 1231 | 1232 | }, this).onReset(this.handleReset); 1233 | } 1234 | 1235 | /** 1236 | * Returns a copy of the values in this view as a new iterator. 1237 | */ 1238 | public copy (): Iterate> 1239 | { 1240 | return Iterate.entries(this.entries()); 1241 | } 1242 | 1243 | /** 1244 | * Returns a view which requires a fully resolved array of values. The view 1245 | * must keep track of the original value index in order to ensure removals 1246 | * and replaces can be performed on the source. 1247 | * 1248 | * @param onResolve 1249 | */ 1250 | public viewResolved(onResolve: (values: Array<[K, T]>, handleAct: (value: T, key: K, index: number) => IterateAction) => void): Iterate 1251 | { 1252 | return new Iterate(next => 1253 | { 1254 | const values: Array<[K, T]> = this.entries(); 1255 | const actions: IterateAction[] = []; 1256 | const replaces: T[] = []; 1257 | const original: T[] = []; 1258 | 1259 | let mutates: boolean = false; 1260 | 1261 | onResolve(values, (value, key, index) => 1262 | { 1263 | const action = next.act(value, key); 1264 | 1265 | if (action === IterateAction.REPLACE || action === IterateAction.REMOVE) 1266 | { 1267 | mutates = true; 1268 | original[ index ] = value; 1269 | actions[ index ] = action; 1270 | replaces[ index ] = next.replaceWith; 1271 | } 1272 | 1273 | return action; 1274 | }); 1275 | 1276 | if (mutates) 1277 | { 1278 | let index: number = 0; 1279 | 1280 | this.each((value, key, modifyIterate) => 1281 | { 1282 | switch (actions[ index ]) 1283 | { 1284 | case IterateAction.REMOVE: 1285 | if (value === original[index]) { 1286 | modifyIterate.remove(); 1287 | } 1288 | break; 1289 | 1290 | case IterateAction.REPLACE: 1291 | if (value === original[index]) { 1292 | modifyIterate.replace( replaces[ index ] ); 1293 | } 1294 | break; 1295 | } 1296 | 1297 | index++; 1298 | }); 1299 | } 1300 | 1301 | }, this).onReset(this.handleReset); 1302 | } 1303 | 1304 | /** 1305 | * Returns a view which has the values sorted. 1306 | * 1307 | * @param comparator An override for any existing comparison logic. 1308 | */ 1309 | public sorted (comparator?: IterateCompare): Iterate 1310 | { 1311 | return this.viewResolved((values, handleAct) => 1312 | { 1313 | const compare = this.getComparator(comparator); 1314 | const mapped = values.map(([key, value], index) => ({ key, value, index })); 1315 | 1316 | mapped.sort((a, b) => compare(a.value, b.value)); 1317 | 1318 | for (const {key, value, index} of mapped) 1319 | { 1320 | if (handleAct(value, key, index) === IterateAction.STOP) 1321 | { 1322 | return; 1323 | } 1324 | } 1325 | }); 1326 | } 1327 | 1328 | /** 1329 | * Returns an view of values in this iterator and presents them in a random order. 1330 | */ 1331 | public shuffle (passes: number = 1): Iterate 1332 | { 1333 | const swap = (arr: X[], i: number, k: number) => { 1334 | const t = arr[i]; 1335 | arr[i] = arr[k]; 1336 | arr[k] = t; 1337 | } 1338 | 1339 | return this.viewResolved((values, handleAct) => 1340 | { 1341 | const indices: number[] = []; 1342 | const n = values.length; 1343 | 1344 | for (let i = 0; i < n; i++) 1345 | { 1346 | indices.push(i); 1347 | } 1348 | 1349 | for (let pass = 0; pass < passes; pass++) 1350 | { 1351 | for (let k = 0; k < n; k++) 1352 | { 1353 | const j = Math.floor(Math.random() * n); 1354 | swap(values, j, k); 1355 | swap(indices, j, k); 1356 | } 1357 | } 1358 | 1359 | for (let i = 0; i < n; i++) 1360 | { 1361 | const [key, value] = values[i]; 1362 | 1363 | if (handleAct(value, key, indices[i]) === IterateAction.STOP) 1364 | { 1365 | return; 1366 | } 1367 | } 1368 | }); 1369 | } 1370 | 1371 | /** 1372 | * Returns an view of values in this iterator and presents them in reverse. 1373 | */ 1374 | public reverse (): Iterate 1375 | { 1376 | return this.viewResolved((values, handleAct) => 1377 | { 1378 | for (let i = values.length - 1; i >= 0; i--) 1379 | { 1380 | const [key, value] = values[i]; 1381 | 1382 | if (handleAct(value, key, i) === IterateAction.STOP) 1383 | { 1384 | return; 1385 | } 1386 | } 1387 | }); 1388 | } 1389 | 1390 | /** 1391 | * Returns an iterator where this iterator is the source and the returned 1392 | * iterator is built from transformed values pulled from values in the source 1393 | * of this iterator. 1394 | * 1395 | * @param transformer The function which transforms a value to another type. 1396 | * @param untransformer The function which untransforms a value when replace is called. 1397 | */ 1398 | public transform(transformer: IterateCallback, 1399 | untransformer: (replaceWith: W, current: W, value: T, key: K) => T = null): Iterate 1400 | { 1401 | return new Iterate(next => 1402 | { 1403 | this.each((prevItem, prevKey, prev) => 1404 | { 1405 | const nextItem: W = transformer( prevItem, prevKey, prev ); 1406 | 1407 | if (typeof nextItem !== 'undefined') 1408 | { 1409 | switch (next.act( nextItem, prevKey )) 1410 | { 1411 | case IterateAction.STOP: 1412 | prev.stop(); 1413 | break; 1414 | case IterateAction.REMOVE: 1415 | prev.remove(); 1416 | break; 1417 | case IterateAction.REPLACE: 1418 | if (untransformer) { 1419 | prev.replace( untransformer( next.replaceWith, nextItem, prevItem, prevKey ) ); 1420 | } 1421 | break; 1422 | } 1423 | } 1424 | }); 1425 | }).onReset(this.handleReset); 1426 | } 1427 | 1428 | /** 1429 | * Invokes the callback for each value in the source of this iterator. The 1430 | * second argument in the callback is the reference to this iterator and 1431 | * [[Iterate.stop]] can be called at anytime to cease iteration. 1432 | * 1433 | * @param callback The function to invoke for each value in this iterator. 1434 | */ 1435 | public each (callback: IterateCallback): this 1436 | { 1437 | this.result = undefined; 1438 | this.callback = callback; 1439 | this.action = IterateAction.CONTINUE; 1440 | this.source( this ); 1441 | this.callback = null; 1442 | 1443 | return this; 1444 | } 1445 | 1446 | /** 1447 | * Passes the result of the iteration to the given function if a truthy 1448 | * result was passed to [[Iterate.stop]]. 1449 | * 1450 | * @param getResult The function to pass the result to if it exists. 1451 | */ 1452 | public withResult (getResult: (result: any) => any): this 1453 | { 1454 | if (this.result) 1455 | { 1456 | getResult( this.result ); 1457 | } 1458 | 1459 | return this; 1460 | } 1461 | 1462 | /** 1463 | * This allows for...of loops to be used on this iterator. 1464 | */ 1465 | [Symbol.iterator] (): Iterator 1466 | { 1467 | return this.array().values(); 1468 | } 1469 | 1470 | /** 1471 | * Returns an iterator for the given array. 1472 | * 1473 | * @param values The array of values to iterate. 1474 | * @returns A new iterator for the given array. 1475 | */ 1476 | public static entries (values: Array<[K, T]> = []): Iterate> 1477 | { 1478 | return new Iterate>(iterator => 1479 | { 1480 | for (let i = 0; i < values.length; i++) 1481 | { 1482 | const [key, value] = values[ i ]; 1483 | 1484 | switch (iterator.act(value, key)) 1485 | { 1486 | case IterateAction.STOP: 1487 | return; 1488 | case IterateAction.REMOVE: 1489 | values.splice(i, 1); 1490 | i--; 1491 | break; 1492 | case IterateAction.REPLACE: 1493 | values.splice(i, 1, [key, iterator.replaceWith]); 1494 | break; 1495 | } 1496 | } 1497 | }).onReset(source => values = source); 1498 | } 1499 | 1500 | /** 1501 | * Returns an iterator for the given array. 1502 | * 1503 | * @param values The array of values to iterate. 1504 | * @returns A new iterator for the given array. 1505 | */ 1506 | public static array (values: T[] = []): Iterate 1507 | { 1508 | return new Iterate(iterator => 1509 | { 1510 | for (let i = 0; i < values.length; i++) 1511 | { 1512 | switch (iterator.act(values[ i ], i)) 1513 | { 1514 | case IterateAction.STOP: 1515 | return; 1516 | case IterateAction.REMOVE: 1517 | values.splice(i, 1); 1518 | i--; 1519 | break; 1520 | case IterateAction.REPLACE: 1521 | values.splice(i, 1, iterator.replaceWith); 1522 | break; 1523 | } 1524 | } 1525 | }).onReset(source => values = source); 1526 | } 1527 | 1528 | /** 1529 | * Returns an iterator for the keys and values specified. If the key and 1530 | * value iterators don't have the same number of values, the returned iterator 1531 | * will have the maximum pairs possible (which is the lesser of the number 1532 | * of keys and values). 1533 | * 1534 | * If the returned iterator is mutated the given keys and values iterators 1535 | * will be mutated as well. If you want to avoid that, pass in readonly 1536 | * key/value iterators. 1537 | * 1538 | * @param keys The iterator to obtain the keys from. 1539 | * @param values The iterator to obtain the values from. 1540 | */ 1541 | public static zip, S, T extends GetValueFor>(keySource: J, valueSource: S): Iterate 1542 | { 1543 | let keyIterator = iterate(keySource) as unknown as Iterate, J>; 1544 | let valueIterator = iterate(valueSource) as unknown as Iterate, S>; 1545 | 1546 | return new Iterate(next => 1547 | { 1548 | const keysArray = keyIterator.array(); 1549 | const removeKeyAt: number[] = []; 1550 | let valuesIndex = 0; 1551 | 1552 | valueIterator.each((value, ignoreKey, prev) => 1553 | { 1554 | if (valuesIndex >= keysArray.length) 1555 | { 1556 | prev.stop(); 1557 | } 1558 | else 1559 | { 1560 | switch (next.act(value, keysArray[valuesIndex])) 1561 | { 1562 | case IterateAction.STOP: 1563 | return; 1564 | case IterateAction.REMOVE: 1565 | prev.remove(); 1566 | removeKeyAt.push(valuesIndex); 1567 | break; 1568 | case IterateAction.REPLACE: 1569 | prev.replace(next.replaceWith); 1570 | break; 1571 | } 1572 | } 1573 | 1574 | valuesIndex++; 1575 | }); 1576 | 1577 | if (removeKeyAt.length > 0) 1578 | { 1579 | let keysIndex = 0; 1580 | 1581 | keyIterator.each((key, ignoreKey, prev) => 1582 | { 1583 | if (keysIndex === removeKeyAt[0]) 1584 | { 1585 | prev.remove(); 1586 | removeKeyAt.shift(); 1587 | } 1588 | else if (removeKeyAt.length === 0) 1589 | { 1590 | prev.stop(); 1591 | } 1592 | 1593 | keysIndex++; 1594 | }); 1595 | } 1596 | }).onReset(([keys, values]: [J, S]) => 1597 | { 1598 | keyIterator = iterate(keys) as unknown as Iterate, J>; 1599 | valueIterator = iterate(values) as unknown as Iterate, S>; 1600 | }); 1601 | } 1602 | 1603 | /** 1604 | * Returns an iterator for any object which has an entries() iterable. 1605 | * 1606 | * @param hasEntries The object with the entries() iterable. 1607 | * @param onRemove The function that should handle removing a key/value. 1608 | * @param onReplace The function that should handle replacing a value. 1609 | */ 1610 | public static hasEntries> ( 1611 | hasEntries?: E, 1612 | onRemove?: (entries: E, key: K, value: T) => any, 1613 | onReplace?: (entries: E, key: K, value: T, newValue: T) => any): Iterate 1614 | { 1615 | return new Iterate(iterator => 1616 | { 1617 | const iterable = hasEntries.entries(); 1618 | 1619 | for (let next = iterable.next(); !next.done; next = iterable.next()) 1620 | { 1621 | const [key, value] = next.value; 1622 | 1623 | switch (iterator.act(value, key)) 1624 | { 1625 | case IterateAction.STOP: 1626 | return; 1627 | case IterateAction.REMOVE: 1628 | if (onRemove) { 1629 | onRemove(hasEntries, key, value); 1630 | } 1631 | break; 1632 | case IterateAction.REPLACE: 1633 | if (onReplace) { 1634 | onReplace(hasEntries, key, value, iterator.replaceWith); 1635 | } 1636 | break; 1637 | } 1638 | } 1639 | }).onReset(source => hasEntries = source); 1640 | } 1641 | 1642 | /** 1643 | * Returns an iterator for the given Map. 1644 | * 1645 | * @param values The Map of key-value pairs to iterate. 1646 | * @returns A new iterator for the given Map. 1647 | */ 1648 | public static map (values: Map = new Map()): Iterate> 1649 | { 1650 | return Iterate.hasEntries>( 1651 | values, 1652 | (map, key) => map.delete(key), 1653 | (map, key, value, newValue) => map.set(key, newValue) 1654 | ); 1655 | } 1656 | 1657 | /** 1658 | * Returns an iterator for the given Set. 1659 | * 1660 | * @param values The Set of values to iterate. 1661 | * @returns A new iterator for the given Set. 1662 | */ 1663 | public static set (values: Set = new Set()): Iterate> 1664 | { 1665 | return Iterate.hasEntries>( 1666 | values, 1667 | (set, key) => set.delete(key), 1668 | (set, key, value, newValue) => values.delete(value) && values.add(newValue) 1669 | ); 1670 | } 1671 | 1672 | /** 1673 | * Returns an iterator for any iterable. Because iterables don't support 1674 | * 1675 | * @param values The iterable collection. 1676 | * @returns A new iterator for the given set. 1677 | */ 1678 | public static iterable (values: Iterable): Iterate> 1679 | { 1680 | return new Iterate>(iterator => 1681 | { 1682 | const iterable = values[Symbol.iterator](); 1683 | let index = 0; 1684 | 1685 | for (let next = iterable.next(); !next.done; next = iterable.next(), index++) 1686 | { 1687 | if (iterator.act(next.value, index) === IterateAction.STOP) 1688 | { 1689 | break; 1690 | } 1691 | } 1692 | }).onReset(source => values = source); 1693 | } 1694 | 1695 | /** 1696 | * Returns an iterator for the given object optionally checking the 1697 | * `hasOwnProperty` function on the given object. 1698 | * 1699 | * @param values The object to iterate. 1700 | * @param hasOwnProperty If `hasOwnProperty` should be checked. 1701 | * @returns A new iterator for the given object. 1702 | */ 1703 | public static object (values: { [key: string]: T }, hasOwnProperty: boolean = true): Iterate 1704 | { 1705 | return new Iterate(iterator => 1706 | { 1707 | for (const key in values) 1708 | { 1709 | if (hasOwnProperty && !values.hasOwnProperty( key )) 1710 | { 1711 | continue; 1712 | } 1713 | 1714 | switch (iterator.act(values[ key ], key)) 1715 | { 1716 | case IterateAction.STOP: 1717 | return; 1718 | case IterateAction.REMOVE: 1719 | delete values[ key ]; 1720 | break; 1721 | case IterateAction.REPLACE: 1722 | values[ key ] = iterator.replaceWith; 1723 | break; 1724 | } 1725 | } 1726 | }).onReset(source => values = source); 1727 | } 1728 | 1729 | /** 1730 | * Returns a function for iterating over a linked-list. You pass a node to 1731 | * the function returned (and whether it should be strict) and an iterator 1732 | * will be returned. 1733 | * 1734 | * @param getValue A function which gets a value from a node. 1735 | * @param getNext A function which returns the next node. When at the end 1736 | * list null or undefined should be returned. 1737 | * @param remove A function which handles a remove request. If this is not 1738 | * specified and a remove is called and `strict` is true an error will be 1739 | * thrown. 1740 | * @param replaceValue A function which applies a value to a node. If this is 1741 | * not specified and a replace is called and `strict` is true an error 1742 | * will be thrown. 1743 | */ 1744 | public static linked ( 1745 | getValue: (node: N) => T, 1746 | getNext: (node: N) => N | undefined | null, 1747 | remove?: (node: N, prev: N) => any, 1748 | replaceValue?: (node: N, value: T) => any, 1749 | getKey?: (node: N) => K) 1750 | { 1751 | if (!getKey) 1752 | { 1753 | getKey = (node) => getValue(node) as unknown as K; 1754 | } 1755 | 1756 | /** 1757 | * Allows iteration of a linked list starting at a node. 1758 | * 1759 | * If `strict` is true and a remove/replace is requested that can not be 1760 | * done then an error will be thrown. 1761 | * 1762 | * @param previousNode The previous node. This is necessary of the 1763 | * starting node is requested to be removed, the user still needs a 1764 | * reference to the first node. 1765 | * @param strict If an error should be thrown when an unsupported action is 1766 | * requested. 1767 | */ 1768 | return function linkedIterator(previousNode?: N, strict: boolean = true) 1769 | { 1770 | return new Iterate(iterator => 1771 | { 1772 | let prev: N = previousNode; 1773 | let curr: N | undefined | null = getNext(previousNode); 1774 | 1775 | while (curr && curr !== previousNode) 1776 | { 1777 | const next = getNext(curr); 1778 | let removed = false; 1779 | 1780 | switch (iterator.act(getValue(curr), getKey(curr))) 1781 | { 1782 | case IterateAction.STOP: 1783 | return; 1784 | 1785 | case IterateAction.REMOVE: 1786 | if (remove) { 1787 | remove(curr, prev); 1788 | removed = true; 1789 | } else if (strict) { 1790 | throw new Error('remove is required for linked list iteration'); 1791 | } 1792 | break; 1793 | 1794 | case IterateAction.REPLACE: 1795 | if (replaceValue) { 1796 | replaceValue(curr, iterator.replaceWith); 1797 | } else if (strict) { 1798 | throw new Error('replace is required for linked list iteration'); 1799 | } 1800 | break; 1801 | } 1802 | 1803 | if (!removed) 1804 | { 1805 | prev = curr; 1806 | } 1807 | 1808 | curr = next; 1809 | } 1810 | }).onReset(source => previousNode = source); 1811 | }; 1812 | } 1813 | 1814 | /** 1815 | * Returns a function for iterating over a tree. You pass a node to the 1816 | * function returned (and whether it should perform a depth-first or 1817 | * breadth-first traversal) and an iterator will be returned. 1818 | * 1819 | * @param getValue A function which gets a value from a node. 1820 | * @param getChildren A function which returns an array of child nodes or an 1821 | * iterator which can return the children. 1822 | * @param replaceValue A function which applies a value to a node. 1823 | */ 1824 | public static tree ( 1825 | getValue: (node: N) => T, 1826 | getChildren: (node: N) => N[] | Iterate | undefined | null, 1827 | replaceValue?: (node: N, value: T) => any, 1828 | getKey?: (node: N) => K) 1829 | { 1830 | if (!getKey) 1831 | { 1832 | getKey = (node) => getValue(node) as unknown as K; 1833 | } 1834 | 1835 | // The iterator to return when a node doesn't have children. 1836 | const NO_CHILDREN = Iterate.empty(); 1837 | 1838 | // Returns an Iterate for the children of the node 1839 | const getChildIterate = (node: N): Iterate => 1840 | { 1841 | const children = getChildren(node); 1842 | 1843 | return !children 1844 | ? NO_CHILDREN 1845 | : Array.isArray(children) 1846 | ? Iterate.array(children) 1847 | : children; 1848 | }; 1849 | 1850 | // Handles actions done to the given node 1851 | const handleAction = (node: N, iter: Iterate, strict: boolean, throwOnRemove: boolean, parent?: Iterate): boolean => 1852 | { 1853 | switch (iter.act(getValue(node), getKey(node))) 1854 | { 1855 | // call stop on the parent iterator and return false 1856 | case IterateAction.STOP: 1857 | if (parent) { 1858 | parent.stop(); 1859 | } 1860 | 1861 | return false; 1862 | 1863 | // replace the value 1864 | case IterateAction.REPLACE: 1865 | if (replaceValue) { 1866 | replaceValue(node, iter.replaceWith); 1867 | } else if (strict) { 1868 | throw new Error('replaceValue is required when replacing a value in a tree'); 1869 | } 1870 | break; 1871 | 1872 | case IterateAction.REMOVE: 1873 | if (parent) { 1874 | parent.remove(); 1875 | } else if (strict && throwOnRemove) { 1876 | throw new Error('remove is not supported for breadth-first iteration'); 1877 | } 1878 | break; 1879 | } 1880 | 1881 | return true; 1882 | }; 1883 | 1884 | // Performs a depth-first iteration on the given tree 1885 | const traverseDepthFirst = (node: N, iter: Iterate, strict: boolean, parent?: Iterate): boolean => 1886 | { 1887 | if (!handleAction(node, iter, strict, false, parent)) 1888 | { 1889 | return false; 1890 | } 1891 | 1892 | return !getChildIterate(node) 1893 | .each((child, childKey, childIter) => traverseDepthFirst(child, iter, strict, childIter)) 1894 | .isStopped(); 1895 | }; 1896 | 1897 | // Performs a breadth-first iteration on the given tree 1898 | const traverseBreadthFirst = (node: N, iter: Iterate, strict: boolean): void => 1899 | { 1900 | const queue: N[] = []; 1901 | 1902 | queue.push(node); 1903 | 1904 | while (queue.length > 0) 1905 | { 1906 | const next = queue.shift(); 1907 | 1908 | if (!handleAction(next, iter, strict, true)) 1909 | { 1910 | break; 1911 | } 1912 | 1913 | getChildIterate(next).array(queue); 1914 | } 1915 | }; 1916 | 1917 | /** 1918 | * Performs a breath-first or depth-first traversal of the tree starting 1919 | * at the given node. Removing a node is only permitted for depth-first 1920 | * traversals. Replace is only performed when `replaceValue` is passed 1921 | * to the [[Iterate.tree]] function. 1922 | * 1923 | * If `strict` is true and a remove/replace is requested that can not be 1924 | * done then an error will be thrown. 1925 | * 1926 | * @param startingNode The node to start traversing at. 1927 | * @param depthFirst True for a depth-first traversal, false for a 1928 | * breadth-first traversal. 1929 | * @param strict If an error should be thrown when an unsupported action is 1930 | * requested. 1931 | */ 1932 | return function treeIterator (startingNode?: N, depthFirst: boolean = true, strict: boolean = true): Iterate 1933 | { 1934 | return new Iterate(iter => depthFirst 1935 | ? traverseDepthFirst(startingNode, iter, strict) 1936 | : traverseBreadthFirst(startingNode, iter, strict) 1937 | ).onReset(source => startingNode = source); 1938 | }; 1939 | } 1940 | 1941 | /** 1942 | * Joins all the given sources into a single iterator where the values 1943 | * returned are in the same order as passed to this function. If any values 1944 | * are removed from the returned iterator they will be removed from the given 1945 | * iterator if it supports removal. 1946 | * 1947 | * @param sources The sources to get iterators for to join. 1948 | * @returns A new iterator for the given sources. 1949 | */ 1950 | public static join (...sources: Iterate[]): Iterate; 1951 | public static join, K extends GetKeyFor> (...sources: S[]): Iterate; 1952 | public static join (...sources: IterateSourceTypeKey[]): Iterate; 1953 | public static join (...sources: IterateSourceType[]): Iterate; 1954 | public static join (...sources: IterateSourceType[]): Iterate; 1955 | public static join (...sources: any[]): Iterate 1956 | { 1957 | return new Iterate(parent => 1958 | { 1959 | const iterators = sources.map(iterate); 1960 | 1961 | for (const child of iterators) 1962 | { 1963 | child.each((value, key, childIterate) => 1964 | { 1965 | switch (parent.act( value as T, key as unknown as K )) 1966 | { 1967 | case IterateAction.REMOVE: 1968 | childIterate.remove(); 1969 | break; 1970 | case IterateAction.STOP: 1971 | childIterate.stop(); 1972 | break; 1973 | case IterateAction.REPLACE: 1974 | childIterate.replace( parent.replaceWith ); 1975 | break; 1976 | } 1977 | }); 1978 | 1979 | if (child.action === IterateAction.STOP) 1980 | { 1981 | return; 1982 | } 1983 | } 1984 | }); 1985 | } 1986 | 1987 | /** 1988 | * Returns a new iterator with no values. 1989 | * 1990 | * @returns A new iterator with no values. 1991 | */ 1992 | public static empty (): Iterate 1993 | { 1994 | return new Iterate(parent => { return; }); 1995 | } 1996 | 1997 | /** 1998 | * Generates a reusable function which takes a source and performs a 1999 | * pre-defined set of views, operations, and mutations. 2000 | * 2001 | * @param execute The function which performs the function. 2002 | */ 2003 | public static func (execute: IterateFunctionExecute): IterateFunction 2004 | { 2005 | return (source, ...args) => 2006 | { 2007 | const subject = iterate(source) as unknown as Iterate; 2008 | let result: R; 2009 | 2010 | execute(subject, latestResult => result = latestResult, ...args); 2011 | 2012 | return result; 2013 | }; 2014 | } 2015 | 2016 | } 2017 | --------------------------------------------------------------------------------