From 5ee5ed410b816b3513c724bd7275070114495252 Mon Sep 17 00:00:00 2001 From: Mo Abbas Date: Tue, 16 May 2023 07:23:24 +1000 Subject: [PATCH] Make List iterabal and spreadable using generator --- __tests__/list.test.ts | 37 ++++++++++- src/list.ts | 143 ++++++++++++++--------------------------- tsconfig.json | 3 +- 3 files changed, 87 insertions(+), 96 deletions(-) diff --git a/__tests__/list.test.ts b/__tests__/list.test.ts index 62e1f5f..2e6107a 100644 --- a/__tests__/list.test.ts +++ b/__tests__/list.test.ts @@ -81,6 +81,41 @@ class Product implements IProduct { } } +test('Iterator', t => { + const pets = new List([ + new Pet({ Age: 10, Name: 'Barley' }), + new Pet({ Age: 4, Name: 'Boots' }), + new Pet({ Age: 6, Name: 'Bissy' }) + ]) + + for (const pet of pets) { + // determine whether all pet names + // in the array start with 'B'. + t.true(pet.Name.startsWith('B')) + } +}) + +test('Spread', t => { + const pets = new List([ + new Pet({ Age: 10, Name: 'Barley' }), + new Pet({ Age: 4, Name: 'Boots' }), + new Pet({ Age: 6, Name: 'Bissy' }) + ]) + const petsCopy = [...pets]; + for (const pet of petsCopy) { + // determine whether all pet names + // in the array start with 'B'. + t.true(pet.Name.startsWith('B')) + } +}) + +test('toStringTag', t => { + const pets = new List([]) + t.true(pets.toString() === '[object List]') + t.true(`${pets}` === '[object List]') +}) + + test('Add', t => { const list = new List() list.Add('hey') @@ -885,7 +920,7 @@ test('ToDictionary', t => { { Age: 25, Name: 'Alice' }, { Age: 50, Name: 'Bob' } ]) - const dictionary = people.ToDictionary(x => x.Name) + const dictionary = people.ToDictionary(x => x.Name) t.deepEqual(dictionary['Bob'], { Age: 50, Name: 'Bob' }) t.is(dictionary['Bob'].Age, 50) const dictionary2 = people.ToDictionary(x => x.Name, y => y.Age) diff --git a/src/list.ts b/src/list.ts index f1fd723..8356acc 100644 --- a/src/list.ts +++ b/src/list.ts @@ -3,13 +3,30 @@ import { composeComparers, negate, isObj, equal, keyComparer } from './helpers' type PredicateType = (value?: T, index?: number, list?: T[]) => boolean class List { + protected _elements: T[] + /** + * Make the List iterable and Spreadable + */ + *[Symbol.iterator]() { + for (let element of this._elements) { + yield element; + } + } + + /** + * property represents the Object name + */ + get [Symbol.toStringTag]() { + return 'List'; // Expected output: "[object List]" + } + /** * Defaults the elements of the list */ constructor(elements: T[] = []) { - this._elements = elements + this._elements = [...elements]; } /** @@ -43,10 +60,7 @@ class List { /** * Applies an accumulator function over a sequence. */ - public Aggregate( - accumulator: (accum: U, value?: T, index?: number, list?: T[]) => any, - initialValue?: U - ): any { + public Aggregate(accumulator: (accum: U, value?: T, index?: number, list?: T[]) => any, initialValue?: U): any { return this._elements.reduce(accumulator, initialValue) } @@ -60,8 +74,6 @@ class List { /** * Determines whether a sequence contains any elements. */ - public Any(): boolean - public Any(predicate: PredicateType): boolean public Any(predicate?: PredicateType): boolean { return predicate ? this._elements.some(predicate) @@ -72,13 +84,7 @@ class List { * Computes the average of a sequence of number values that are obtained by invoking * a transform function on each element of the input sequence. */ - public Average(): number - public Average( - transform: (value?: T, index?: number, list?: T[]) => any - ): number - public Average( - transform?: (value?: T, index?: number, list?: T[]) => any - ): number { + public Average(transform?: (value?: T, index?: number, list?: T[]) => any): number { return this.Sum(transform) / this.Count(transform) } @@ -113,10 +119,10 @@ class List { /** * Returns the number of elements in a sequence. */ - public Count(): number - public Count(predicate: PredicateType): number public Count(predicate?: PredicateType): number { - return predicate ? this.Where(predicate).Count() : this._elements.length + return predicate + ? this.Where(predicate).Count() + : this._elements.length } /** @@ -124,7 +130,9 @@ class List { * in a singleton collection if the sequence is empty. */ public DefaultIfEmpty(defaultValue?: T): List { - return this.Count() ? this : new List([defaultValue]) + return this.Count() + ? this + : new List([defaultValue]) } /** @@ -156,11 +164,8 @@ class List { public ElementAt(index: number): T { if (index < this.Count() && index >= 0) { return this._elements[index] - } else { - throw new Error( - 'ArgumentOutOfRangeException: index is less than 0 or greater than or equal to the number of elements in source.' - ) } + throw new Error('ArgumentOutOfRangeException: index is less than 0 or greater than or equal to the number of elements in source.') } /** @@ -182,23 +187,16 @@ class List { /** * Returns the first element of a sequence. */ - public First(): T - public First(predicate: PredicateType): T public First(predicate?: PredicateType): T { if (this.Count()) { return predicate ? this.Where(predicate).First() : this._elements[0] - } else { - throw new Error( - 'InvalidOperationException: The source sequence is empty.' - ) } + throw new Error('InvalidOperationException: The source sequence is empty.') } /** * Returns the first element of a sequence, or a default value if the sequence contains no elements. */ - public FirstOrDefault(): T - public FirstOrDefault(predicate: PredicateType): T public FirstOrDefault(predicate?: PredicateType): T { return this.Count(predicate) ? this.First(predicate) : undefined } @@ -281,31 +279,24 @@ class List { key2: (key: U) => any, result: (first: T, second: U) => R ): List { - return this.SelectMany(x => - list.Where(y => key2(y) === key1(x)).Select(z => result(x, z)) - ) + return this.SelectMany(x => list.Where(y => key2(y) === key1(x)).Select(z => result(x, z))) } /** * Returns the last element of a sequence. */ - public Last(): T - public Last(predicate: PredicateType): T public Last(predicate?: PredicateType): T { if (this.Count()) { return predicate ? this.Where(predicate).Last() : this._elements[this.Count() - 1] - } else { - throw Error('InvalidOperationException: The source sequence is empty.') } + throw Error('InvalidOperationException: The source sequence is empty.') } /** * Returns the last element of a sequence, or a default value if the sequence contains no elements. */ - public LastOrDefault(): T - public LastOrDefault(predicate: PredicateType): T public LastOrDefault(predicate?: PredicateType): T { return this.Count(predicate) ? this.Last(predicate) : undefined } @@ -313,11 +304,7 @@ class List { /** * Returns the maximum value in a generic sequence. */ - public Max(): number - public Max(selector: (value: T, index: number, array: T[]) => number): number - public Max( - selector?: (value: T, index: number, array: T[]) => number - ): number { + public Max(selector?: (value: T, index: number, array: T[]) => number): number { const id = x => x return Math.max(...this._elements.map(selector || id)) } @@ -325,11 +312,7 @@ class List { /** * Returns the minimum value in a generic sequence. */ - public Min(): number - public Min(selector: (value: T, index: number, array: T[]) => number): number - public Min( - selector?: (value: T, index: number, array: T[]) => number - ): number { + public Min(selector?: (value: T, index: number, array: T[]) => number): number { const id = x => x return Math.min(...this._elements.map(selector || id)) } @@ -338,7 +321,7 @@ class List { * Filters the elements of a sequence based on a specified type. */ public OfType(type: any): List { - let typeName + let typeName: string switch (type) { case Number: typeName = typeof 0 @@ -350,7 +333,7 @@ class List { typeName = typeof true break case Function: - typeName = typeof function() {} // tslint:disable-line no-empty + typeName = typeof function () { } // tslint:disable-line no-empty break default: typeName = null @@ -364,10 +347,7 @@ class List { /** * Sorts the elements of a sequence in ascending order according to a key. */ - public OrderBy( - keySelector: (key: T) => any, - comparer = keyComparer(keySelector, false) - ): List { + public OrderBy(keySelector: (key: T) => any, comparer = keyComparer(keySelector, false)): List { // tslint:disable-next-line: no-use-before-declare return new OrderedList(this._elements, comparer) } @@ -375,10 +355,7 @@ class List { /** * Sorts the elements of a sequence in descending order according to a key. */ - public OrderByDescending( - keySelector: (key: T) => any, - comparer = keyComparer(keySelector, true) - ): List { + public OrderByDescending(keySelector: (key: T) => any, comparer = keyComparer(keySelector, true)): List { // tslint:disable-next-line: no-use-before-declare return new OrderedList(this._elements, comparer) } @@ -430,18 +407,14 @@ class List { /** * Projects each element of a sequence into a new form. */ - public Select( - selector: (element: T, index: number) => TOut - ): List { + public Select(selector: (element: T, index: number) => TOut): List { return new List(this._elements.map(selector)) } /** * Projects each element of a sequence to a List and flattens the resulting sequences into one sequence. */ - public SelectMany>( - selector: (element: T, index: number) => TOut - ): TOut { + public SelectMany>(selector: (element: T, index: number) => TOut): TOut { return this.Aggregate( (ac, _, i) => ( ac.AddRange( @@ -468,9 +441,8 @@ class List { public Single(predicate?: PredicateType): T { if (this.Count(predicate) !== 1) { throw new Error('The collection does not contain exactly one element.') - } else { - return this.First(predicate) } + return this.First(predicate) } /** @@ -478,7 +450,9 @@ class List { * this method throws an exception if there is more than one element in the sequence. */ public SingleOrDefault(predicate?: PredicateType): T { - return this.Count(predicate) ? this.Single(predicate) : undefined + return this.Count(predicate) + ? this.Single(predicate) + : undefined } /** @@ -508,13 +482,7 @@ class List { * Computes the sum of the sequence of number values that are obtained by invoking * a transform function on each element of the input sequence. */ - public Sum(): number - public Sum( - transform: (value?: T, index?: number, list?: T[]) => number - ): number - public Sum( - transform?: (value?: T, index?: number, list?: T[]) => number - ): number { + public Sum(transform?: (value?: T, index?: number, list?: T[]) => number): number { return transform ? this.Select(transform).Sum() : this.Aggregate((ac, v) => (ac += +v), 0) @@ -553,23 +521,14 @@ class List { /** * Creates a Dictionary from a List according to a specified key selector function. */ - public ToDictionary( - key: (key: T) => TKey - ): List<{ Key: TKey; Value: T }> - public ToDictionary( - key: (key: T) => TKey, - value: (value: T) => TValue - ): List<{ Key: TKey; Value: T | TValue }> - public ToDictionary( - key: (key: T) => TKey, - value?: (value: T) => TValue - ): List<{ Key: TKey; Value: T | TValue }> { + public ToDictionary(key: (key: T) => TKey, value?: (value: T) => TValue): List<{ Key: TKey; Value: T | TValue }> { return this.Aggregate((dicc, v, i) => { dicc[ this.Select(key) .ElementAt(i) .toString() ] = value ? this.Select(value).ElementAt(i) : v + dicc.Add({ Key: this.Select(key).ElementAt(i), Value: value ? this.Select(value).ElementAt(i) : v @@ -588,10 +547,7 @@ class List { /** * Creates a Lookup from an IEnumerable according to specified key selector and element selector functions. */ - public ToLookup( - keySelector: (key: T) => string | number, - elementSelector: (element: T) => TResult - ): { [key: string]: TResult[] } { + public ToLookup(keySelector: (key: T) => string | number, elementSelector: (element: T) => TResult): { [key: string]: TResult[] } { return this.GroupBy(keySelector, elementSelector) } @@ -612,14 +568,13 @@ class List { /** * Applies a specified function to the corresponding elements of two sequences, producing a sequence of the results. */ - public Zip( - list: List, - result: (first: T, second: U) => TOut - ): List { + public Zip(list: List, result: (first: T, second: U) => TOut): List { return list.Count() < this.Count() ? list.Select((x, y) => result(this.ElementAt(y), x)) : this.Select((x, y) => result(x, list.ElementAt(y))) } + + private map } /** diff --git a/tsconfig.json b/tsconfig.json index bad8d07..aef809b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,8 @@ "outDir": "dist", "typeRoots": [ "node_modules/@types" - ] + ], + "downlevelIteration": true }, "include": [ "src",