├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── angular.json ├── package-lock.json ├── package.json ├── projects └── ts-generic-collections │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── common.ts │ │ ├── dictionary.spec.ts │ │ ├── dictionary.ts │ │ ├── interfaces.ts │ │ ├── list.spec.ts │ │ ├── list.ts │ │ ├── queue.spec.ts │ │ ├── queue.ts │ │ ├── randomized.queue.spec.ts │ │ ├── randomized.queue.ts │ │ ├── sorted.dictionary.spec.ts │ │ ├── sorted.dictionary.ts │ │ ├── stack.spec.ts │ │ └── stack.ts │ ├── public_api.ts │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # profiling files 12 | chrome-profiler-events.json 13 | speed-measure-plugin.json 14 | 15 | # IDEs and editors 16 | /.idea 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # IDE - VSCode 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | .history/* 31 | 32 | # misc 33 | /.sass-cache 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | npm-debug.log 38 | yarn-error.log 39 | testem.log 40 | /typings 41 | 42 | # System Files 43 | .DS_Store 44 | Thumbs.db 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | sudo: false 3 | 4 | language: node_js 5 | node_js: 6 | - '8' 7 | 8 | addons: 9 | chrome: stable 10 | 11 | cache: 12 | directories: 13 | - ./node_modules 14 | 15 | install: 16 | - npm install 17 | 18 | script: 19 | - npm run test -- --watch=false --no-progress --browsers=ChromeHeadlessNoSandbox 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Shantanu 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ts-generic-collections-linq 2 | # TypeScript library provides generic, strongly-typed, queryable collections 3 | 4 | [![Build Status](https://travis-ci.com/VeritasSoftware/ts-generic-collections.svg?branch=master)](https://travis-ci.com/VeritasSoftware/ts-generic-collections) 5 | 6 | ![npm](https://img.shields.io/npm/v/ts-generic-collections-linq) ![npm](https://img.shields.io/npm/dw/ts-generic-collections-linq) 7 | 8 | [Library on NPM](https://www.npmjs.com/package/ts-generic-collections-linq) 9 | 10 | ### The generic collections are: 11 | 12 | * List 13 | * Dictionary, SortedDictionary 14 | * Queue, RandomizedQueue 15 | * Stack 16 | 17 | ### List, Dictionary collections implement interface IEnumerable\ 18 | 19 | ```typescript 20 | export interface IEnumerable { 21 | elementAt(index: number) : T; 22 | any(predicate?: (item: T)=> boolean) : boolean; 23 | all(predicate?: (item: T)=> boolean) : boolean; 24 | single(predicate?: (item: T)=> boolean) : T; 25 | first(predicate?: (item: T)=> boolean) : T; 26 | last(predicate?: (item: T)=> boolean) : T; 27 | singleOrDefault(predicate: (item: T)=> boolean) : T; 28 | firstOrDefault(predicate: (item: T)=> boolean) : T; 29 | lastOrDefault(predicate: (item: T)=> boolean) : T; 30 | where(predicate: (item: T)=> boolean) : IEnumerable; 31 | select(predicate: (item: T)=> TResult) : IEnumerable; 32 | selectMany(predicate: (item: T)=> Array) : IEnumerable; 33 | join(outer: IEnumerable, conditionInner: (item: T)=> TMatch, 34 | conditionOuter: (item: TOuter)=> TMatch, select: (x: T, y:TOuter)=> TResult, leftJoin?: boolean) : IEnumerable; 35 | groupBy(predicate: (item: T) => Array) : IEnumerable>; 36 | orderBy(comparer: IComparer) : IEnumerable; 37 | distinct(comparer: IEqualityComparer) : IEnumerable; 38 | union(list: IEnumerable) : IEnumerable; 39 | reverse(): IEnumerable; 40 | skip(no: number) : IEnumerable; 41 | take(no: number) : IEnumerable; 42 | sum(predicate: (item: T)=> number) : number; 43 | avg(predicate: (item: T)=> number) : number; 44 | min(predicate: (item: T)=> number) : number; 45 | max(predicate: (item: T)=> number) : number; 46 | count(predicate?: (item: T)=> boolean) : number; 47 | forEach(predicate: (item: T)=> void) : void; 48 | length: number; 49 | toArray() : Array; 50 | asEnumerable() : IEnumerable; 51 | } 52 | ``` 53 | 54 | ## List 55 | 56 | ### List implements interface IList\ 57 | 58 | ```typescript 59 | export interface IList extends IEnumerable { 60 | add(item: T) : void; 61 | addRange(items: T[]) : void; 62 | remove(predicate: (item:T) => boolean) : void; 63 | removeAt(index: number) : void; 64 | clear() : void; 65 | } 66 | ``` 67 | 68 | ### You can create queries like below 69 | 70 | Import the library: 71 | 72 | ```typescript 73 | import { List, Dictionary } from 'ts-generic-collections-linq' 74 | ``` 75 | 76 | Below query gets the owners by the sex of their pets. 77 | 78 | ```typescript 79 | let owners = new List(); 80 | 81 | let owner = new Owner(); 82 | owner.id = 1; 83 | owner.name = "John Doe"; 84 | owners.add(owner); 85 | 86 | owner = new Owner(); 87 | owner.id = 2; 88 | owner.name = "Jane Doe"; 89 | owners.add(owner); 90 | 91 | let pets = new List(); 92 | 93 | let pet = new Pet(); 94 | pet.ownerId = 2; 95 | pet.name = "Sam"; 96 | pet.sex = Sex.M; 97 | 98 | pets.add(pet); 99 | 100 | pet = new Pet(); 101 | pet.ownerId = 1; 102 | pet.name = "Jenny"; 103 | pet.sex = Sex.F; 104 | 105 | pets.add(pet); 106 | 107 | //query to get owners by the sex/gender of their pets 108 | let ownersByPetSex = owners.join(pets, owner => owner.id, pet => pet.ownerId, (x, y) => new OwnerPet(x,y)) 109 | .groupBy(x => [x.pet.sex]) 110 | .select(x => new OwnersByPetSex(x.groups[0], x.list.select(x => x.owner))); 111 | 112 | expect(ownersByPetSex.toArray().length === 2).toBeTruthy(); 113 | 114 | expect(ownersByPetSex.toArray()[0].sex == Sex.F).toBeTruthy(); 115 | expect(ownersByPetSex.toArray()[0].owners.length === 1).toBeTruthy(); 116 | expect(ownersByPetSex.toArray()[0].owners.toArray()[0].name == "John Doe").toBeTruthy(); 117 | 118 | expect(ownersByPetSex.toArray()[1].sex == Sex.M).toBeTruthy(); 119 | expect(ownersByPetSex.toArray()[1].owners.length == 1).toBeTruthy(); 120 | expect(ownersByPetSex.toArray()[1].owners.toArray()[0].name == "Jane Doe").toBeTruthy(); 121 | ``` 122 | 123 | ### You can instantiate a List from JSON as shown below 124 | 125 | ```typescript 126 | let jsonOwnerArray = '[{"id":1, "name": "John Doe"},{"id":2, "name": "Jane Doe"}]'; 127 | 128 | let ownerArray: Owner[] = JSON.parse(jsonOwnerArray); 129 | 130 | let list = new List(ownerArray); 131 | ``` 132 | 133 | ### Entities for above example are 134 | 135 | ```typescript 136 | class Owner { 137 | id: number; 138 | name: string; 139 | } 140 | 141 | class Pet { 142 | ownerId: number; 143 | name: string; 144 | sex: Sex; 145 | } 146 | 147 | enum Sex { 148 | M, 149 | F 150 | } 151 | 152 | class OwnerPet { 153 | owner: Owner; 154 | pet: Pet; 155 | 156 | constructor(owner: Owner, pet: Pet) { 157 | this.owner = owner; 158 | this.pet = pet; 159 | } 160 | } 161 | 162 | class OwnersByPetSex { 163 | sex: Sex; 164 | owners: IEnumerable; 165 | 166 | constructor(sex: Sex, owners: IEnumerable) { 167 | this.sex = sex; 168 | this.owners = owners; 169 | } 170 | } 171 | ``` 172 | 173 | ## Dictionary 174 | 175 | ### Dictionary, SortedDictionary implement IDictionary 176 | 177 | ```typescript 178 | export interface IDictionary extends IEnumerable> { 179 | add(key: TKey, value: TValue) : void; 180 | addRange(items: KeyValuePair[]) : void; 181 | remove(predicate: (item:KeyValuePair) => boolean) : void; 182 | removeAt(index: number) : void; 183 | clear() : void; 184 | 185 | containsKey(key: TKey) : boolean; 186 | containsValue(value: TValue) : boolean; 187 | tryGetValue(key: TKey) : TValue; 188 | } 189 | ``` 190 | 191 | ### SortedDictionary 192 | 193 | * sorted by Key 194 | * uses **IComparer\** to provide the sorted collection. 195 | 196 | ## Queue 197 | 198 | ### Queue, RandomizedQueue implement interface IQueue\ 199 | 200 | ```typescript 201 | export interface IQueue { 202 | clear() : void; 203 | contains(item: T) : boolean; 204 | dequeue() : T; 205 | enqueue(item: T) : void; 206 | peek(): T; 207 | forEach(predicate: (item: T)=> void) : void; 208 | toArray(): Array; 209 | } 210 | ``` 211 | 212 | ### RandomizedQueue 213 | 214 | * enqueue to the end. 215 | * dequeue a random item. 216 | * peek (but not dequeue) a random item. 217 | * if you peek and then dequeue, the peeked item is dequeued. 218 | 219 | ## Stack 220 | 221 | ### Stack implements interface IStack\ 222 | 223 | ```typescript 224 | export interface IStack { 225 | clear() : void; 226 | contains(item: T) : boolean; 227 | pop() : T; 228 | push(item: T) : void; 229 | peek(): T; 230 | forEach(predicate: (item: T)=> void) : void; 231 | toArray(): Array; 232 | } 233 | ``` 234 | 235 | ### You can browse more examples of queries below 236 | 237 | [**List**](https://github.com/VeritasSoftware/ts-generic-collections/blob/master/projects/ts-generic-collections/src/lib/list.spec.ts) 238 | 239 | [**Dictionary**](https://github.com/VeritasSoftware/ts-generic-collections/blob/master/projects/ts-generic-collections/src/lib/dictionary.spec.ts) 240 | 241 | [**SortedDictionary**](https://github.com/VeritasSoftware/ts-generic-collections/blob/master/projects/ts-generic-collections/src/lib/sorted.dictionary.spec.ts) 242 | 243 | [**Queue**](https://github.com/VeritasSoftware/ts-generic-collections/blob/master/projects/ts-generic-collections/src/lib/queue.spec.ts) 244 | 245 | [**RandomizedQueue**](https://github.com/VeritasSoftware/ts-generic-collections/blob/master/projects/ts-generic-collections/src/lib/randomized.queue.spec.ts) 246 | 247 | [**Stack**](https://github.com/VeritasSoftware/ts-generic-collections/blob/master/projects/ts-generic-collections/src/lib/stack.spec.ts) 248 | 249 | 250 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.2.2. -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ts-generic-collections": { 7 | "root": "projects/ts-generic-collections", 8 | "sourceRoot": "projects/ts-generic-collections/src", 9 | "projectType": "library", 10 | "prefix": "ts", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-ng-packagr:build", 14 | "options": { 15 | "tsConfig": "projects/ts-generic-collections/tsconfig.lib.json", 16 | "project": "projects/ts-generic-collections/ng-package.json" 17 | } 18 | }, 19 | "test": { 20 | "builder": "@angular-devkit/build-angular:karma", 21 | "options": { 22 | "main": "projects/ts-generic-collections/src/test.ts", 23 | "tsConfig": "projects/ts-generic-collections/tsconfig.spec.json", 24 | "karmaConfig": "projects/ts-generic-collections/karma.conf.js" 25 | } 26 | }, 27 | "lint": { 28 | "builder": "@angular-devkit/build-angular:tslint", 29 | "options": { 30 | "tsConfig": [ 31 | "projects/ts-generic-collections/tsconfig.lib.json", 32 | "projects/ts-generic-collections/tsconfig.spec.json" 33 | ], 34 | "exclude": [ 35 | "**/node_modules/**" 36 | ] 37 | } 38 | } 39 | } 40 | } 41 | }, 42 | "defaultProject": "ts-generic-collections" 43 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-generic-collections-linq", 3 | "version": "1.0.7", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "dependencies": { 13 | "@angular/animations": "~7.2.0", 14 | "@angular/common": "~7.2.0", 15 | "@angular/compiler": "~7.2.0", 16 | "@angular/core": "~7.2.0", 17 | "@angular/forms": "~7.2.0", 18 | "@angular/platform-browser": "~7.2.0", 19 | "@angular/platform-browser-dynamic": "~7.2.0", 20 | "@angular/router": "~7.2.0", 21 | "core-js": "^2.5.4", 22 | "rxjs": "~6.3.3", 23 | "tslib": "^1.9.0", 24 | "zone.js": "~0.8.26" 25 | }, 26 | "devDependencies": { 27 | "@angular-devkit/build-angular": "~0.12.0", 28 | "@angular-devkit/build-ng-packagr": "^0.12.3", 29 | "@angular/cli": "~7.2.2", 30 | "@angular/compiler-cli": "~7.2.0", 31 | "@angular/language-service": "~7.2.0", 32 | "@types/jasmine": "~2.8.8", 33 | "@types/jasminewd2": "~2.0.3", 34 | "@types/node": "~8.9.4", 35 | "codelyzer": "~4.5.0", 36 | "jasmine-core": "~2.99.1", 37 | "jasmine-spec-reporter": "~4.2.1", 38 | "karma": "~3.1.1", 39 | "karma-chrome-launcher": "~2.2.0", 40 | "karma-coverage-istanbul-reporter": "~2.0.1", 41 | "karma-jasmine": "~1.1.2", 42 | "karma-jasmine-html-reporter": "^0.2.2", 43 | "ng-packagr": "^4.2.0", 44 | "protractor": "~5.4.0", 45 | "ts-node": "~7.0.0", 46 | "tsickle": ">=0.34.0", 47 | "tslib": "^1.9.0", 48 | "tslint": "~5.11.0", 49 | "typescript": "~3.2.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /projects/ts-generic-collections/README.md: -------------------------------------------------------------------------------- 1 | # ts-generic-collections-linq 2 | # TypeScript library provides generic, strongly-typed, queryable collections 3 | 4 | ### The generic collections are: 5 | 6 | * List 7 | * Dictionary, SortedDictionary 8 | * Queue, RandomizedQueue 9 | * Stack 10 | 11 | ### List, Dictionary collections implement interface IEnumerable\ 12 | 13 | ```typescript 14 | export interface IEnumerable { 15 | elementAt(index: number) : T; 16 | any(predicate?: (item: T)=> boolean) : boolean; 17 | all(predicate?: (item: T)=> boolean) : boolean; 18 | single(predicate?: (item: T)=> boolean) : T; 19 | first(predicate?: (item: T)=> boolean) : T; 20 | last(predicate?: (item: T)=> boolean) : T; 21 | singleOrDefault(predicate: (item: T)=> boolean) : T; 22 | firstOrDefault(predicate: (item: T)=> boolean) : T; 23 | lastOrDefault(predicate: (item: T)=> boolean) : T; 24 | where(predicate: (item: T)=> boolean) : IEnumerable; 25 | select(predicate: (item: T)=> TResult) : IEnumerable; 26 | selectMany(predicate: (item: T)=> Array) : IEnumerable; 27 | join(outer: IEnumerable, conditionInner: (item: T)=> TMatch, 28 | conditionOuter: (item: TOuter)=> TMatch, select: (x: T, y:TOuter)=> TResult, leftJoin?: boolean) : IEnumerable; 29 | groupBy(predicate: (item: T) => Array) : IEnumerable>; 30 | orderBy(comparer: IComparer) : IEnumerable; 31 | distinct(comparer: IEqualityComparer) : IEnumerable; 32 | union(list: IEnumerable) : IEnumerable; 33 | reverse(): IEnumerable; 34 | skip(no: number) : IEnumerable; 35 | take(no: number) : IEnumerable; 36 | sum(predicate: (item: T)=> number) : number; 37 | avg(predicate: (item: T)=> number) : number; 38 | min(predicate: (item: T)=> number) : number; 39 | max(predicate: (item: T)=> number) : number; 40 | count(predicate?: (item: T)=> boolean) : number; 41 | forEach(predicate: (item: T)=> void) : void; 42 | length: number; 43 | toArray() : Array; 44 | asEnumerable() : IEnumerable; 45 | } 46 | ``` 47 | 48 | ## List 49 | 50 | ### List implements interface IList\ 51 | 52 | ```typescript 53 | export interface IList extends IEnumerable { 54 | add(item: T) : void; 55 | addRange(items: T[]) : void; 56 | remove(predicate: (item:T) => boolean) : void; 57 | removeAt(index: number) : void; 58 | clear() : void; 59 | } 60 | ``` 61 | 62 | ### You can create queries like below 63 | 64 | Import the library: 65 | 66 | ```typescript 67 | import { List, Dictionary } from 'ts-generic-collections-linq' 68 | ``` 69 | 70 | Below query gets the owners by the sex of their pets. 71 | 72 | ```typescript 73 | let owners = new List(); 74 | 75 | let owner = new Owner(); 76 | owner.id = 1; 77 | owner.name = "John Doe"; 78 | owners.add(owner); 79 | 80 | owner = new Owner(); 81 | owner.id = 2; 82 | owner.name = "Jane Doe"; 83 | owners.add(owner); 84 | 85 | let pets = new List(); 86 | 87 | let pet = new Pet(); 88 | pet.ownerId = 2; 89 | pet.name = "Sam"; 90 | pet.sex = Sex.M; 91 | 92 | pets.add(pet); 93 | 94 | pet = new Pet(); 95 | pet.ownerId = 1; 96 | pet.name = "Jenny"; 97 | pet.sex = Sex.F; 98 | 99 | pets.add(pet); 100 | 101 | //query to get owners by the sex/gender of their pets 102 | let ownersByPetSex = owners.join(pets, owner => owner.id, pet => pet.ownerId, (x, y) => new OwnerPet(x,y)) 103 | .groupBy(x => [x.pet.sex]) 104 | .select(x => new OwnersByPetSex(x.groups[0], x.list.select(x => x.owner))); 105 | 106 | expect(ownersByPetSex.toArray().length === 2).toBeTruthy(); 107 | 108 | expect(ownersByPetSex.toArray()[0].sex == Sex.F).toBeTruthy(); 109 | expect(ownersByPetSex.toArray()[0].owners.length === 1).toBeTruthy(); 110 | expect(ownersByPetSex.toArray()[0].owners.toArray()[0].name == "John Doe").toBeTruthy(); 111 | 112 | expect(ownersByPetSex.toArray()[1].sex == Sex.M).toBeTruthy(); 113 | expect(ownersByPetSex.toArray()[1].owners.length == 1).toBeTruthy(); 114 | expect(ownersByPetSex.toArray()[1].owners.toArray()[0].name == "Jane Doe").toBeTruthy(); 115 | ``` 116 | 117 | ### You can instantiate a List from JSON as shown below 118 | 119 | ```typescript 120 | let jsonOwnerArray = '[{"id":1, "name": "John Doe"},{"id":2, "name": "Jane Doe"}]'; 121 | 122 | let ownerArray: Owner[] = JSON.parse(jsonOwnerArray); 123 | 124 | let list = new List(ownerArray); 125 | ``` 126 | 127 | ### Entities for above example are 128 | 129 | ```typescript 130 | class Owner { 131 | id: number; 132 | name: string; 133 | } 134 | 135 | class Pet { 136 | ownerId: number; 137 | name: string; 138 | sex: Sex; 139 | } 140 | 141 | enum Sex { 142 | M, 143 | F 144 | } 145 | 146 | class OwnerPet { 147 | owner: Owner; 148 | pet: Pet; 149 | 150 | constructor(owner: Owner, pet: Pet) { 151 | this.owner = owner; 152 | this.pet = pet; 153 | } 154 | } 155 | 156 | class OwnersByPetSex { 157 | sex: Sex; 158 | owners: IEnumerable; 159 | 160 | constructor(sex: Sex, owners: IEnumerable) { 161 | this.sex = sex; 162 | this.owners = owners; 163 | } 164 | } 165 | ``` 166 | 167 | ## Dictionary 168 | 169 | ### Dictionary, SortedDictionary implement IDictionary 170 | 171 | ```typescript 172 | export interface IDictionary extends IEnumerable> { 173 | add(key: TKey, value: TValue) : void; 174 | addRange(items: KeyValuePair[]) : void; 175 | remove(predicate: (item:KeyValuePair) => boolean) : void; 176 | removeAt(index: number) : void; 177 | clear() : void; 178 | 179 | containsKey(key: TKey) : boolean; 180 | containsValue(value: TValue) : boolean; 181 | tryGetValue(key: TKey) : TValue; 182 | } 183 | ``` 184 | 185 | ### SortedDictionary 186 | 187 | * sorted by Key 188 | * uses **IComparer\** to provide the sorted collection. 189 | 190 | ## Queue 191 | 192 | ### Queue, RandomizedQueue implement interface IQueue\ 193 | 194 | ```typescript 195 | export interface IQueue { 196 | clear() : void; 197 | contains(item: T) : boolean; 198 | dequeue() : T; 199 | enqueue(item: T) : void; 200 | peek(): T; 201 | forEach(predicate: (item: T)=> void) : void; 202 | toArray(): Array; 203 | } 204 | ``` 205 | 206 | ### RandomizedQueue 207 | 208 | * enqueue to the end. 209 | * dequeue a random item. 210 | * peek (but not dequeue) a random item. 211 | * if you peek and then dequeue, the peeked item is dequeued. 212 | 213 | ## Stack 214 | 215 | ### Stack implements interface IStack\ 216 | 217 | ```typescript 218 | export interface IStack { 219 | clear() : void; 220 | contains(item: T) : boolean; 221 | pop() : T; 222 | push(item: T) : void; 223 | peek(): T; 224 | forEach(predicate: (item: T)=> void) : void; 225 | toArray(): Array; 226 | } 227 | ``` 228 | -------------------------------------------------------------------------------- /projects/ts-generic-collections/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | customLaunchers: { 31 | ChromeHeadlessNoSandbox: { 32 | base: 'ChromeHeadless', 33 | flags: ['--no-sandbox'] 34 | } 35 | } 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /projects/ts-generic-collections/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ts-generic-collections", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /projects/ts-generic-collections/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-generic-collections-linq", 3 | "version": "1.0.7", 4 | "description": "TypeScript library provides strongly-typed, queryable collections.", 5 | "author": { 6 | "name": "VeritasSoftware", "email": "veritas.software.au@gmail.com" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/VeritasSoftware/ts-generic-collections" 11 | }, 12 | "homepage": "https://github.com/VeritasSoftware/ts-generic-collections", 13 | "keywords": [ 14 | "generic", 15 | "strongly-typed", 16 | "queryable", 17 | "collections", 18 | "list", 19 | "dictionary", 20 | "sorted dictionary", 21 | "queue", 22 | "randomized queue", 23 | "stack", 24 | "library", 25 | "linq", 26 | "typescript" 27 | ], 28 | "license": "MIT", 29 | "peerDependencies": { 30 | "@angular/common": "^7.2.0", 31 | "@angular/core": "^7.2.0" 32 | } 33 | } -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/common.ts: -------------------------------------------------------------------------------- 1 | import { IEnumerable, IGroup } from './interfaces'; 2 | import { List } from './list'; 3 | 4 | export class Group implements IGroup { 5 | groups: any[]; 6 | list: IEnumerable = new List(); 7 | 8 | constructor(groups: any[], list: Array) { 9 | this.groups = groups; 10 | this.list = new List(list); 11 | } 12 | } 13 | 14 | export var objCompare = function (obj1, obj2) { 15 | 16 | if ((typeof obj1 !== 'object' && typeof obj1 !== 'function') && (typeof obj2 !== 'object' && typeof obj2 !== 'function')) { 17 | return obj1 === obj2; 18 | } 19 | 20 | //Loop through properties in object 1 21 | for (var p in obj1) { 22 | //Check property exists on both objects 23 | if (obj1.hasOwnProperty(p) !== obj2.hasOwnProperty(p)) return false; 24 | 25 | switch (typeof (obj1[p])) { 26 | //Deep compare objects 27 | case 'object': 28 | if (!objCompare(obj1[p], obj2[p])) return false; 29 | break; 30 | //Compare function code 31 | case 'function': 32 | if (typeof (obj2[p]) == 'undefined' || (p != 'compare' && obj1[p].toString() != obj2[p].toString())) return false; 33 | break; 34 | //Compare values 35 | default: 36 | if (obj1[p] != obj2[p]) return false; 37 | } 38 | } 39 | 40 | //Check object 2 for any extra properties 41 | for (var p in obj2) { 42 | if (typeof (obj1[p]) == 'undefined') return false; 43 | } 44 | return true; 45 | }; 46 | 47 | export const ITEM_NOT_FOUND_MSG: string = "Item does not exist."; 48 | export const MULTIPLE_INSTANCES_FOUND_MSG: string = "Multiple instances of entity found."; -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/dictionary.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { Dictionary, KeyValuePair } from './dictionary'; 3 | import { IEnumerable, IComparer } from './interfaces'; 4 | import { IList, List } from './list'; 5 | 6 | describe('Dictionary', () => { 7 | 8 | beforeEach(async(() => { 9 | 10 | })); 11 | 12 | beforeEach(() => { 13 | 14 | }); 15 | 16 | it('instantiate dictionary from json', () => { 17 | let jsonKeyValuePairArray = '[{"key": 1, "value": {"id":1, "name": "Mercedez", "model": "S 400", "country": "Germany", "isLuxury": true }},{"key": 2, "value": {"id":2, "name": "Ford", "model": "F 100", "country": "US", "isLuxury": false }}]'; 18 | 19 | let keyValueArray: KeyValuePair[] = JSON.parse(jsonKeyValuePairArray); 20 | 21 | let list = new Dictionary(keyValueArray); 22 | 23 | var luxuryCars = list.where(x => x.value.isLuxury); 24 | 25 | expect(list.length == 1); 26 | expect(luxuryCars.toArray()[0].value.model == "Mercedez"); 27 | }); 28 | 29 | it('add', () => { 30 | let dictionary = new Dictionary>(); 31 | 32 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 33 | 34 | let features = new List(); 35 | 36 | let feature = new Feature(1, "2 - Door Sedan"); 37 | 38 | features.add(feature); 39 | 40 | dictionary.add(car, features); 41 | 42 | expect(dictionary.length == 1); 43 | expect(dictionary.containsKey(car)); 44 | expect(dictionary.containsValue(features)); 45 | expect(dictionary.tryGetValue(car).length == 1); 46 | }); 47 | 48 | it('add fail', () => { 49 | let dictionary = new Dictionary>(); 50 | 51 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 52 | 53 | let features = new List(); 54 | 55 | let feature = new Feature(1, "2 - Door Sedan"); 56 | 57 | features.add(feature); 58 | 59 | dictionary.add(car, features); 60 | 61 | car = new Car(1, "Mercedez", "S 400", Country.Germany); 62 | 63 | try { 64 | dictionary.add(car, features); 65 | } 66 | catch(e) { 67 | expect(e == "Duplicate key. Cannot add."); 68 | } 69 | }); 70 | 71 | it('remove', () => { 72 | let dictionary = new Dictionary>(); 73 | 74 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 75 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 76 | 77 | let features = new List(); 78 | 79 | let feature = new Feature(1, "2 - Door Sedan"); 80 | 81 | features.add(feature); 82 | 83 | dictionary.add(car, features); 84 | 85 | features = new List(); 86 | 87 | feature = new Feature(2, "4 - Door Sedan"); 88 | 89 | features.add(feature); 90 | 91 | dictionary.add(car2, features); 92 | 93 | expect(dictionary.length == 2); 94 | 95 | dictionary.remove(x => x.key.country == Country.Germany); 96 | 97 | expect(dictionary.length == 1).toBeTruthy(); 98 | }); 99 | 100 | it('removeAt', () => { 101 | let dictionary = new Dictionary>(); 102 | 103 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 104 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 105 | 106 | let features = new List(); 107 | 108 | let feature = new Feature(1, "2 - Door Sedan"); 109 | 110 | features.add(feature); 111 | 112 | dictionary.add(car, features); 113 | 114 | features = new List(); 115 | 116 | feature = new Feature(2, "4 - Door Sedan"); 117 | 118 | features.add(feature); 119 | 120 | dictionary.add(car2, features); 121 | 122 | expect(dictionary.length == 2); 123 | 124 | dictionary.removeAt(1); 125 | 126 | expect(dictionary.length == 1).toBeTruthy(); 127 | expect(dictionary.elementAt(0).key.name === 'Mercedez').toBeTruthy(); 128 | }); 129 | 130 | it('where', () => { 131 | let dictionary = new Dictionary>(); 132 | 133 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 134 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 135 | 136 | let features = new List(); 137 | 138 | let feature = new Feature(1, "2 - Door Sedan"); 139 | 140 | features.add(feature); 141 | 142 | dictionary.add(car, features); 143 | 144 | features = new List(); 145 | 146 | feature = new Feature(2, "4 - Door Sedan"); 147 | 148 | features.add(feature); 149 | 150 | dictionary.add(car2, features); 151 | 152 | let result = dictionary.where(x => x.value.any(x => x.name == "4 - Door Sedan")); 153 | 154 | expect(result.first().key.id == 2); 155 | }); 156 | 157 | it('singleOrDefault', () => { 158 | let dictionary = new Dictionary>(); 159 | 160 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 161 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 162 | 163 | let features = new List(); 164 | 165 | let feature = new Feature(1, "2 - Door Sedan"); 166 | 167 | features.add(feature); 168 | 169 | dictionary.add(car, features); 170 | 171 | features = new List(); 172 | 173 | feature = new Feature(2, "4 - Door Sedan"); 174 | 175 | features.add(feature); 176 | 177 | dictionary.add(car2, features); 178 | 179 | let result = dictionary.singleOrDefault(x => x.key.country == Country.Germany); 180 | 181 | expect(result.key.id == 1); 182 | }); 183 | 184 | it('firstOrDefault', () => { 185 | let dictionary = new Dictionary>(); 186 | 187 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 188 | let car2 = new Car(2, "Mercedez", "S 500", Country.Germany); 189 | 190 | let features = new List(); 191 | 192 | let feature = new Feature(1, "2 - Door Sedan"); 193 | 194 | features.add(feature); 195 | 196 | dictionary.add(car, features); 197 | 198 | features = new List(); 199 | 200 | feature = new Feature(2, "4 - Door Sedan"); 201 | 202 | features.add(feature); 203 | 204 | dictionary.add(car2, features); 205 | 206 | let first = dictionary.firstOrDefault(x => x.key.name == "Mercedez"); 207 | 208 | expect(first.key.id = 1); 209 | }); 210 | 211 | it('singleOrDefault fail', () => { 212 | let dictionary = new Dictionary>(); 213 | 214 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 215 | let car2 = new Car(2, "Mercedez", "S 500", Country.Germany); 216 | 217 | let features = new List(); 218 | 219 | let feature = new Feature(1, "2 - Door Sedan"); 220 | 221 | features.add(feature); 222 | 223 | dictionary.add(car, features); 224 | 225 | features = new List(); 226 | 227 | feature = new Feature(2, "4 - Door Sedan"); 228 | 229 | features.add(feature); 230 | 231 | dictionary.add(car2, features); 232 | 233 | try { 234 | let single = dictionary.singleOrDefault(x => x.key.name == "Mercedez"); 235 | } 236 | catch (e) { 237 | expect(e == "Multiple instances of entity found."); 238 | } 239 | }); 240 | 241 | it('join', () => { 242 | 243 | }); 244 | 245 | it('groupBy', () => { 246 | let dictionary = new Dictionary>(); 247 | 248 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 249 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 250 | 251 | let features = new List(); 252 | 253 | let feature = new Feature(1, "2 - Door Sedan"); 254 | 255 | features.add(feature); 256 | 257 | dictionary.add(car, features); 258 | 259 | features = new List(); 260 | 261 | feature = new Feature(2, "4 - Door Sedan"); 262 | 263 | features.add(feature); 264 | 265 | dictionary.add(car2, features); 266 | 267 | let result = dictionary.groupBy(x => [x.key.country]); 268 | 269 | expect(result.toArray()[0].groups[0] == Country.Germany); 270 | expect(result.toArray()[1].groups[0] == Country.England); 271 | }); 272 | 273 | it('groupBy multiple fields', () => { 274 | let dictionary = new Dictionary>(); 275 | 276 | let car = new Car(1, "Mercedez", "S 400", Country.Germany, true); 277 | let car2 = new Car(2, "Jaguar", "J 500", Country.England, true); 278 | let car3 = new Car(3, "Ford", "F 500", Country.US); 279 | 280 | let features = new List(); 281 | 282 | let feature = new Feature(1, "2 - Door"); 283 | 284 | features.add(feature); 285 | 286 | feature = new Feature(1, "Sedan"); 287 | 288 | features.add(feature); 289 | 290 | dictionary.add(car, features); 291 | 292 | features = new List(); 293 | 294 | feature = new Feature(1, "4 - Door"); 295 | 296 | features.add(feature); 297 | 298 | feature = new Feature(1, "Sedan"); 299 | 300 | features.add(feature); 301 | 302 | dictionary.add(car2, features); 303 | 304 | features = new List(); 305 | 306 | feature = new Feature(1, "4 - Door"); 307 | 308 | features.add(feature); 309 | 310 | feature = new Feature(1, "Hatchback"); 311 | 312 | features.add(feature); 313 | 314 | dictionary.add(car3, features); 315 | 316 | let result = dictionary.groupBy(x => [x.key.isLuxury, x.key.country]); 317 | 318 | expect(result.toArray().length == 2); 319 | 320 | expect(result.toArray()[0].groups[0] == false); 321 | expect(result.toArray()[0].groups[1] == Country.US); 322 | expect(result.toArray()[0].list.toArray()[0].key.id == 3); 323 | 324 | expect(result.toArray()[1].groups[0] == true); 325 | expect(result.toArray()[1].groups[1] == Country.England); 326 | expect(result.toArray()[1].list.toArray()[0].key.id == 2); 327 | 328 | expect(result.toArray()[1].groups[0] == true); 329 | expect(result.toArray()[1].groups[1] == Country.Germany); 330 | expect(result.toArray()[1].list.toArray()[0].key.id == 1); 331 | }); 332 | 333 | it('orderBy', () => { 334 | let dictionary = new Dictionary>(); 335 | 336 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 337 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 338 | 339 | let features = new List(); 340 | 341 | let feature = new Feature(1, "2 - Door Sedan"); 342 | 343 | features.add(feature); 344 | 345 | dictionary.add(car, features); 346 | 347 | features = new List(); 348 | 349 | feature = new Feature(2, "4 - Door Sedan"); 350 | 351 | features.add(feature); 352 | 353 | dictionary.add(car2, features); 354 | 355 | let result = dictionary.orderBy(new ComparerByCarName()); 356 | 357 | expect(result.toArray()[0].key.name == "Jaguar"); 358 | expect(result.toArray()[1].key.name == "Mercedez"); 359 | }); 360 | 361 | it('union', () => { 362 | let dictionary = new Dictionary>(); 363 | 364 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 365 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 366 | 367 | let features = new List(); 368 | 369 | let feature = new Feature(1, "2 - Door Sedan"); 370 | 371 | features.add(feature); 372 | 373 | dictionary.add(car, features); 374 | 375 | features = new List(); 376 | 377 | feature = new Feature(2, "4 - Door Sedan"); 378 | 379 | features.add(feature); 380 | 381 | dictionary.add(car2, features); 382 | 383 | 384 | let dictionary2 = new Dictionary>(); 385 | 386 | car = new Car(1, "Volvo", "V 400", Country.Germany); 387 | car2 = new Car(2, "Ford", "F 500", Country.US); 388 | 389 | features = new List(); 390 | 391 | feature = new Feature(1, "2 - Door Sedan"); 392 | 393 | features.add(feature); 394 | 395 | dictionary2.add(car, features); 396 | 397 | features = new List(); 398 | 399 | feature = new Feature(2, "4 - Door Sedan"); 400 | 401 | features.add(feature); 402 | 403 | dictionary2.add(car2, features); 404 | 405 | let result = dictionary.union(dictionary2); 406 | 407 | expect(result.length == 4); 408 | }); 409 | }); 410 | 411 | enum Country { 412 | US, 413 | Germany, 414 | England 415 | } 416 | 417 | class Car { 418 | id: number; 419 | name: string; 420 | model: string; 421 | country: Country; 422 | isLuxury: boolean; 423 | 424 | constructor(id: number, name: string, model: string, country: Country, isLuxury: boolean = false) { 425 | this.id = id; 426 | this.name = name; 427 | this.country = country; 428 | this.isLuxury = isLuxury; 429 | } 430 | } 431 | 432 | class Feature { 433 | name: string; 434 | carId: number; 435 | 436 | constructor(carId: number, name: string) { 437 | this.carId = carId; 438 | this.name = name; 439 | } 440 | } 441 | 442 | class ComparerByCarName implements IComparer>> { 443 | compare(x: KeyValuePair>, y: KeyValuePair>) : number { 444 | if (x.key.name > y.key.name) { 445 | return 1; 446 | } 447 | 448 | return -1; 449 | } 450 | } -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/dictionary.ts: -------------------------------------------------------------------------------- 1 | import { IEnumerable, IComparer, IEqualityComparer } from './interfaces'; 2 | import { List } from './list'; 3 | import { Group, objCompare, ITEM_NOT_FOUND_MSG, MULTIPLE_INSTANCES_FOUND_MSG } from './common'; 4 | 5 | export interface IDictionary extends IEnumerable> { 6 | add(key: TKey, value: TValue) : void; 7 | addRange(items: KeyValuePair[]) : void; 8 | remove(predicate: (item:KeyValuePair) => boolean) : void; 9 | removeAt(index: number) : void; 10 | clear() : void; 11 | 12 | containsKey(key: TKey) : boolean; 13 | containsValue(value: TValue) : boolean; 14 | tryGetValue(key: TKey) : TValue; 15 | } 16 | 17 | export class Dictionary implements IDictionary 18 | { 19 | private list: Array> = new Array>(); 20 | 21 | constructor(list: Array> = null) { 22 | if (list) { 23 | this.list = list; 24 | } 25 | } 26 | 27 | /* IList */ 28 | 29 | add(key: TKey, value: TValue) : void { 30 | let pair = new KeyValuePair(key, value); 31 | 32 | if (this.containsKey(key)) { 33 | throw "Duplicate key. Cannot add." 34 | } 35 | 36 | this.list.push(pair); 37 | } 38 | 39 | addRange(items: KeyValuePair[]) : void { 40 | items.forEach(x => this.add(x.key, x.value)); 41 | } 42 | 43 | removeAt(index: number) : void { 44 | this.list.splice(index, 1); 45 | } 46 | 47 | clear() : void { 48 | this.list = new Array>(); 49 | } 50 | 51 | remove(predicate: (item:KeyValuePair) => boolean) : void { 52 | let temp = new Array>(); 53 | 54 | this.list.forEach(element => { 55 | if (!predicate(element)) 56 | { 57 | temp.push(element); 58 | } 59 | }); 60 | 61 | this.list = temp; 62 | } 63 | 64 | containsKey(key: TKey) : boolean { 65 | return this.any(x => objCompare(x.key, key)); 66 | } 67 | 68 | containsValue(value: TValue) : boolean { 69 | return this.any(x => objCompare(x.value, value)); 70 | } 71 | 72 | tryGetValue(key: TKey) : TValue { 73 | let item = this.singleOrDefault(x => objCompare(x.key, key)); 74 | 75 | if (item) { 76 | return item.value; 77 | } 78 | 79 | return null; 80 | } 81 | 82 | /* IEnumerable */ 83 | 84 | asEnumerable() : IEnumerable> { 85 | return this; 86 | } 87 | 88 | get length(): number { 89 | return this.list.length; 90 | } 91 | 92 | elementAt(index: number) : KeyValuePair { 93 | try { 94 | return this.list[index]; 95 | } 96 | catch (e) { 97 | return null; 98 | } 99 | } 100 | 101 | any(predicate?: (item: KeyValuePair)=> boolean) : boolean { 102 | if (!predicate) { 103 | return this.list.length > 0; 104 | } 105 | 106 | for (let i=0; i)=> boolean) : boolean { 116 | if (!predicate) { 117 | return this.list.length > 0; 118 | } 119 | 120 | for (let i=0; i)=> boolean = null) : KeyValuePair { 130 | if (this.list.length <= 0) { 131 | throw ITEM_NOT_FOUND_MSG; 132 | } 133 | 134 | if (predicate) { 135 | let item = this.singleOrDefault(predicate); 136 | 137 | if (!item) { 138 | throw ITEM_NOT_FOUND_MSG; 139 | } 140 | 141 | return item; 142 | } 143 | 144 | return this.list[0]; 145 | } 146 | 147 | first(predicate: (item: KeyValuePair)=> boolean = null) : KeyValuePair { 148 | if (this.list.length <= 0) { 149 | throw ITEM_NOT_FOUND_MSG; 150 | } 151 | 152 | if (predicate) { 153 | let item = this.firstOrDefault(predicate); 154 | 155 | if (!item) { 156 | throw ITEM_NOT_FOUND_MSG; 157 | } 158 | 159 | return item; 160 | } 161 | 162 | return this.list[0]; 163 | } 164 | 165 | last(predicate: (item: KeyValuePair)=> boolean) : KeyValuePair { 166 | if (this.list.length <= 0) { 167 | throw ITEM_NOT_FOUND_MSG; 168 | } 169 | 170 | if (predicate) { 171 | let item = this.lastOrDefault(predicate); 172 | 173 | if (!item) { 174 | throw ITEM_NOT_FOUND_MSG; 175 | } 176 | 177 | return item; 178 | } 179 | 180 | return this.list[this.list.length - 1]; 181 | } 182 | 183 | singleOrDefault(predicate: (item: KeyValuePair)=> boolean) : KeyValuePair { 184 | let temp = new Array>(); 185 | 186 | this.list.filter(element => { 187 | if (predicate(element)) 188 | { 189 | temp.push(element); 190 | } 191 | }); 192 | 193 | if (temp.length > 1) { 194 | throw MULTIPLE_INSTANCES_FOUND_MSG; 195 | } 196 | 197 | if (temp.length <= 0) { 198 | return null; 199 | } 200 | 201 | return temp[0]; 202 | } 203 | 204 | firstOrDefault(predicate: (item: KeyValuePair)=> boolean) : KeyValuePair { 205 | for (let i=0; i)=> boolean) : KeyValuePair { 217 | for (let i=this.length; i>=0; i--) { 218 | let item = this.list[i - 1]; 219 | if (predicate(item)) 220 | { 221 | return item; 222 | } 223 | } 224 | 225 | return null; 226 | } 227 | 228 | where(predicate: (item: KeyValuePair)=> boolean) : IDictionary { 229 | let temp = new Dictionary(); 230 | 231 | this.list.filter(element => { 232 | if (predicate(element)) 233 | { 234 | temp.add(element.key, element.value); 235 | } 236 | }); 237 | 238 | return temp; 239 | } 240 | 241 | select(predicate: (item: KeyValuePair)=> TResult) : IEnumerable { 242 | let temp = new List(); 243 | 244 | this.forEach(x => temp.add(predicate(x))); 245 | 246 | return temp; 247 | } 248 | 249 | forEach(predicate: (item: KeyValuePair)=> void) : void { 250 | this.list.forEach(x => predicate(x)); 251 | } 252 | 253 | toArray() : Array> { 254 | return this.list.slice(); 255 | } 256 | 257 | join(outer: IEnumerable, conditionInner: (item: KeyValuePair)=> TMatch, 258 | conditionOuter: (item: TOuter)=> TMatch, select: (x: KeyValuePair, y:TOuter)=> TResult, leftJoin: boolean = false) : IEnumerable { 259 | let resultList = new List(); 260 | 261 | this.list.forEach(x => { 262 | let outerEntries = outer.toArray().filter(y => conditionInner(x) === conditionOuter(y)); 263 | 264 | if (leftJoin && outerEntries && outerEntries.length <= 0) { 265 | resultList.add(select(x, null)); 266 | } 267 | else { 268 | outerEntries.forEach(z => resultList.add(select(x, z))); 269 | } 270 | }) 271 | 272 | return resultList; 273 | } 274 | 275 | groupBy(predicate: (item: KeyValuePair) => Array) : IEnumerable>> { 276 | let groups = {}; 277 | this.list.forEach(function (o) { 278 | var group = JSON.stringify(predicate(o)); 279 | groups[group] = groups[group] || []; 280 | groups[group].push(o); 281 | }); 282 | let g = Object.keys(groups).map(function (group) { 283 | let a = group.substr(1, group.length - 2); 284 | 285 | let grp= new Group>(new List(a.split(',')).select(x => x.replace(/^(")?(.*?)(")?$/ig, "$2")).toArray(), 286 | groups[group]); 287 | 288 | return grp; 289 | }); 290 | 291 | return new List>>(g); 292 | } 293 | 294 | selectMany(predicate: (item: KeyValuePair)=> Array) : IEnumerable { 295 | return this.list.reduce((out, inx) => { 296 | var items = predicate(inx); 297 | out.addRange(items); 298 | return out; 299 | }, new List(new Array())); 300 | } 301 | 302 | orderBy(comparer: IComparer>) : IEnumerable> { 303 | let temp = this.list.sort((x,y) => comparer.compare(x, y)); 304 | 305 | return new List>(temp); 306 | } 307 | 308 | distinct(comparer: IEqualityComparer>) : IEnumerable> { 309 | let uniques = new List>(); 310 | this.forEach(x => { 311 | if (uniques.length > 0) { 312 | if (!uniques.any(y => comparer.equals(x, y))) 313 | { 314 | uniques.add(x); 315 | } 316 | } 317 | else { 318 | uniques.add(x); 319 | } 320 | }); 321 | 322 | return uniques; 323 | } 324 | 325 | union(list: IEnumerable>) : IDictionary { 326 | this.addRange(list.toArray()); 327 | 328 | return this; 329 | } 330 | 331 | reverse(): IEnumerable> { 332 | return new List>(this.list.slice().reverse()); 333 | } 334 | 335 | skip(no: number) : IDictionary { 336 | if (no > 0) { 337 | return new Dictionary(this.list.slice(no, this.list.length - 1)); 338 | } 339 | 340 | return this; 341 | } 342 | 343 | take(no: number) : IDictionary { 344 | if (no > 0) { 345 | return new Dictionary(this.list.slice(0, no)); 346 | } 347 | 348 | return this; 349 | } 350 | 351 | sum(predicate: (item: KeyValuePair)=> number) : number { 352 | let sum: number = 0; 353 | this.list.forEach(x => sum = sum + predicate(x)); 354 | 355 | return sum; 356 | } 357 | 358 | avg(predicate: (item: KeyValuePair)=> number) : number { 359 | return this.sum(predicate) / this.length; 360 | } 361 | 362 | min(predicate: (item: KeyValuePair)=> number) : number { 363 | let min: number = 0; 364 | let i = 0; 365 | this.list.forEach(x => 366 | { 367 | if (i == 0) { 368 | min = predicate(x); 369 | } 370 | else { 371 | let val = predicate(x); 372 | if (val < min) { 373 | min = val; 374 | } 375 | } 376 | i++; 377 | }); 378 | 379 | return min; 380 | } 381 | 382 | max(predicate: (item: KeyValuePair)=> number) : number { 383 | let max: number = 0; 384 | let i = 0; 385 | this.list.forEach(x => 386 | { 387 | if (i == 0) { 388 | max = predicate(x); 389 | } 390 | else { 391 | let val = predicate(x); 392 | if (val > max) { 393 | max = val; 394 | } 395 | } 396 | i++; 397 | }); 398 | 399 | return max; 400 | } 401 | 402 | count(predicate: (item: KeyValuePair)=> boolean = null) : number { 403 | if (!predicate) { 404 | return this.length; 405 | } 406 | 407 | let count: number = 0; 408 | this.list.forEach(x => { 409 | if(predicate(x)) { 410 | count++; 411 | } 412 | }); 413 | 414 | return count; 415 | } 416 | } 417 | 418 | export class KeyValuePair { 419 | key: TKey; 420 | value: TValue; 421 | 422 | constructor(key: TKey, value: TValue) { 423 | this.key = key; 424 | this.value = value; 425 | } 426 | } -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface IEnumerable { 2 | elementAt(index: number) : T; 3 | any(predicate?: (item: T)=> boolean) : boolean; 4 | all(predicate?: (item: T)=> boolean) : boolean; 5 | single(predicate?: (item: T)=> boolean) : T; 6 | first(predicate?: (item: T)=> boolean) : T; 7 | last(predicate?: (item: T)=> boolean) : T; 8 | singleOrDefault(predicate: (item: T)=> boolean) : T; 9 | firstOrDefault(predicate: (item: T)=> boolean) : T; 10 | lastOrDefault(predicate: (item: T)=> boolean) : T; 11 | where(predicate: (item: T)=> boolean) : IEnumerable; 12 | select(predicate: (item: T)=> TResult) : IEnumerable; 13 | selectMany(predicate: (item: T)=> Array) : IEnumerable; 14 | join(outer: IEnumerable, conditionInner: (item: T)=> TMatch, 15 | conditionOuter: (item: TOuter)=> TMatch, select: (x: T, y:TOuter)=> TResult, leftJoin?: boolean) : IEnumerable; 16 | groupBy(predicate: (item: T) => Array) : IEnumerable>; 17 | orderBy(comparer: IComparer) : IEnumerable; 18 | distinct(comparer: IEqualityComparer) : IEnumerable; 19 | union(list: IEnumerable) : IEnumerable; 20 | reverse(): IEnumerable; 21 | skip(no: number) : IEnumerable; 22 | take(no: number) : IEnumerable; 23 | sum(predicate: (item: T)=> number) : number; 24 | avg(predicate: (item: T)=> number) : number; 25 | min(predicate: (item: T)=> number) : number; 26 | max(predicate: (item: T)=> number) : number; 27 | count(predicate?: (item: T)=> boolean) : number; 28 | forEach(predicate: (item: T)=> void) : void; 29 | length: number; 30 | toArray() : Array; 31 | asEnumerable() : IEnumerable; 32 | } 33 | 34 | export interface IGroup { 35 | groups: any[]; 36 | list: IEnumerable; 37 | } 38 | 39 | export interface IComparer { 40 | compare(x:T, y: T) : number; 41 | } 42 | 43 | export interface IEqualityComparer { 44 | equals(x:T, y: T) : boolean; 45 | } -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/list.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { List, IList } from './list'; 4 | import { IEnumerable, IComparer, IEqualityComparer } from './interfaces'; 5 | import { ITEM_NOT_FOUND_MSG, MULTIPLE_INSTANCES_FOUND_MSG } from './common'; 6 | 7 | //using distribution 8 | //import { List, IEnumerable, IComparer, IEqualityComparer, ITEM_NOT_FOUND_MSG, MULTIPLE_INSTANCES_FOUND_MSG } from '../../../../dist/ts-generic-collections'; 9 | 10 | describe('List', () => { 11 | 12 | beforeEach(async(() => { 13 | 14 | })); 15 | 16 | beforeEach(() => { 17 | 18 | }); 19 | 20 | it('instantiate list from json', () => { 21 | let jsonOwnerArray = '[{"id":1, "name": "John Doe"},{"id":2, "name": "Jane Doe"}]'; 22 | 23 | let ownerArray: Owner[] = JSON.parse(jsonOwnerArray); 24 | 25 | let list = new List(ownerArray); 26 | 27 | var jane = list.singleOrDefault(x => x.id == 2); 28 | 29 | expect(list.length == 2); 30 | expect(jane.name == "Jane Doe"); 31 | }); 32 | 33 | it('add', () => { 34 | let list = new List(); 35 | 36 | let owner = new Owner(); 37 | owner.name = "John Doe"; 38 | 39 | //add 40 | list.add(owner); 41 | 42 | expect(list.length == 1).toBeTruthy(); 43 | }); 44 | 45 | it('addRange', () => { 46 | let list = new List(); 47 | 48 | let owner = new Owner(); 49 | owner.name = "John Doe"; 50 | 51 | list.add(owner); 52 | 53 | let array = new Array(); 54 | 55 | owner = new Owner(); 56 | owner.name = "Jane Doe"; 57 | 58 | array.push(owner); 59 | 60 | owner = new Owner(); 61 | owner.name = "Peter Smith"; 62 | 63 | array.push(owner); 64 | 65 | list.addRange(array); 66 | 67 | expect(list.length == 3); 68 | }); 69 | 70 | it('remove', () => { 71 | let list = new List(); 72 | 73 | let owner = new Owner(); 74 | owner.name = "John Doe"; 75 | list.add(owner); 76 | 77 | owner = new Owner(); 78 | owner.name = "Jane Doe"; 79 | list.add(owner); 80 | 81 | //remove 82 | list.remove(owner => owner.name.includes('Doe')); 83 | 84 | expect(list.length === 0).toBeTruthy(); 85 | }); 86 | 87 | it('removeAt', () => { 88 | let list = new List(); 89 | 90 | let owner = new Owner(); 91 | owner.name = "John Doe"; 92 | list.add(owner); 93 | 94 | owner = new Owner(); 95 | owner.name = "Jane Doe"; 96 | list.add(owner); 97 | 98 | //removeAt 99 | list.removeAt(0); 100 | 101 | expect(list.length === 1).toBeTruthy(); 102 | expect(list.elementAt(0).name === 'Jane Doe').toBeTruthy(); 103 | }); 104 | 105 | it('elementAt', () => { 106 | let list = new List(); 107 | 108 | let owner = new Owner(); 109 | owner.name = "John Doe"; 110 | list.add(owner); 111 | 112 | owner = new Owner(); 113 | owner.name = "Jane Doe"; 114 | list.add(owner); 115 | 116 | //remove 117 | let result = list.elementAt(1); 118 | 119 | expect(result.name == "Jane Doe").toBeTruthy(); 120 | }); 121 | 122 | it('any', () => { 123 | let list = new List(); 124 | 125 | let owner = new Owner(); 126 | owner.name = "John Doe"; 127 | list.add(owner); 128 | 129 | owner = new Owner(); 130 | owner.name = "Jane Doe"; 131 | list.add(owner); 132 | 133 | //remove 134 | let result = list.any(x => x.name == "Jane Doe"); 135 | 136 | expect(result).toBeTruthy(); 137 | }); 138 | 139 | it('all', () => { 140 | let list = new List(); 141 | 142 | let owner = new Owner(); 143 | owner.name = "John Doe"; 144 | list.add(owner); 145 | 146 | owner = new Owner(); 147 | owner.name = "Jane Doe"; 148 | list.add(owner); 149 | 150 | //remove 151 | let result = list.all(x => x.name.includes("Doe")); 152 | 153 | expect(result).toBeTruthy(); 154 | }); 155 | 156 | it('where', () => { 157 | let owners = new List(); 158 | 159 | let owner = new Owner(); 160 | owner.id = 1; 161 | owner.name = "John Doe"; 162 | owners.add(owner); 163 | 164 | owner = new Owner(); 165 | owner.id = 2; 166 | owner.name = "Jane Doe"; 167 | owners.add(owner); 168 | 169 | let pets = new List(); 170 | 171 | let pet = new Pet(); 172 | pet.ownerId = 2; 173 | pet.name = "Sam"; 174 | pet.sex = Sex.M; 175 | 176 | pets.add(pet); 177 | 178 | pet = new Pet(); 179 | pet.ownerId = 1; 180 | pet.name = "Jenny"; 181 | pet.sex = Sex.F; 182 | 183 | pets.add(pet); 184 | 185 | //Get owners who have Female pets 186 | let ownersWhoHaveFemalePets = owners.join(pets, owner => owner.id, pet => pet.ownerId, (x, y) => new OwnerPet(x,y)) 187 | .where(x => x.pet.sex == Sex.F) 188 | .select(x => x.owner); 189 | 190 | expect(ownersWhoHaveFemalePets.length === 1).toBeTruthy(); 191 | expect(ownersWhoHaveFemalePets.toArray()[0].name = "John Doe").toBeTruthy(); 192 | }); 193 | 194 | it('singleOrDefault', () => { 195 | let list = new List(); 196 | 197 | let owner = new Owner(); 198 | owner.name = "John Doe"; 199 | list.add(owner); 200 | 201 | owner = new Owner(); 202 | owner.name = "Jane Doe"; 203 | list.add(owner); 204 | 205 | //singleOrDefault 206 | let ownerResult = list.singleOrDefault(owner => owner.name == 'John Doe'); 207 | 208 | expect(ownerResult.name == "John Doe").toBeTruthy(); 209 | }); 210 | 211 | it('singleOrDefault fail', () => { 212 | let list = new List(); 213 | 214 | let owner = new Owner(); 215 | owner.name = "John Doe"; 216 | list.add(owner); 217 | 218 | owner = new Owner(); 219 | owner.name = "Jane Doe"; 220 | list.add(owner); 221 | 222 | try 223 | { 224 | //singleOrDefault fail 225 | let ownerResult = list.singleOrDefault(owner => owner.name.includes('Doe')); 226 | } 227 | catch(e) { 228 | expect(e == MULTIPLE_INSTANCES_FOUND_MSG).toBeTruthy(); 229 | } 230 | }); 231 | 232 | it('single', () => { 233 | let list = new List(); 234 | 235 | let owner = new Owner(); 236 | owner.name = "John Doe"; 237 | list.add(owner); 238 | 239 | owner = new Owner(); 240 | owner.name = "Jane Doe"; 241 | list.add(owner); 242 | 243 | //single 244 | let ownerResult = list.single(owner => owner.name == 'John Doe'); 245 | 246 | expect(ownerResult.name == "John Doe").toBeTruthy(); 247 | }); 248 | 249 | it('single fail', () => { 250 | let list = new List(); 251 | 252 | let owner = new Owner(); 253 | owner.name = "John Doe"; 254 | list.add(owner); 255 | 256 | owner = new Owner(); 257 | owner.name = "Jane Doe"; 258 | list.add(owner); 259 | 260 | try 261 | { 262 | //single 263 | let ownerResult = list.single(owner => owner.name == 'Peter Smith'); 264 | } 265 | catch(e) { 266 | expect(e == ITEM_NOT_FOUND_MSG); 267 | } 268 | }); 269 | 270 | it('firstOrDefault', () => { 271 | let list = new List(); 272 | 273 | let owner = new Owner(); 274 | owner.name = "John Doe"; 275 | list.add(owner); 276 | 277 | owner = new Owner(); 278 | owner.name = "Jane Doe"; 279 | list.add(owner); 280 | 281 | //firstOrDefault 282 | let ownerResult = list.firstOrDefault(owner => owner.name.includes('Doe')); 283 | 284 | expect(ownerResult.name == "John Doe").toBeTruthy(); 285 | }); 286 | 287 | it('first', () => { 288 | let list = new List(); 289 | 290 | let owner = new Owner(); 291 | owner.name = "John Doe"; 292 | list.add(owner); 293 | 294 | owner = new Owner(); 295 | owner.name = "Jane Doe"; 296 | list.add(owner); 297 | 298 | //last 299 | let ownerResult = list.first(owner => owner.name.includes('Doe')); 300 | 301 | expect(ownerResult.name == "John Doe").toBeTruthy(); 302 | }); 303 | 304 | it('first fail', () => { 305 | let list = new List(); 306 | 307 | let owner = new Owner(); 308 | owner.name = "John Doe"; 309 | list.add(owner); 310 | 311 | owner = new Owner(); 312 | owner.name = "Jane Doe"; 313 | list.add(owner); 314 | 315 | try 316 | { 317 | //last 318 | let ownerResult = list.first(owner => owner.name == 'Peter Smith'); 319 | } 320 | catch(e) { 321 | expect(e == ITEM_NOT_FOUND_MSG); 322 | } 323 | }); 324 | 325 | it('lastOrDefault', () => { 326 | let list = new List(); 327 | 328 | let owner = new Owner(); 329 | owner.name = "John Doe"; 330 | list.add(owner); 331 | 332 | owner = new Owner(); 333 | owner.name = "Jane Doe"; 334 | list.add(owner); 335 | 336 | //firstOrDefault 337 | let ownerResult = list.lastOrDefault(owner => owner.name.includes('Doe')); 338 | 339 | expect(ownerResult.name == "Jane Doe").toBeTruthy(); 340 | }); 341 | 342 | it('last', () => { 343 | let list = new List(); 344 | 345 | let owner = new Owner(); 346 | owner.name = "John Doe"; 347 | list.add(owner); 348 | 349 | owner = new Owner(); 350 | owner.name = "Jane Doe"; 351 | list.add(owner); 352 | 353 | //last 354 | let ownerResult = list.last(owner => owner.name.includes('Doe')); 355 | 356 | expect(ownerResult.name == "Jane Doe").toBeTruthy(); 357 | }); 358 | 359 | it('last fail', () => { 360 | let list = new List(); 361 | 362 | let owner = new Owner(); 363 | owner.name = "John Doe"; 364 | list.add(owner); 365 | 366 | owner = new Owner(); 367 | owner.name = "Jane Doe"; 368 | list.add(owner); 369 | 370 | try 371 | { 372 | //last 373 | let ownerResult = list.last(owner => owner.name == 'Peter Smith'); 374 | } 375 | catch(e) { 376 | expect(e == ITEM_NOT_FOUND_MSG); 377 | } 378 | }); 379 | 380 | it('join', () => { 381 | let owners = new List(); 382 | 383 | let owner = new Owner(); 384 | owner.id = 1; 385 | owner.name = "John Doe"; 386 | owners.add(owner); 387 | 388 | owner = new Owner(); 389 | owner.id = 2; 390 | owner.name = "Jane Doe"; 391 | owners.add(owner); 392 | 393 | let pets = new List(); 394 | 395 | let pet = new Pet(); 396 | pet.ownerId = 2; 397 | pet.name = "Sam"; 398 | 399 | pets.add(pet); 400 | 401 | pet = new Pet(); 402 | pet.ownerId = 2; 403 | pet.name = "Pete"; 404 | 405 | pets.add(pet); 406 | 407 | pet = new Pet(); 408 | pet.ownerId = 1; 409 | pet.name = "Bob"; 410 | 411 | pets.add(pet); 412 | 413 | //join 414 | let ownerPets = owners.join(pets, owner => owner.id, pet => pet.ownerId, (x, y) => new OwnerPet(x,y)); 415 | 416 | expect(ownerPets.toArray().filter(op => op.owner.name == "John Doe" && op.pet.name == "Bob").length == 1).toBeTruthy(); 417 | expect(ownerPets.toArray().filter(op => op.owner.name == "Jane Doe" && op.pet.name == "Sam").length == 1).toBeTruthy(); 418 | expect(ownerPets.toArray().filter(op => op.owner.name == "Jane Doe" && op.pet.name == "Pete").length == 1).toBeTruthy(); 419 | }); 420 | 421 | it('leftJoin', () => { 422 | let owners = new List(); 423 | 424 | let owner = new Owner(); 425 | owner.id = 1; 426 | owner.name = "John Doe"; 427 | owners.add(owner); 428 | 429 | owner = new Owner(); 430 | owner.id = 2; 431 | owner.name = "Jane Doe"; 432 | owners.add(owner); 433 | 434 | let pets = new List(); 435 | 436 | let pet = new Pet(); 437 | pet.ownerId = 2; 438 | pet.name = "Sam"; 439 | 440 | pets.add(pet); 441 | 442 | //leftJoin 443 | let ownerPets = owners.join(pets, owner => owner.id, pet => pet.ownerId, (x, y) => new OwnerPet(x,y), true); 444 | 445 | expect(ownerPets.toArray().filter(op => op.owner.name == "John Doe" && !op.pet).length == 1).toBeTruthy(); 446 | expect(ownerPets.toArray().filter(op => op.owner.name == "Jane Doe" && op.pet.name == "Sam").length == 1).toBeTruthy(); 447 | }); 448 | 449 | it('groupBy', () => { 450 | let owners = new List(); 451 | 452 | let owner = new Owner(); 453 | owner.id = 1; 454 | owner.name = "John Doe"; 455 | owners.add(owner); 456 | 457 | owner = new Owner(); 458 | owner.id = 2; 459 | owner.name = "Jane Doe"; 460 | owners.add(owner); 461 | 462 | let pets = new List(); 463 | 464 | let pet = new Pet(); 465 | pet.ownerId = 2; 466 | pet.name = "Sam"; 467 | pet.sex = Sex.M; 468 | 469 | pets.add(pet); 470 | 471 | pet = new Pet(); 472 | pet.ownerId = 2; 473 | pet.name = "Millie"; 474 | pet.sex = Sex.F; 475 | 476 | pets.add(pet); 477 | 478 | pet = new Pet(); 479 | pet.ownerId = 1; 480 | pet.name = "Jenny"; 481 | pet.sex = Sex.F; 482 | 483 | pets.add(pet); 484 | 485 | //groupBy 486 | let ownersByPetSex = owners.join(pets, owner => owner.id, pet => pet.ownerId, (x, y) => new OwnerPet(x,y)) 487 | .groupBy(x => [x.pet.sex]) 488 | .select(x => new OwnersByPetSex(x.groups[0], x.list.select(x => x.owner))); 489 | 490 | expect(ownersByPetSex.toArray().length === 2).toBeTruthy(); 491 | 492 | expect(ownersByPetSex.toArray()[0].sex == Sex.F).toBeTruthy(); 493 | expect(ownersByPetSex.toArray()[0].owners.length === 2).toBeTruthy(); 494 | expect(ownersByPetSex.toArray()[0].owners.toArray()[0].name == "John Doe").toBeTruthy(); 495 | expect(ownersByPetSex.toArray()[0].owners.toArray()[1].name == "Jane Doe").toBeTruthy(); 496 | 497 | expect(ownersByPetSex.toArray()[1].sex == Sex.M).toBeTruthy(); 498 | expect(ownersByPetSex.toArray()[1].owners.length == 1).toBeTruthy(); 499 | expect(ownersByPetSex.toArray()[1].owners.toArray()[0].name == "Jane Doe").toBeTruthy(); 500 | }); 501 | 502 | it('selectMany', () => { 503 | let ownerPets = new List(); 504 | 505 | let owner = new Owner(); 506 | owner.id = 1; 507 | owner.name = "John Doe"; 508 | 509 | let pets = new List(); 510 | 511 | let pet = new Pet(); 512 | pet.ownerId = 1; 513 | pet.name = "Sam"; 514 | pet.sex = Sex.M; 515 | 516 | pets.add(pet); 517 | 518 | pet = new Pet(); 519 | pet.ownerId = 1; 520 | pet.name = "Millie"; 521 | pet.sex = Sex.F; 522 | 523 | pets.add(pet); 524 | 525 | ownerPets.add(new OwnerPets(owner, pets.toArray())); 526 | 527 | owner = new Owner(); 528 | owner.id = 2; 529 | owner.name = "Jane Doe"; 530 | 531 | pets = new List(); 532 | 533 | pet = new Pet(); 534 | pet.ownerId = 2; 535 | pet.name = "Moby"; 536 | pet.sex = Sex.M; 537 | 538 | pets.add(pet); 539 | 540 | pet = new Pet(); 541 | pet.ownerId = 2; 542 | pet.name = "Billie"; 543 | pet.sex = Sex.F; 544 | 545 | pets.add(pet); 546 | 547 | ownerPets.add(new OwnerPets(owner, pets.toArray())); 548 | 549 | //selectMany 550 | var result = ownerPets.selectMany(x => x.pets); 551 | 552 | expect(result.length === 4).toBeTruthy(); 553 | }); 554 | 555 | it('groupBy multiple fields', () => { 556 | let owners = new List(); 557 | 558 | let owner = new Owner(); 559 | owner.id = 1; 560 | owner.name = "John Doe"; 561 | owner.sex = Sex.M; 562 | owners.add(owner); 563 | 564 | owner = new Owner(); 565 | owner.id = 2; 566 | owner.name = "Jane Doe"; 567 | owner.sex = Sex.F; 568 | owners.add(owner); 569 | 570 | let pets = new List(); 571 | 572 | let pet = new Pet(); 573 | pet.ownerId = 2; 574 | pet.name = "Sam"; 575 | pet.type = PetType.Dog; 576 | pet.sex = Sex.M; 577 | 578 | pets.add(pet); 579 | 580 | pet = new Pet(); 581 | pet.ownerId = 1; 582 | pet.name = "Jenny"; 583 | pet.type = PetType.Cat; 584 | pet.sex = Sex.F; 585 | 586 | pets.add(pet); 587 | 588 | //groupBy multiple 589 | let ownersByPetTypeAndSex = owners.join(pets, owner => owner.id, pet => pet.ownerId, (x, y) => new OwnerPet(x,y)) 590 | .groupBy(x => [x.pet.sex, x.pet.type]) 591 | .select(x => new OwnersByPetTypeAndSex(x.groups[1], x.groups[0], x.list.select(x => x.owner))); 592 | 593 | expect(ownersByPetTypeAndSex.toArray().length === 2).toBeTruthy(); 594 | 595 | expect(ownersByPetTypeAndSex.toArray()[0].type == PetType.Cat).toBeTruthy(); 596 | expect(ownersByPetTypeAndSex.toArray()[0].sex == Sex.F).toBeTruthy(); 597 | expect(ownersByPetTypeAndSex.toArray()[0].owners.length === 1).toBeTruthy(); 598 | expect(ownersByPetTypeAndSex.toArray()[0].owners.toArray()[0].name == "John Doe").toBeTruthy(); 599 | 600 | expect(ownersByPetTypeAndSex.toArray()[1].type == PetType.Dog).toBeTruthy(); 601 | expect(ownersByPetTypeAndSex.toArray()[1].sex == Sex.M).toBeTruthy(); 602 | expect(ownersByPetTypeAndSex.toArray()[1].owners.length === 1).toBeTruthy(); 603 | expect(ownersByPetTypeAndSex.toArray()[1].owners.toArray()[0].name == "Jane Doe").toBeTruthy(); 604 | }); 605 | 606 | it('orderBy', () => { 607 | let owners = new List(); 608 | 609 | let owner = new Owner(); 610 | owner.id = 1; 611 | owner.name = "John Doe"; 612 | owners.add(owner); 613 | 614 | owner = new Owner(); 615 | owner.id = 2; 616 | owner.name = "Jane Doe"; 617 | owners.add(owner); 618 | 619 | let pets = new List(); 620 | 621 | let pet = new Pet(); 622 | pet.ownerId = 2; 623 | pet.name = "Sam"; 624 | 625 | pets.add(pet); 626 | 627 | pet = new Pet(); 628 | pet.ownerId = 2; 629 | pet.name = "Abby"; 630 | 631 | pets.add(pet); 632 | 633 | pet = new Pet(); 634 | pet.ownerId = 1; 635 | pet.name = "Jason"; 636 | 637 | pets.add(pet); 638 | 639 | pet = new Pet(); 640 | pet.ownerId = 1; 641 | pet.name = "Bob"; 642 | 643 | pets.add(pet); 644 | 645 | //orderBy 646 | let ownerPets = owners.join(pets, owner => owner.id, pet => pet.ownerId, (x, y) => new OwnerPet(x,y)) 647 | .orderBy(new Comparer()); 648 | 649 | expect(ownerPets.toArray()[0].owner.id == 1).toBeTruthy(); 650 | expect(ownerPets.toArray()[0].pet.name == "Bob").toBeTruthy(); 651 | 652 | expect(ownerPets.toArray()[1].owner.id == 1).toBeTruthy(); 653 | expect(ownerPets.toArray()[1].pet.name == "Jason").toBeTruthy(); 654 | 655 | expect(ownerPets.toArray()[2].owner.id == 2).toBeTruthy(); 656 | expect(ownerPets.toArray()[2].pet.name == "Abby").toBeTruthy(); 657 | 658 | expect(ownerPets.toArray()[3].owner.id == 2).toBeTruthy(); 659 | expect(ownerPets.toArray()[3].pet.name == "Sam").toBeTruthy(); 660 | }); 661 | 662 | it('union', () => { 663 | let ownersA = new List(); 664 | 665 | let owner = new Owner(); 666 | owner.id = 1; 667 | owner.name = "John Doe"; 668 | ownersA.add(owner); 669 | 670 | owner = new Owner(); 671 | owner.id = 2; 672 | owner.name = "Jane Doe"; 673 | ownersA.add(owner); 674 | 675 | let ownersB = new List(); 676 | 677 | owner = new Owner(); 678 | owner.id = 1; 679 | owner.name = "Peter"; 680 | ownersB.add(owner); 681 | 682 | owner = new Owner(); 683 | owner.id = 2; 684 | owner.name = "Meghan"; 685 | ownersB.add(owner); 686 | 687 | //union 688 | let ownersResult = ownersA.union(ownersB); 689 | 690 | expect(ownersResult.length === 4).toBeTruthy(); 691 | }); 692 | 693 | it('reverse', () => { 694 | let list = new List(); 695 | 696 | let owner = new Owner(); 697 | owner.name = "John Doe"; 698 | list.add(owner); 699 | 700 | owner = new Owner(); 701 | owner.name = "Jane Doe"; 702 | list.add(owner); 703 | 704 | //reverse 705 | let result = list.reverse(); 706 | 707 | expect(result.elementAt(0).name == "Jane Doe").toBeTruthy(); 708 | expect(result.elementAt(1).name == "John Doe").toBeTruthy(); 709 | }); 710 | 711 | it('distinct', () => { 712 | let numbers: number[] = [1, 2, 3, 1, 3]; 713 | let list = new List(numbers); 714 | 715 | let distinct = list.distinct(new EqualityComparer()); 716 | 717 | expect(distinct.length == 3).toBeTruthy(); 718 | expect(distinct.elementAt(0) == 1).toBeTruthy(); 719 | expect(distinct.elementAt(1) == 2).toBeTruthy(); 720 | expect(distinct.elementAt(2) == 3).toBeTruthy(); 721 | }); 722 | 723 | it('skip', () => { 724 | let numbers: number[] = [1, 2, 3] 725 | let list: IList = new List(numbers); 726 | 727 | let result = list.skip(2); 728 | 729 | expect(result.length == 1); 730 | }); 731 | 732 | it('take', () => { 733 | let numbers: number[] = [1, 2, 3] 734 | let list: IList = new List(numbers); 735 | 736 | let result = list.take(2); 737 | 738 | expect(result.length == 2); 739 | }); 740 | 741 | it('skip and take', () => { 742 | let numbers: number[] = [1, 2, 3, 4] 743 | let list: IList = new List(numbers); 744 | 745 | let result = list.skip(1).take(2); 746 | 747 | expect(result.length == 2); 748 | }); 749 | 750 | it('sum', () => { 751 | let numbers: number[] = [1, 2, 3] 752 | let list: IList = new List(numbers); 753 | 754 | let sum = list.sum(x => x); 755 | 756 | expect(sum == 6); 757 | }); 758 | 759 | it('average', () => { 760 | let numbers: number[] = [1, 2, 3] 761 | let list: IList = new List(numbers); 762 | 763 | let avg = list.avg(x => x); 764 | 765 | expect(avg == 2); 766 | }); 767 | 768 | it('count', () => { 769 | let numbers: number[] = [1, 2, 3, 101, 102] 770 | let list: IList = new List(numbers); 771 | 772 | let countNumbersGreaterThan100 = list.count(x => x > 100); 773 | 774 | expect(countNumbersGreaterThan100 == 2); 775 | }); 776 | 777 | it('min', () => { 778 | let numbers: number[] = [5, 2, 1, 101, 102] 779 | let list: IList = new List(numbers); 780 | 781 | let min = list.min(x => x); 782 | 783 | expect(min == 1); 784 | }); 785 | 786 | it('max', () => { 787 | let numbers: number[] = [5, 2, 102, 102, 101] 788 | let list: IList = new List(numbers); 789 | 790 | let max = list.max(x => x); 791 | 792 | expect(max == 102); 793 | }); 794 | }); 795 | 796 | class Owner { 797 | id: number; 798 | name: string; 799 | sex: Sex; 800 | } 801 | 802 | enum PetType { 803 | Cat, 804 | Dog 805 | } 806 | 807 | class Pet { 808 | ownerId: number; 809 | name: string; 810 | sex: Sex; 811 | type: PetType; 812 | } 813 | 814 | enum Sex { 815 | M, 816 | F 817 | } 818 | 819 | class OwnerPet { 820 | owner: Owner; 821 | pet: Pet; 822 | 823 | constructor(owner: Owner, pet: Pet) { 824 | this.owner = owner; 825 | this.pet = pet; 826 | } 827 | } 828 | 829 | class OwnerPets { 830 | owner: Owner; 831 | pets: Pet[]; 832 | 833 | constructor(owner: Owner, pets: Pet[]) { 834 | this.owner = owner; 835 | this.pets = pets; 836 | } 837 | } 838 | 839 | class OwnersByPetSex { 840 | sex: Sex; 841 | owners: IEnumerable; 842 | 843 | constructor(sex: Sex, owners: IEnumerable) { 844 | this.sex = sex; 845 | this.owners = owners; 846 | } 847 | } 848 | 849 | class OwnersByPetTypeAndSex { 850 | type: PetType; 851 | sex: Sex; 852 | owners: IEnumerable; 853 | 854 | constructor(type: PetType, sex: Sex, owners: IEnumerable) { 855 | this.type = type; 856 | this.sex = sex; 857 | this.owners = owners; 858 | } 859 | } 860 | 861 | class Comparer implements IComparer { 862 | compare(x: OwnerPet, y: OwnerPet) : number { 863 | if (x.owner.id > y.owner.id) { 864 | if (x.pet.name > y.pet.name) { 865 | return 1; 866 | } 867 | 868 | return 0; 869 | } 870 | 871 | return -1 872 | } 873 | } 874 | 875 | class EqualityComparer implements IEqualityComparer { 876 | equals(x: number, y: number) : boolean { 877 | return x == y; 878 | } 879 | } -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/list.ts: -------------------------------------------------------------------------------- 1 | import { IEnumerable, IComparer, IEqualityComparer } from './interfaces'; 2 | import { Group, objCompare, ITEM_NOT_FOUND_MSG, MULTIPLE_INSTANCES_FOUND_MSG } from './common'; 3 | 4 | export interface IList extends IEnumerable { 5 | add(item: T) : void; 6 | addRange(items: T[]) : void; 7 | remove(predicate: (item:T) => boolean) : void; 8 | removeAt(index: number) : void; 9 | clear() : void; 10 | } 11 | 12 | export class List implements IList { 13 | 14 | private list: Array = new Array(); 15 | 16 | constructor(array: Array = null) { 17 | if (array) 18 | this.list = array; 19 | } 20 | 21 | /* IList */ 22 | 23 | add(item: T) : void { 24 | this.list.push(item); 25 | } 26 | 27 | addRange(items: T[]) : void { 28 | items.forEach(x => this.add(x)); 29 | } 30 | 31 | remove(predicate: (item:T) => boolean) : void { 32 | let temp = new Array(); 33 | 34 | this.list.forEach(element => { 35 | if (!predicate(element)) 36 | { 37 | temp.push(element); 38 | } 39 | }); 40 | 41 | this.list = temp; 42 | } 43 | 44 | removeAt(index: number) : void { 45 | this.list.splice(index, 1); 46 | } 47 | 48 | clear() : void { 49 | this.list = new Array(); 50 | } 51 | 52 | /* IEnumerable */ 53 | 54 | asEnumerable() : IEnumerable { 55 | return this; 56 | } 57 | 58 | get length(): number { 59 | return this.list.length; 60 | } 61 | 62 | elementAt(index: number) : T { 63 | try { 64 | return this.list[index]; 65 | } 66 | catch (e) { 67 | return null; 68 | } 69 | } 70 | 71 | any(predicate?: (item: T)=> boolean) : boolean { 72 | if (!predicate) { 73 | return this.list.length > 0; 74 | } 75 | 76 | for (let i=0; i boolean) : boolean { 86 | if (!predicate) { 87 | return this.list.length > 0; 88 | } 89 | 90 | for (let i=0; i boolean = null) : T { 100 | if (this.list.length <= 0) { 101 | throw ITEM_NOT_FOUND_MSG; 102 | } 103 | 104 | if (predicate) { 105 | let item = this.singleOrDefault(predicate); 106 | 107 | if (!item) { 108 | throw ITEM_NOT_FOUND_MSG; 109 | } 110 | 111 | return item; 112 | } 113 | 114 | return this.list[0]; 115 | } 116 | 117 | first(predicate: (item: T)=> boolean = null) : T { 118 | if (this.list.length <= 0) { 119 | throw ITEM_NOT_FOUND_MSG; 120 | } 121 | 122 | if (predicate) { 123 | let item = this.firstOrDefault(predicate); 124 | 125 | if (!item) { 126 | throw ITEM_NOT_FOUND_MSG; 127 | } 128 | 129 | return item; 130 | } 131 | 132 | return this.list[0]; 133 | } 134 | 135 | last(predicate: (item: T)=> boolean = null) : T { 136 | if (this.list.length <= 0) { 137 | throw ITEM_NOT_FOUND_MSG; 138 | } 139 | 140 | if (predicate) { 141 | let item = this.lastOrDefault(predicate); 142 | 143 | if (!item) { 144 | throw ITEM_NOT_FOUND_MSG; 145 | } 146 | 147 | return item; 148 | } 149 | 150 | return this.list[this.list.length - 1]; 151 | } 152 | 153 | singleOrDefault(predicate: (item: T)=> boolean) : T { 154 | let temp = new Array(); 155 | 156 | this.list.filter(element => { 157 | if (predicate(element)) 158 | { 159 | temp.push(element); 160 | } 161 | }); 162 | 163 | if (temp.length > 1) { 164 | throw MULTIPLE_INSTANCES_FOUND_MSG; 165 | } 166 | 167 | if (temp.length <= 0) { 168 | return null; 169 | } 170 | 171 | return temp[0]; 172 | } 173 | 174 | firstOrDefault(predicate: (item: T)=> boolean) : T { 175 | for (let i=0; i boolean) : T { 187 | for (let i=this.length; i>=0; i--) { 188 | let item = this.list[i - 1]; 189 | if (predicate(item)) 190 | { 191 | return item; 192 | } 193 | } 194 | 195 | return null; 196 | } 197 | 198 | where(predicate: (item: T)=> boolean) : IEnumerable { 199 | let temp = new List(); 200 | 201 | this.list.filter(element => { 202 | if (predicate(element)) 203 | { 204 | temp.add(element); 205 | } 206 | }); 207 | 208 | return temp; 209 | } 210 | 211 | select(predicate: (item: T)=> TResult) : IEnumerable { 212 | let temp = new List(); 213 | 214 | this.forEach(x => temp.add(predicate(x))); 215 | 216 | return temp; 217 | } 218 | 219 | forEach(predicate: (item: T)=> void) : void { 220 | this.list.forEach(x => predicate(x)); 221 | } 222 | 223 | toArray() : Array { 224 | return this.list.slice(); 225 | } 226 | 227 | join(outer: IEnumerable, conditionInner: (item: T)=> TMatch, 228 | conditionOuter: (item: TOuter)=> TMatch, select: (x: T, y:TOuter)=> TResult, leftJoin: boolean = false) : IEnumerable { 229 | let resultList = new List(); 230 | 231 | this.list.forEach(x => { 232 | let outerEntries = outer.toArray().filter(y => conditionInner(x) === conditionOuter(y)); 233 | 234 | if (leftJoin && outerEntries && outerEntries.length <= 0) { 235 | resultList.add(select(x, null)); 236 | } 237 | else { 238 | outerEntries.forEach(z => resultList.add(select(x, z))); 239 | } 240 | }) 241 | 242 | return resultList; 243 | } 244 | 245 | groupBy(predicate: (item: T) => Array) : IEnumerable> { 246 | let groups = {}; 247 | this.list.forEach(function (o) { 248 | var group = JSON.stringify(predicate(o)); 249 | groups[group] = groups[group] || []; 250 | groups[group].push(o); 251 | }); 252 | let g = Object.keys(groups).map(function (group) { 253 | let a = group.substr(1, group.length - 2); 254 | 255 | let grp= new Group(new List(a.split(',')).select(x => x.replace(/^(")?(.*?)(")?$/ig, "$2")).toArray(), 256 | groups[group]); 257 | 258 | return grp; 259 | }); 260 | 261 | return new List>(g); 262 | } 263 | 264 | selectMany(predicate: (item: T)=> Array) : IEnumerable { 265 | return this.list.reduce((out, inx) => { 266 | var items = predicate(inx); 267 | out.addRange(items); 268 | return out; 269 | }, new List(new Array())); 270 | } 271 | 272 | orderBy(comparer: IComparer) : IEnumerable { 273 | let temp = this.list.sort((x,y) => comparer.compare(x, y)); 274 | 275 | return new List(temp); 276 | } 277 | 278 | union(list: IEnumerable) : IEnumerable { 279 | this.addRange(list.toArray()); 280 | 281 | return this; 282 | } 283 | 284 | reverse(): IEnumerable { 285 | return new List(this.list.slice().reverse()); 286 | } 287 | 288 | distinct(comparer: IEqualityComparer) : IEnumerable { 289 | let uniques = new List(); 290 | this.forEach(x => { 291 | if (uniques.length > 0) { 292 | if (!uniques.any(y => comparer.equals(x, y))) 293 | { 294 | uniques.add(x); 295 | } 296 | } 297 | else { 298 | uniques.add(x); 299 | } 300 | }); 301 | 302 | return uniques; 303 | } 304 | 305 | skip(no: number) : IEnumerable { 306 | if (no > 0) { 307 | return new List(this.list.slice(no, this.list.length - 1)); 308 | } 309 | 310 | return this; 311 | } 312 | 313 | take(no: number) : IEnumerable { 314 | if (no > 0) { 315 | return new List(this.list.slice(0, no)); 316 | } 317 | 318 | return this; 319 | } 320 | 321 | sum(predicate: (item: T)=> number) : number { 322 | let sum: number = 0; 323 | this.list.forEach(x => sum = sum + predicate(x)); 324 | 325 | return sum; 326 | } 327 | 328 | avg(predicate: (item: T)=> number) : number { 329 | return this.sum(predicate) / this.length; 330 | } 331 | 332 | min(predicate: (item: T)=> number) : number { 333 | let min: number = 0; 334 | let i = 0; 335 | this.list.forEach(x => 336 | { 337 | if (i == 0) { 338 | min = predicate(x); 339 | } 340 | else { 341 | let val = predicate(x); 342 | if (val < min) { 343 | min = val; 344 | } 345 | } 346 | i++; 347 | }); 348 | 349 | return min; 350 | } 351 | 352 | max(predicate: (item: T)=> number) : number { 353 | let max: number = 0; 354 | let i = 0; 355 | this.list.forEach(x => 356 | { 357 | if (i == 0) { 358 | max = predicate(x); 359 | } 360 | else { 361 | let val = predicate(x); 362 | if (val > max) { 363 | max = val; 364 | } 365 | } 366 | i++; 367 | }); 368 | 369 | return max; 370 | } 371 | 372 | count(predicate: (item: T)=> boolean = null) : number { 373 | if (!predicate) { 374 | return this.length; 375 | } 376 | 377 | let count: number = 0; 378 | this.list.forEach(x => { 379 | if(predicate(x)) { 380 | count++; 381 | } 382 | }); 383 | 384 | return count; 385 | } 386 | 387 | } -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/queue.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { Queue } from './queue'; 4 | 5 | describe('Queue', () => { 6 | 7 | beforeEach(async(() => { 8 | 9 | })); 10 | 11 | beforeEach(() => { 12 | 13 | }); 14 | 15 | it('instantiate queue from json', () => { 16 | let jsonOwnerArray = '[{"id":1, "name": "John Doe"},{"id":2, "name": "Jane Doe"}]'; 17 | 18 | let ownerArray: Owner[] = JSON.parse(jsonOwnerArray); 19 | 20 | let list = new Queue(ownerArray); 21 | 22 | expect(list.toArray().length == 2); 23 | }); 24 | 25 | it('enqueue', () => { 26 | let queue = new Queue(); 27 | 28 | let owner = new Owner(); 29 | owner.name = "John Doe"; 30 | 31 | //enqueue 32 | queue.enqueue(owner); 33 | 34 | expect(queue.toArray().length == 1).toBeTruthy(); 35 | 36 | let owner2 = new Owner(); 37 | owner2.name = "Jane Doe"; 38 | 39 | //enqueue 40 | queue.enqueue(owner2); 41 | 42 | expect(queue.toArray().length == 2).toBeTruthy(); 43 | }); 44 | 45 | it('dequeue', () => { 46 | let queue = new Queue(); 47 | 48 | let owner = new Owner(); 49 | owner.name = "John Doe"; 50 | 51 | //enqueue 52 | queue.enqueue(owner); 53 | 54 | let owner2 = new Owner(); 55 | owner2.name = "Jane Doe"; 56 | 57 | //enqueue 58 | queue.enqueue(owner2); 59 | 60 | expect(queue.toArray().length == 2).toBeTruthy(); 61 | 62 | //dequeue 63 | var dequeued = queue.dequeue(); 64 | 65 | expect(dequeued.name == "John Doe").toBeTruthy(); 66 | expect(queue.toArray().length == 1).toBeTruthy(); 67 | 68 | var dequeued2 = queue.dequeue(); 69 | 70 | expect(dequeued2.name == "Jane Doe").toBeTruthy(); 71 | expect(queue.toArray().length == 0).toBeTruthy(); 72 | }); 73 | 74 | it('peek', () => { 75 | let queue = new Queue(); 76 | 77 | let owner = new Owner(); 78 | owner.name = "John Doe"; 79 | 80 | //enqueue 81 | queue.enqueue(owner); 82 | 83 | let owner2 = new Owner(); 84 | owner2.name = "Jane Doe"; 85 | 86 | //enqueue 87 | queue.enqueue(owner2); 88 | 89 | expect(queue.toArray().length == 2).toBeTruthy(); 90 | 91 | //peek 92 | var peeked = queue.peek(); 93 | 94 | expect(peeked.name == "John Doe").toBeTruthy(); 95 | expect(queue.toArray().length == 2).toBeTruthy(); 96 | }); 97 | 98 | it('forEach', () => { 99 | let queue = new Queue(); 100 | 101 | let owner = new Owner(); 102 | owner.name = "John Doe"; 103 | 104 | //enqueue 105 | queue.enqueue(owner); 106 | 107 | let owner2 = new Owner(); 108 | owner2.name = "Jane Doe"; 109 | 110 | //enqueue 111 | queue.enqueue(owner2); 112 | 113 | expect(queue.toArray().length == 2).toBeTruthy(); 114 | 115 | let i: number = 0; 116 | //forEach 117 | queue.forEach(item => { 118 | i++; 119 | }); 120 | 121 | expect(queue.toArray().length == i).toBeTruthy(); 122 | }); 123 | 124 | }); 125 | 126 | class Owner { 127 | id: number; 128 | name: string; 129 | } 130 | -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/queue.ts: -------------------------------------------------------------------------------- 1 | import { objCompare } from './common'; 2 | import { List } from './list'; 3 | 4 | export interface IQueue { 5 | clear() : void; 6 | contains(item: T) : boolean; 7 | dequeue() : T; 8 | enqueue(item: T) : void; 9 | peek(): T; 10 | forEach(predicate: (item: T)=> void) : void; 11 | toArray(): Array; 12 | } 13 | 14 | export class Queue implements IQueue { 15 | private list: List = new List(); 16 | 17 | constructor(array: Array = null) { 18 | if (array) 19 | this.list = new List(array); 20 | } 21 | 22 | clear(): void { 23 | this.list.clear(); 24 | } 25 | contains(item: T): boolean { 26 | return this.list.any(x => objCompare(x, item)); 27 | } 28 | dequeue() : T { 29 | if (this.list.length > 0) 30 | { 31 | var element = this.list.elementAt(0); 32 | 33 | this.list.removeAt(0); 34 | 35 | return element; 36 | } 37 | 38 | return null; 39 | } 40 | enqueue(item: T) : void { 41 | this.list.add(item); 42 | } 43 | peek() : T { 44 | if (this.list.length > 0) 45 | { 46 | var element = this.list.elementAt(0); 47 | 48 | return element; 49 | } 50 | 51 | return null; 52 | } 53 | forEach(predicate: (item: T)=> void) : void { 54 | this.list.forEach(predicate); 55 | } 56 | toArray() : Array { 57 | return this.list.toArray(); 58 | } 59 | } -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/randomized.queue.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RandomizedQueue } from './randomized.queue'; 4 | 5 | describe('Randomized Queue', () => { 6 | 7 | beforeEach(async(() => { 8 | 9 | })); 10 | 11 | beforeEach(() => { 12 | 13 | }); 14 | 15 | it('instantiate queue from json', () => { 16 | let jsonOwnerArray = '[{"id":1, "name": "John Doe"},{"id":2, "name": "Jane Doe"}]'; 17 | 18 | let ownerArray: Owner[] = JSON.parse(jsonOwnerArray); 19 | 20 | let list = new RandomizedQueue(ownerArray); 21 | 22 | expect(list.toArray().length == 2); 23 | }); 24 | 25 | it('enqueue', () => { 26 | let queue = new RandomizedQueue(); 27 | 28 | let owner = new Owner(); 29 | owner.name = "John Doe"; 30 | 31 | //enqueue 32 | queue.enqueue(owner); 33 | 34 | expect(queue.toArray().length == 1).toBeTruthy(); 35 | 36 | let owner2 = new Owner(); 37 | owner2.name = "Jane Doe"; 38 | 39 | //enqueue 40 | queue.enqueue(owner2); 41 | 42 | expect(queue.toArray().length == 2).toBeTruthy(); 43 | }); 44 | 45 | it('dequeue', () => { 46 | let queue = new RandomizedQueue(); 47 | 48 | let owner = new Owner(); 49 | owner.name = "John Doe"; 50 | 51 | //enqueue 52 | queue.enqueue(owner); 53 | 54 | let owner2 = new Owner(); 55 | owner2.name = "Jane Doe"; 56 | 57 | //enqueue 58 | queue.enqueue(owner2); 59 | 60 | expect(queue.toArray().length == 2).toBeTruthy(); 61 | 62 | //dequeue 63 | var dequeued = queue.dequeue(); 64 | 65 | expect(queue.toArray().length == 1).toBeTruthy(); 66 | }); 67 | 68 | it('peek', () => { 69 | let queue = new RandomizedQueue(); 70 | 71 | let owner = new Owner(); 72 | owner.name = "John Doe"; 73 | 74 | //enqueue 75 | queue.enqueue(owner); 76 | 77 | let owner2 = new Owner(); 78 | owner2.name = "Jane Doe"; 79 | 80 | //enqueue 81 | queue.enqueue(owner2); 82 | 83 | expect(queue.toArray().length == 2).toBeTruthy(); 84 | 85 | //peek 86 | var peeked = queue.peek(); 87 | 88 | expect(peeked.name == "John Doe" || peeked.name == "Jane Doe").toBeTruthy(); 89 | expect(queue.toArray().length == 2).toBeTruthy(); 90 | }); 91 | 92 | it('peekAndDequeue', () => { 93 | let queue = new RandomizedQueue(); 94 | 95 | let owner = new Owner(); 96 | owner.name = "John Doe"; 97 | 98 | //enqueue 99 | queue.enqueue(owner); 100 | 101 | let owner2 = new Owner(); 102 | owner2.name = "Jane Doe"; 103 | 104 | //enqueue 105 | queue.enqueue(owner2); 106 | 107 | expect(queue.toArray().length == 2).toBeTruthy(); 108 | 109 | //peek 110 | var peeked = queue.peek(); 111 | 112 | expect(peeked.name == "John Doe" || peeked.name == "Jane Doe").toBeTruthy(); 113 | expect(queue.toArray().length == 2).toBeTruthy(); 114 | 115 | var dequeued = queue.dequeue(); 116 | expect(dequeued.name == peeked.name); 117 | expect(queue.toArray().length == 1).toBeTruthy(); 118 | }); 119 | 120 | it('forEach', () => { 121 | let queue = new RandomizedQueue(); 122 | 123 | let owner = new Owner(); 124 | owner.name = "John Doe"; 125 | 126 | //enqueue 127 | queue.enqueue(owner); 128 | 129 | let owner2 = new Owner(); 130 | owner2.name = "Jane Doe"; 131 | 132 | //enqueue 133 | queue.enqueue(owner2); 134 | 135 | expect(queue.toArray().length == 2).toBeTruthy(); 136 | 137 | let i: number = 0; 138 | //forEach 139 | queue.forEach(item => { 140 | i++; 141 | }); 142 | 143 | expect(queue.toArray().length == i).toBeTruthy(); 144 | }); 145 | 146 | }); 147 | 148 | class Owner { 149 | id: number; 150 | name: string; 151 | } 152 | -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/randomized.queue.ts: -------------------------------------------------------------------------------- 1 | import { IQueue } from './queue'; 2 | import { objCompare } from './common'; 3 | import { List } from './list'; 4 | 5 | export class RandomizedQueue implements IQueue { 6 | private list: List = new List(); 7 | private peekIndex: number = -1; 8 | 9 | constructor(array: Array = null) { 10 | if (array) 11 | this.list = new List(array); 12 | } 13 | 14 | clear(): void { 15 | this.list.clear(); 16 | } 17 | contains(item: T): boolean { 18 | return this.list.any(x => objCompare(x, item)); 19 | } 20 | dequeue() : T { 21 | if (this.list.length > 0) 22 | { 23 | var min = 0; 24 | var max = this.list.length; 25 | 26 | var index = this.peekIndex >= 0 ? this.peekIndex : Math.floor(Math.random() * (max - min)) + min; 27 | 28 | var element = this.list.elementAt(index); 29 | 30 | this.list.removeAt(index); 31 | 32 | this.peekIndex = -1; 33 | 34 | return element; 35 | } 36 | 37 | return null; 38 | } 39 | enqueue(item: T) : void { 40 | this.list.add(item); 41 | } 42 | peek() : T { 43 | if (this.list.length > 0) 44 | { 45 | var min = 0; 46 | var max = this.list.length; 47 | 48 | this.peekIndex = Math.floor(Math.random() * (max - min)) + min; 49 | 50 | var element = this.list.elementAt(this.peekIndex); 51 | 52 | return element; 53 | } 54 | 55 | return null; 56 | } 57 | forEach(predicate: (item: T)=> void) : void { 58 | this.list.forEach(predicate); 59 | } 60 | toArray() : Array { 61 | return this.list.toArray(); 62 | } 63 | } -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/sorted.dictionary.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { KeyValuePair } from './dictionary'; 3 | import { IComparer } from './interfaces'; 4 | import { IList, List } from './list'; 5 | import { SortedDictionary } from './sorted.dictionary'; 6 | 7 | describe('Sorted Dictionary', () => { 8 | 9 | beforeEach(async(() => { 10 | 11 | })); 12 | 13 | beforeEach(() => { 14 | 15 | }); 16 | 17 | it('instantiate dictionary from json', () => { 18 | let jsonKeyValuePairArray = '[{"key": 2, "value": {"id":2, "name": "Ford", "model": "F 100", "country": "US", "isLuxury": true }},{"key": 1, "value": {"id":1, "name": "Mercedez", "model": "S 400", "country": "Germany", "isLuxury": true }}]'; 19 | 20 | let keyValueArray: KeyValuePair[] = JSON.parse(jsonKeyValuePairArray); 21 | 22 | let list = new SortedDictionary(null, keyValueArray); 23 | 24 | var luxuryCars = list.where(x => x.value.isLuxury); 25 | 26 | expect(list.length == 2); 27 | expect(luxuryCars.toArray()[0].value.name == "Mercedez"); 28 | expect(luxuryCars.toArray()[0].value.name == "Ford"); 29 | }); 30 | 31 | it('add', () => { 32 | let dictionary = new SortedDictionary>(new CarComparer()); 33 | 34 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 35 | 36 | let features = new List(); 37 | 38 | let feature = new Feature(1, "2 - Door Sedan"); 39 | 40 | features.add(feature); 41 | 42 | dictionary.add(car, features); 43 | 44 | expect(dictionary.length == 1); 45 | expect(dictionary.containsKey(car)); 46 | expect(dictionary.containsValue(features)); 47 | expect(dictionary.tryGetValue(car).length == 1); 48 | }); 49 | 50 | it('add fail', () => { 51 | let dictionary = new SortedDictionary>(new CarComparer()); 52 | 53 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 54 | 55 | let features = new List(); 56 | 57 | let feature = new Feature(1, "2 - Door Sedan"); 58 | 59 | features.add(feature); 60 | 61 | dictionary.add(car, features); 62 | 63 | car = new Car(1, "Mercedez", "S 400", Country.Germany); 64 | 65 | try { 66 | dictionary.add(car, features); 67 | } 68 | catch(e) { 69 | expect(e == "Duplicate key. Cannot add."); 70 | } 71 | }); 72 | 73 | it('remove', () => { 74 | let dictionary = new SortedDictionary>(new CarComparer()); 75 | 76 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 77 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 78 | 79 | let features = new List(); 80 | 81 | let feature = new Feature(1, "2 - Door Sedan"); 82 | 83 | features.add(feature); 84 | 85 | dictionary.add(car, features); 86 | 87 | features = new List(); 88 | 89 | feature = new Feature(2, "4 - Door Sedan"); 90 | 91 | features.add(feature); 92 | 93 | dictionary.add(car2, features); 94 | 95 | expect(dictionary.length == 2); 96 | 97 | dictionary.remove(x => x.key.country == Country.Germany); 98 | 99 | expect(dictionary.length == 1).toBeTruthy(); 100 | }); 101 | 102 | it('removeAt', () => { 103 | let dictionary = new SortedDictionary>(new CarComparer()); 104 | 105 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 106 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 107 | 108 | let features = new List(); 109 | 110 | let feature = new Feature(1, "2 - Door Sedan"); 111 | 112 | features.add(feature); 113 | 114 | dictionary.add(car, features); 115 | 116 | features = new List(); 117 | 118 | feature = new Feature(2, "4 - Door Sedan"); 119 | 120 | features.add(feature); 121 | 122 | dictionary.add(car2, features); 123 | 124 | expect(dictionary.length == 2); 125 | 126 | dictionary.removeAt(1); 127 | 128 | expect(dictionary.length == 1).toBeTruthy(); 129 | expect(dictionary.elementAt(0).key.name === 'Jaguar').toBeTruthy(); 130 | }); 131 | 132 | it('where', () => { 133 | let dictionary = new SortedDictionary>(new CarComparer()); 134 | 135 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 136 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 137 | 138 | let features = new List(); 139 | 140 | let feature = new Feature(1, "2 - Door Sedan"); 141 | 142 | features.add(feature); 143 | 144 | dictionary.add(car, features); 145 | 146 | features = new List(); 147 | 148 | feature = new Feature(2, "4 - Door Sedan"); 149 | 150 | features.add(feature); 151 | 152 | dictionary.add(car2, features); 153 | 154 | let result = dictionary.where(x => x.value.any(x => x.name == "4 - Door Sedan")); 155 | 156 | expect(result.first().key.id == 2); 157 | }); 158 | 159 | it('singleOrDefault', () => { 160 | let dictionary = new SortedDictionary>(new CarComparer()); 161 | 162 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 163 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 164 | 165 | let features = new List(); 166 | 167 | let feature = new Feature(1, "2 - Door Sedan"); 168 | 169 | features.add(feature); 170 | 171 | dictionary.add(car, features); 172 | 173 | features = new List(); 174 | 175 | feature = new Feature(2, "4 - Door Sedan"); 176 | 177 | features.add(feature); 178 | 179 | dictionary.add(car2, features); 180 | 181 | let result = dictionary.singleOrDefault(x => x.key.country == Country.Germany); 182 | 183 | expect(result.key.id == 1); 184 | }); 185 | 186 | it('firstOrDefault', () => { 187 | let dictionary = new SortedDictionary>(new CarComparer()); 188 | 189 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 190 | let car2 = new Car(2, "Mercedez", "S 500", Country.Germany); 191 | 192 | let features = new List(); 193 | 194 | let feature = new Feature(1, "2 - Door Sedan"); 195 | 196 | features.add(feature); 197 | 198 | dictionary.add(car, features); 199 | 200 | features = new List(); 201 | 202 | feature = new Feature(2, "4 - Door Sedan"); 203 | 204 | features.add(feature); 205 | 206 | dictionary.add(car2, features); 207 | 208 | let first = dictionary.firstOrDefault(x => x.key.name == "Mercedez"); 209 | 210 | expect(first.key.id = 1); 211 | }); 212 | 213 | it('singleOrDefault fail', () => { 214 | let dictionary = new SortedDictionary>(new CarComparer()); 215 | 216 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 217 | let car2 = new Car(2, "Mercedez", "S 500", Country.Germany); 218 | 219 | let features = new List(); 220 | 221 | let feature = new Feature(1, "2 - Door Sedan"); 222 | 223 | features.add(feature); 224 | 225 | dictionary.add(car, features); 226 | 227 | features = new List(); 228 | 229 | feature = new Feature(2, "4 - Door Sedan"); 230 | 231 | features.add(feature); 232 | 233 | dictionary.add(car2, features); 234 | 235 | try { 236 | let single = dictionary.singleOrDefault(x => x.key.name == "Mercedez"); 237 | } 238 | catch (e) { 239 | expect(e == "Multiple instances of entity found."); 240 | } 241 | }); 242 | 243 | it('join', () => { 244 | 245 | }); 246 | 247 | it('groupBy', () => { 248 | let dictionary = new SortedDictionary>(new CarComparer()); 249 | 250 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 251 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 252 | 253 | let features = new List(); 254 | 255 | let feature = new Feature(1, "2 - Door Sedan"); 256 | 257 | features.add(feature); 258 | 259 | dictionary.add(car, features); 260 | 261 | features = new List(); 262 | 263 | feature = new Feature(2, "4 - Door Sedan"); 264 | 265 | features.add(feature); 266 | 267 | dictionary.add(car2, features); 268 | 269 | let result = dictionary.groupBy(x => [x.key.country]); 270 | 271 | expect(result.toArray()[0].groups[0] == Country.Germany); 272 | expect(result.toArray()[1].groups[0] == Country.England); 273 | }); 274 | 275 | it('groupBy multiple fields', () => { 276 | let dictionary = new SortedDictionary>(new CarComparer()); 277 | 278 | let car = new Car(1, "Mercedez", "S 400", Country.Germany, true); 279 | let car2 = new Car(2, "Jaguar", "J 500", Country.England, true); 280 | let car3 = new Car(3, "Ford", "F 500", Country.US); 281 | 282 | let features = new List(); 283 | 284 | let feature = new Feature(1, "2 - Door"); 285 | 286 | features.add(feature); 287 | 288 | feature = new Feature(1, "Sedan"); 289 | 290 | features.add(feature); 291 | 292 | dictionary.add(car, features); 293 | 294 | features = new List(); 295 | 296 | feature = new Feature(1, "4 - Door"); 297 | 298 | features.add(feature); 299 | 300 | feature = new Feature(1, "Sedan"); 301 | 302 | features.add(feature); 303 | 304 | dictionary.add(car2, features); 305 | 306 | features = new List(); 307 | 308 | feature = new Feature(1, "4 - Door"); 309 | 310 | features.add(feature); 311 | 312 | feature = new Feature(1, "Hatchback"); 313 | 314 | features.add(feature); 315 | 316 | dictionary.add(car3, features); 317 | 318 | let result = dictionary.groupBy(x => [x.key.isLuxury, x.key.country]); 319 | 320 | expect(result.toArray().length == 2); 321 | 322 | expect(result.toArray()[0].groups[0] == false); 323 | expect(result.toArray()[0].groups[1] == Country.US); 324 | expect(result.toArray()[0].list.toArray()[0].key.id == 3); 325 | 326 | expect(result.toArray()[1].groups[0] == true); 327 | expect(result.toArray()[1].groups[1] == Country.England); 328 | expect(result.toArray()[1].list.toArray()[0].key.id == 2); 329 | 330 | expect(result.toArray()[1].groups[0] == true); 331 | expect(result.toArray()[1].groups[1] == Country.Germany); 332 | expect(result.toArray()[1].list.toArray()[0].key.id == 1); 333 | }); 334 | 335 | it('orderBy', () => { 336 | let dictionary = new SortedDictionary>(new CarComparer()); 337 | 338 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 339 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 340 | 341 | let features = new List(); 342 | 343 | let feature = new Feature(1, "2 - Door Sedan"); 344 | 345 | features.add(feature); 346 | 347 | dictionary.add(car, features); 348 | 349 | features = new List(); 350 | 351 | feature = new Feature(2, "4 - Door Sedan"); 352 | 353 | features.add(feature); 354 | 355 | dictionary.add(car2, features); 356 | 357 | let result = dictionary.orderBy(new ComparerByCarName()); 358 | 359 | expect(result.toArray()[0].key.name == "Jaguar"); 360 | expect(result.toArray()[1].key.name == "Mercedez"); 361 | }); 362 | 363 | it('union', () => { 364 | let dictionary = new SortedDictionary>(new CarComparer()); 365 | 366 | let car = new Car(1, "Mercedez", "S 400", Country.Germany); 367 | let car2 = new Car(2, "Jaguar", "J 500", Country.England); 368 | 369 | let features = new List(); 370 | 371 | let feature = new Feature(1, "2 - Door Sedan"); 372 | 373 | features.add(feature); 374 | 375 | dictionary.add(car, features); 376 | 377 | features = new List(); 378 | 379 | feature = new Feature(2, "4 - Door Sedan"); 380 | 381 | features.add(feature); 382 | 383 | dictionary.add(car2, features); 384 | 385 | 386 | let dictionary2 = new SortedDictionary>(new CarComparer()); 387 | 388 | car = new Car(1, "Volvo", "V 400", Country.Germany); 389 | car2 = new Car(2, "Ford", "F 500", Country.US); 390 | 391 | features = new List(); 392 | 393 | feature = new Feature(1, "2 - Door Sedan"); 394 | 395 | features.add(feature); 396 | 397 | dictionary2.add(car, features); 398 | 399 | features = new List(); 400 | 401 | feature = new Feature(2, "4 - Door Sedan"); 402 | 403 | features.add(feature); 404 | 405 | dictionary2.add(car2, features); 406 | 407 | let result = dictionary.union(dictionary2); 408 | 409 | expect(result.length == 4); 410 | }); 411 | }); 412 | 413 | enum Country { 414 | US, 415 | Germany, 416 | England 417 | } 418 | 419 | class Car { 420 | id: number; 421 | name: string; 422 | model: string; 423 | country: Country; 424 | isLuxury: boolean; 425 | 426 | constructor(id: number, name: string, model: string, country: Country, isLuxury: boolean = false) { 427 | this.id = id; 428 | this.name = name; 429 | this.model = model; 430 | this.country = country; 431 | this.isLuxury = isLuxury; 432 | } 433 | } 434 | 435 | class Feature { 436 | name: string; 437 | carId: number; 438 | 439 | constructor(carId: number, name: string) { 440 | this.carId = carId; 441 | this.name = name; 442 | } 443 | } 444 | 445 | class ComparerByCarName implements IComparer>> { 446 | compare(x: KeyValuePair>, y: KeyValuePair>) : number { 447 | if (x.key.name > y.key.name) { 448 | return 1; 449 | } 450 | 451 | return -1; 452 | } 453 | } 454 | 455 | class CarComparer implements IComparer { 456 | compare(x: Car, y: Car) : number { 457 | if (x.name > y.name) { 458 | return 1; 459 | } 460 | 461 | if (x.name == y.name) { 462 | return 0; 463 | } 464 | 465 | return -1; 466 | } 467 | } -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/sorted.dictionary.ts: -------------------------------------------------------------------------------- 1 | import { IEnumerable, IComparer, IEqualityComparer } from './interfaces'; 2 | import { List } from './list'; 3 | import { Group, objCompare, ITEM_NOT_FOUND_MSG, MULTIPLE_INSTANCES_FOUND_MSG } from './common'; 4 | import { IDictionary, Dictionary, KeyValuePair } from './dictionary'; 5 | 6 | class StringComparer implements IComparer { 7 | compare(x: TKey, y: TKey) : number { 8 | var xKey = x as unknown as string; 9 | var yKey = y as unknown as string; 10 | 11 | if (xKey > yKey) { 12 | return 1; 13 | } 14 | else if (xKey == yKey) { 15 | return 0; 16 | } 17 | else { 18 | return -1; 19 | } 20 | } 21 | } 22 | 23 | class NumberComparer implements IComparer { 24 | compare(x: TKey, y: TKey) : number { 25 | var xKey = x as unknown as number; 26 | var yKey = y as unknown as number; 27 | 28 | if (xKey > yKey) { 29 | return 1; 30 | } 31 | else if (xKey == yKey) { 32 | return 0; 33 | } 34 | else { 35 | return -1; 36 | } 37 | } 38 | } 39 | 40 | export class SortedDictionary implements IDictionary 41 | { 42 | private list: Array> = new Array>(); 43 | private comparer: IComparer; 44 | 45 | constructor(comparer: IComparer, list: Array> = null) { 46 | if (list) { 47 | this.list = list; 48 | } 49 | 50 | this.comparer = comparer; 51 | 52 | if (this.list && this.list.length > 0) 53 | { 54 | if (this.comparer) { 55 | this.list.sort((x,y) => this.comparer.compare(x.key, y.key)); 56 | } 57 | else { 58 | var value = this.list[0].key; 59 | 60 | if (typeof value === "string") { 61 | this.comparer = new StringComparer(); 62 | 63 | this.list = this.list.sort((x,y) => this.comparer.compare(x.key, y.key)); 64 | } 65 | else if (typeof value === "number") { 66 | this.comparer = new NumberComparer(); 67 | 68 | this.list = this.list.sort((x,y) => this.comparer.compare(x.key, y.key)); 69 | } 70 | } 71 | } 72 | } 73 | 74 | /* IList */ 75 | 76 | add(key: TKey, value: TValue) : void { 77 | let pair = new KeyValuePair(key, value); 78 | 79 | if (this.containsKey(key)) { 80 | throw "Duplicate key. Cannot add." 81 | } 82 | 83 | this.list.push(pair); 84 | 85 | if (this.comparer) { 86 | this.list = this.list.sort((x,y) => this.comparer.compare(x.key, y.key)); 87 | } 88 | } 89 | 90 | addRange(items: KeyValuePair[]) : void { 91 | items.forEach(x => this.add(x.key, x.value)); 92 | } 93 | 94 | removeAt(index: number) : void { 95 | this.list.splice(index, 1); 96 | } 97 | 98 | clear() : void { 99 | this.list = new Array>(); 100 | } 101 | 102 | remove(predicate: (item:KeyValuePair) => boolean) : void { 103 | let temp = new Array>(); 104 | 105 | this.list.forEach(element => { 106 | if (!predicate(element)) 107 | { 108 | temp.push(element); 109 | } 110 | }); 111 | 112 | this.list = temp; 113 | } 114 | 115 | containsKey(key: TKey) : boolean { 116 | return this.any(x => objCompare(x.key, key)); 117 | } 118 | 119 | containsValue(value: TValue) : boolean { 120 | return this.any(x => objCompare(x.value, value)); 121 | } 122 | 123 | tryGetValue(key: TKey) : TValue { 124 | let item = this.singleOrDefault(x => objCompare(x.key, key)); 125 | 126 | if (item) { 127 | return item.value; 128 | } 129 | 130 | return null; 131 | } 132 | 133 | /* IEnumerable */ 134 | 135 | asEnumerable() : IEnumerable> { 136 | return this; 137 | } 138 | 139 | get length(): number { 140 | return this.list.length; 141 | } 142 | 143 | elementAt(index: number) : KeyValuePair { 144 | try { 145 | return this.list[index]; 146 | } 147 | catch (e) { 148 | return null; 149 | } 150 | } 151 | 152 | any(predicate?: (item: KeyValuePair)=> boolean) : boolean { 153 | if (!predicate) { 154 | return this.list.length > 0; 155 | } 156 | 157 | for (let i=0; i)=> boolean) : boolean { 167 | if (!predicate) { 168 | return this.list.length > 0; 169 | } 170 | 171 | for (let i=0; i)=> boolean = null) : KeyValuePair { 181 | if (this.list.length <= 0) { 182 | throw ITEM_NOT_FOUND_MSG; 183 | } 184 | 185 | if (predicate) { 186 | let item = this.singleOrDefault(predicate); 187 | 188 | if (!item) { 189 | throw ITEM_NOT_FOUND_MSG; 190 | } 191 | 192 | return item; 193 | } 194 | 195 | return this.list[0]; 196 | } 197 | 198 | first(predicate: (item: KeyValuePair)=> boolean = null) : KeyValuePair { 199 | if (this.list.length <= 0) { 200 | throw ITEM_NOT_FOUND_MSG; 201 | } 202 | 203 | if (predicate) { 204 | let item = this.firstOrDefault(predicate); 205 | 206 | if (!item) { 207 | throw ITEM_NOT_FOUND_MSG; 208 | } 209 | 210 | return item; 211 | } 212 | 213 | return this.list[0]; 214 | } 215 | 216 | last(predicate: (item: KeyValuePair)=> boolean) : KeyValuePair { 217 | if (this.list.length <= 0) { 218 | throw ITEM_NOT_FOUND_MSG; 219 | } 220 | 221 | if (predicate) { 222 | let item = this.lastOrDefault(predicate); 223 | 224 | if (!item) { 225 | throw ITEM_NOT_FOUND_MSG; 226 | } 227 | 228 | return item; 229 | } 230 | 231 | return this.list[this.list.length - 1]; 232 | } 233 | 234 | singleOrDefault(predicate: (item: KeyValuePair)=> boolean) : KeyValuePair { 235 | let temp = new Array>(); 236 | 237 | this.list.filter(element => { 238 | if (predicate(element)) 239 | { 240 | temp.push(element); 241 | } 242 | }); 243 | 244 | if (temp.length > 1) { 245 | throw MULTIPLE_INSTANCES_FOUND_MSG; 246 | } 247 | 248 | if (temp.length <= 0) { 249 | return null; 250 | } 251 | 252 | return temp[0]; 253 | } 254 | 255 | firstOrDefault(predicate: (item: KeyValuePair)=> boolean) : KeyValuePair { 256 | for (let i=0; i)=> boolean) : KeyValuePair { 268 | for (let i=this.length; i>=0; i--) { 269 | let item = this.list[i - 1]; 270 | if (predicate(item)) 271 | { 272 | return item; 273 | } 274 | } 275 | 276 | return null; 277 | } 278 | 279 | where(predicate: (item: KeyValuePair)=> boolean) : IDictionary { 280 | let temp = new Dictionary(); 281 | 282 | this.list.filter(element => { 283 | if (predicate(element)) 284 | { 285 | temp.add(element.key, element.value); 286 | } 287 | }); 288 | 289 | return temp; 290 | } 291 | 292 | select(predicate: (item: KeyValuePair)=> TResult) : IEnumerable { 293 | let temp = new List(); 294 | 295 | this.forEach(x => temp.add(predicate(x))); 296 | 297 | return temp; 298 | } 299 | 300 | forEach(predicate: (item: KeyValuePair)=> void) : void { 301 | this.list.forEach(x => predicate(x)); 302 | } 303 | 304 | toArray() : Array> { 305 | return this.list.slice(); 306 | } 307 | 308 | join(outer: IEnumerable, conditionInner: (item: KeyValuePair)=> TMatch, 309 | conditionOuter: (item: TOuter)=> TMatch, select: (x: KeyValuePair, y:TOuter)=> TResult, leftJoin: boolean = false) : IEnumerable { 310 | let resultList = new List(); 311 | 312 | this.list.forEach(x => { 313 | let outerEntries = outer.toArray().filter(y => conditionInner(x) === conditionOuter(y)); 314 | 315 | if (leftJoin && outerEntries && outerEntries.length <= 0) { 316 | resultList.add(select(x, null)); 317 | } 318 | else { 319 | outerEntries.forEach(z => resultList.add(select(x, z))); 320 | } 321 | }) 322 | 323 | return resultList; 324 | } 325 | 326 | groupBy(predicate: (item: KeyValuePair) => Array) : IEnumerable>> { 327 | let groups = {}; 328 | this.list.forEach(function (o) { 329 | var group = JSON.stringify(predicate(o)); 330 | groups[group] = groups[group] || []; 331 | groups[group].push(o); 332 | }); 333 | let g = Object.keys(groups).map(function (group) { 334 | let a = group.substr(1, group.length - 2); 335 | 336 | let grp= new Group>(new List(a.split(',')).select(x => x.replace(/^(")?(.*?)(")?$/ig, "$2")).toArray(), 337 | groups[group]); 338 | 339 | return grp; 340 | }); 341 | 342 | return new List>>(g); 343 | } 344 | 345 | selectMany(predicate: (item: KeyValuePair)=> Array) : IEnumerable { 346 | return this.list.reduce((out, inx) => { 347 | var items = predicate(inx); 348 | out.addRange(items); 349 | return out; 350 | }, new List(new Array())); 351 | } 352 | 353 | orderBy(comparer: IComparer>) : IEnumerable> { 354 | let temp = this.list.sort((x,y) => comparer.compare(x, y)); 355 | 356 | return new List>(temp); 357 | } 358 | 359 | distinct(comparer: IEqualityComparer>) : IEnumerable> { 360 | let uniques = new List>(); 361 | this.forEach(x => { 362 | if (uniques.length > 0) { 363 | if (!uniques.any(y => comparer.equals(x, y))) 364 | { 365 | uniques.add(x); 366 | } 367 | } 368 | else { 369 | uniques.add(x); 370 | } 371 | }); 372 | 373 | return uniques; 374 | } 375 | 376 | union(list: IEnumerable>) : IDictionary { 377 | this.addRange(list.toArray()); 378 | 379 | return this; 380 | } 381 | 382 | reverse(): IEnumerable> { 383 | return new List>(this.list.slice().reverse()); 384 | } 385 | 386 | skip(no: number) : IDictionary { 387 | if (no > 0) { 388 | return new Dictionary(this.list.slice(no, this.list.length - 1)); 389 | } 390 | 391 | return this; 392 | } 393 | 394 | take(no: number) : IDictionary { 395 | if (no > 0) { 396 | return new Dictionary(this.list.slice(0, no)); 397 | } 398 | 399 | return this; 400 | } 401 | 402 | sum(predicate: (item: KeyValuePair)=> number) : number { 403 | let sum: number = 0; 404 | this.list.forEach(x => sum = sum + predicate(x)); 405 | 406 | return sum; 407 | } 408 | 409 | avg(predicate: (item: KeyValuePair)=> number) : number { 410 | return this.sum(predicate) / this.length; 411 | } 412 | 413 | min(predicate: (item: KeyValuePair)=> number) : number { 414 | let min: number = 0; 415 | let i = 0; 416 | this.list.forEach(x => 417 | { 418 | if (i == 0) { 419 | min = predicate(x); 420 | } 421 | else { 422 | let val = predicate(x); 423 | if (val < min) { 424 | min = val; 425 | } 426 | } 427 | i++; 428 | }); 429 | 430 | return min; 431 | } 432 | 433 | max(predicate: (item: KeyValuePair)=> number) : number { 434 | let max: number = 0; 435 | let i = 0; 436 | this.list.forEach(x => 437 | { 438 | if (i == 0) { 439 | max = predicate(x); 440 | } 441 | else { 442 | let val = predicate(x); 443 | if (val > max) { 444 | max = val; 445 | } 446 | } 447 | i++; 448 | }); 449 | 450 | return max; 451 | } 452 | 453 | count(predicate: (item: KeyValuePair)=> boolean = null) : number { 454 | if (!predicate) { 455 | return this.length; 456 | } 457 | 458 | let count: number = 0; 459 | this.list.forEach(x => { 460 | if(predicate(x)) { 461 | count++; 462 | } 463 | }); 464 | 465 | return count; 466 | } 467 | } -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/stack.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { Stack } from './stack'; 4 | 5 | describe('Stack', () => { 6 | 7 | beforeEach(async(() => { 8 | 9 | })); 10 | 11 | beforeEach(() => { 12 | 13 | }); 14 | 15 | it('instantiate stack from json', () => { 16 | let jsonOwnerArray = '[{"id":1, "name": "John Doe"},{"id":2, "name": "Jane Doe"}]'; 17 | 18 | let ownerArray: Owner[] = JSON.parse(jsonOwnerArray); 19 | 20 | let stack = new Stack(ownerArray); 21 | 22 | expect(stack.toArray().length == 2); 23 | }); 24 | 25 | it('push', () => { 26 | let stack = new Stack(); 27 | 28 | let owner = new Owner(); 29 | owner.name = "John Doe"; 30 | 31 | //push 32 | stack.push(owner); 33 | 34 | expect(stack.toArray().length == 1).toBeTruthy(); 35 | 36 | let owner2 = new Owner(); 37 | owner2.name = "Jane Doe"; 38 | 39 | //push 40 | stack.push(owner2); 41 | 42 | expect(stack.toArray().length == 2).toBeTruthy(); 43 | }); 44 | 45 | it('pop', () => { 46 | let stack = new Stack(); 47 | 48 | let owner = new Owner(); 49 | owner.name = "John Doe"; 50 | 51 | //push 52 | stack.push(owner); 53 | 54 | let owner2 = new Owner(); 55 | owner2.name = "Jane Doe"; 56 | 57 | //push 58 | stack.push(owner2); 59 | 60 | expect(stack.toArray().length == 2).toBeTruthy(); 61 | 62 | //pop 63 | var popped = stack.pop(); 64 | 65 | expect(popped.name == "Jane Doe").toBeTruthy(); 66 | expect(stack.toArray().length == 1).toBeTruthy(); 67 | 68 | var popped2 = stack.pop(); 69 | 70 | expect(popped2.name == "John Doe").toBeTruthy(); 71 | expect(stack.toArray().length == 0).toBeTruthy(); 72 | }); 73 | 74 | it('peek', () => { 75 | let stack = new Stack(); 76 | 77 | let owner = new Owner(); 78 | owner.name = "John Doe"; 79 | 80 | //push 81 | stack.push(owner); 82 | 83 | let owner2 = new Owner(); 84 | owner2.name = "Jane Doe"; 85 | 86 | //push 87 | stack.push(owner2); 88 | 89 | expect(stack.toArray().length == 2).toBeTruthy(); 90 | 91 | //peek 92 | var peeked = stack.peek(); 93 | 94 | expect(peeked.name == "Jane Doe").toBeTruthy(); 95 | expect(stack.toArray().length == 2).toBeTruthy(); 96 | }); 97 | 98 | it('forEach', () => { 99 | let stack = new Stack(); 100 | 101 | let owner = new Owner(); 102 | owner.name = "John Doe"; 103 | 104 | //push 105 | stack.push(owner); 106 | 107 | let owner2 = new Owner(); 108 | owner2.name = "Jane Doe"; 109 | 110 | //push 111 | stack.push(owner2); 112 | 113 | expect(stack.toArray().length == 2).toBeTruthy(); 114 | 115 | let i: number = 0; 116 | //forEach 117 | stack.forEach(item => { 118 | i++; 119 | }); 120 | 121 | expect(stack.toArray().length == i).toBeTruthy(); 122 | }); 123 | 124 | }); 125 | 126 | class Owner { 127 | id: number; 128 | name: string; 129 | } 130 | -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/lib/stack.ts: -------------------------------------------------------------------------------- 1 | import { objCompare } from './common'; 2 | import { List } from './list'; 3 | 4 | export interface IStack { 5 | clear() : void; 6 | contains(item: T) : boolean; 7 | pop() : T; 8 | push(item: T) : void; 9 | peek(): T; 10 | forEach(predicate: (item: T)=> void) : void; 11 | toArray(): Array; 12 | } 13 | 14 | export class Stack implements IStack { 15 | private list: List = new List(); 16 | 17 | constructor(array: Array = null) { 18 | if (array) 19 | this.list = new List(array); 20 | } 21 | 22 | clear(): void { 23 | this.list.clear(); 24 | } 25 | contains(item: T): boolean { 26 | return this.list.any(x => objCompare(x, item)); 27 | } 28 | pop(): T { 29 | if (this.list.length > 0) { 30 | 31 | var element = this.list.elementAt(this.list.length - 1); 32 | 33 | this.list.removeAt(this.list.length - 1); 34 | 35 | return element; 36 | } 37 | 38 | return null; 39 | } 40 | push(item: T): void { 41 | this.list.add(item); 42 | } 43 | peek(): T { 44 | if (this.list.length > 0) { 45 | var element = this.list.elementAt(this.list.length - 1); 46 | 47 | return element; 48 | } 49 | 50 | return null; 51 | } 52 | forEach(predicate: (item: T) => void): void { 53 | this.list.reverse().forEach(predicate); 54 | } 55 | toArray(): T[] { 56 | var tmp = new List(this.list.toArray()); 57 | return tmp.reverse().toArray(); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of ts-generic-collections 3 | */ 4 | 5 | export * from './lib/interfaces'; 6 | export * from './lib/common'; 7 | export * from './lib/list'; 8 | export * from './lib/dictionary'; 9 | export * from './lib/sorted.dictionary'; 10 | export * from './lib/queue'; 11 | export * from './lib/randomized.queue'; 12 | export * from './lib/stack'; 13 | -------------------------------------------------------------------------------- /projects/ts-generic-collections/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/es7/reflect'; 4 | import 'zone.js/dist/zone'; 5 | import 'zone.js/dist/zone-testing'; 6 | import { getTestBed } from '@angular/core/testing'; 7 | import { 8 | BrowserDynamicTestingModule, 9 | platformBrowserDynamicTesting 10 | } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | declare const require: any; 13 | 14 | // First, initialize the Angular testing environment. 15 | getTestBed().initTestEnvironment( 16 | BrowserDynamicTestingModule, 17 | platformBrowserDynamicTesting() 18 | ); 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | // And load the modules. 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /projects/ts-generic-collections/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "module": "es2015", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "types": [], 15 | "lib": [ 16 | "dom", 17 | "es2018" 18 | ] 19 | }, 20 | "angularCompilerOptions": { 21 | "annotateForClosureCompiler": true, 22 | "skipTemplateCodegen": true, 23 | "strictMetadataEmit": true, 24 | "fullTemplateTypeCheck": true, 25 | "strictInjectionParameters": true, 26 | "enableResourceInlining": true 27 | }, 28 | "exclude": [ 29 | "src/test.ts", 30 | "**/*.spec.ts" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /projects/ts-generic-collections/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /projects/ts-generic-collections/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "ts", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "ts", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ], 21 | "paths": { 22 | "ts-generic-collections": [ 23 | "dist/ts-generic-collections" 24 | ], 25 | "ts-generic-collections/*": [ 26 | "dist/ts-generic-collections/*" 27 | ] 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-redundant-jsdoc": true, 69 | "no-shadowed-variable": true, 70 | "no-string-literal": false, 71 | "no-string-throw": true, 72 | "no-switch-case-fall-through": true, 73 | "no-trailing-whitespace": true, 74 | "no-unnecessary-initializer": true, 75 | "no-unused-expression": true, 76 | "no-use-before-declare": true, 77 | "no-var-keyword": true, 78 | "object-literal-sort-keys": false, 79 | "one-line": [ 80 | true, 81 | "check-open-brace", 82 | "check-catch", 83 | "check-else", 84 | "check-whitespace" 85 | ], 86 | "prefer-const": true, 87 | "quotemark": [ 88 | true, 89 | "single" 90 | ], 91 | "radix": true, 92 | "semicolon": [ 93 | true, 94 | "always" 95 | ], 96 | "triple-equals": [ 97 | true, 98 | "allow-null-check" 99 | ], 100 | "typedef-whitespace": [ 101 | true, 102 | { 103 | "call-signature": "nospace", 104 | "index-signature": "nospace", 105 | "parameter": "nospace", 106 | "property-declaration": "nospace", 107 | "variable-declaration": "nospace" 108 | } 109 | ], 110 | "unified-signatures": true, 111 | "variable-name": false, 112 | "whitespace": [ 113 | true, 114 | "check-branch", 115 | "check-decl", 116 | "check-operator", 117 | "check-separator", 118 | "check-type" 119 | ], 120 | "no-output-on-prefix": true, 121 | "use-input-property-decorator": true, 122 | "use-output-property-decorator": true, 123 | "use-host-property-decorator": true, 124 | "no-input-rename": true, 125 | "no-output-rename": true, 126 | "use-life-cycle-interface": true, 127 | "use-pipe-transform-interface": true, 128 | "component-class-suffix": true, 129 | "directive-class-suffix": true 130 | } 131 | } 132 | --------------------------------------------------------------------------------