From c2ec011f72bbb2d4d7ed9d70cc870395b2ce7d70 Mon Sep 17 00:00:00 2001 From: Daniel Bradley Date: Tue, 24 Jul 2018 16:28:41 +0100 Subject: [PATCH] feat(arrays): Add index to callbacks Add index argument to iterable callbacks. re #11 --- src/arrays.ts | 89 ++++++++++++++++++++++++++++----------------- test/arrays.test.ts | 55 ++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 33 deletions(-) diff --git a/src/arrays.ts b/src/arrays.ts index 546dc4f1..78d59552 100644 --- a/src/arrays.ts +++ b/src/arrays.ts @@ -10,16 +10,16 @@ export function ofIterable(source: Iterable): T[] { * Creates a new array whose elements are the results of applying the specified mapping to each of the elements of the source collection. * @param mapping A function to transform items from the input collection. */ -export function map(mapping: (item: T) => U): (source: T[]) => U[] +export function map(mapping: (item: T, index: number) => U): (source: T[]) => U[] /** * Creates a new array whose elements are the results of applying the specified mapping to each of the elements of the source collection. * @param source The input collection. * @param mapping A function to transform items from the input collection. */ -export function map(source: T[], mapping: (item: T) => U): U[] +export function map(source: T[], mapping: (item: T, index: number) => U): U[] export function map(a: any, b?: any): any { const partial = typeof a === 'function' - const mapping: (item: T) => U = partial ? a : b + const mapping: (item: T, index: number) => U = partial ? a : b function exec(source: T[]) { return source.map(mapping) } @@ -30,16 +30,16 @@ export function map(a: any, b?: any): any { * Returns a new array containing only the elements of the collection for which the given predicate returns true. * @param predicate A function to test whether each item in the input collection should be included in the output. */ -export function filter(predicate: (item: T) => boolean): (source: T[]) => T[] +export function filter(predicate: (item: T, index: number) => boolean): (source: T[]) => T[] /** * Returns a new array containing only the elements of the collection for which the given predicate returns true. * @param source The input collection. * @param predicate A function to test whether each item in the input collection should be included in the output. */ -export function filter(source: T[], predicate: (item: T) => boolean): T[] +export function filter(source: T[], predicate: (item: T, index: number) => boolean): T[] export function filter(a: any, b?: any): any { const partial = typeof a === 'function' - const predicate: (item: T) => boolean = partial ? a : b + const predicate: (item: T, index: number) => boolean = partial ? a : b function exec(source: T[]) { return source.filter(predicate) } @@ -50,23 +50,27 @@ export function filter(a: any, b?: any): any { * Applies the given function to each element of the array and returns a new array comprised of the results for each element where the function returns a value. * @param chooser A function to transform items from the input collection to a new value to be included, or undefined to be excluded. */ -export function choose(chooser: (item: T) => U | undefined): (source: T[]) => U[] +export function choose( + chooser: (item: T, index: number) => U | undefined +): (source: T[]) => U[] /** * Applies the given function to each element of the array and returns a new array comprised of the results for each element where the function returns a value. * @param source The input collection. * @param chooser A function to transform items from the input collection to a new value to be included, or undefined to be excluded. */ -export function choose(source: T[], chooser: (item: T) => U | undefined): U[] +export function choose(source: T[], chooser: (item: T, index: number) => U | undefined): U[] export function choose(a: any, b?: any): any { const partial = typeof a === 'function' - const chooser: (item: T) => U | undefined = partial ? a : b + const chooser: (item: T, index: number) => U | undefined = partial ? a : b function exec(source: T[]) { const target = [] + let index = 0 for (const item of source) { - const chosen = chooser(item) + const chosen = chooser(item, index) if (chosen !== undefined) { target.push(chosen) } + index++ } return target } @@ -77,23 +81,27 @@ export function choose(a: any, b?: any): any { * Applies the given function to each element of the source array and concatenates all the results. * @param mapping A function to transform elements of the input collection into collections that are concatenated. */ -export function collect(mapping: (item: T) => Iterable): (source: T[]) => U[] +export function collect( + mapping: (item: T, index: number) => Iterable +): (source: T[]) => U[] /** * Applies the given function to each element of the source array and concatenates all the results. * @param source The input collection. * @param mapping A function to transform elements of the input collection into collections that are concatenated. */ -export function collect(source: T[], mapping: (item: T) => Iterable): U[] +export function collect(source: T[], mapping: (item: T, index: number) => Iterable): U[] export function collect(a: any, b?: any): any { const partial = typeof a === 'function' - const mapping: (item: T) => Iterable = partial ? a : b + const mapping: (item: T, index: number) => Iterable = partial ? a : b function exec(source: T[]) { const target = [] + let index = 0 for (const item of source) { - const children = mapping(item) + const children = mapping(item, index) for (const child of children) { target.push(child) } + index++ } return target } @@ -142,7 +150,7 @@ export function concat(sources: Iterable): T[] { * @param selector A function that transforms the array items into comparable keys. * @param source The input collection. */ -export function distinctBy(selector: (item: T) => Key): (source: T[]) => T[] +export function distinctBy(selector: (item: T, index: number) => Key): (source: T[]) => T[] /** * Returns an array that contains no duplicate entries according to the equality comparisons on * the keys returned by the given key-generating function. If an element occurs multiple times in @@ -150,17 +158,19 @@ export function distinctBy(selector: (item: T) => Key): (source: T[]) => * @param source The input collection. * @param selector A function that transforms the array items into comparable keys. */ -export function distinctBy(source: T[], selector: (item: T) => Key): T[] +export function distinctBy(source: T[], selector: (item: T, index: number) => Key): T[] export function distinctBy(a: any, b?: any): any { const partial = typeof a === 'function' - const selector: (item: T) => Key = partial ? a : b + const selector: (item: T, index: number) => Key = partial ? a : b function exec(source: T[]): T[] { const seen = new Map() + let index = 0 for (const item of source) { - const key = selector(item) + const key = selector(item, index) if (!seen.has(key)) { seen.set(key, item) } + index++ } return Array.from(seen.values()) } @@ -172,16 +182,16 @@ export function distinctBy(a: any, b?: any): any { * @param predicate A function to test each item of the input collection. * @param source The input collection. */ -export function exists(predicate: (item: T) => boolean): (source: T[]) => boolean +export function exists(predicate: (item: T, index: number) => boolean): (source: T[]) => boolean /** * Tests if any element of the array satisfies the given predicate. * @param source The input collection. * @param predicate A function to test each item of the input collection. */ -export function exists(source: T[], predicate: (item: T) => boolean): boolean +export function exists(source: T[], predicate: (item: T, index: number) => boolean): boolean export function exists(a: any, b?: any): any { const partial = typeof a === 'function' - const predicate: (item: T) => boolean = partial ? a : b + const predicate: (item: T, index: number) => boolean = partial ? a : b function exec(source: T[]): boolean { return source.some(predicate) } @@ -194,22 +204,24 @@ export function exists(a: any, b?: any): any { * @param source The input collection. * @throws If no item is found matching the criteria of the predicate. */ -export function get(predicate: (item: T) => boolean): (source: T[]) => T +export function get(predicate: (item: T, index: number) => boolean): (source: T[]) => T /** * Returns the first element for which the given function returns true. * @param source The input collection. * @param predicate A function to test whether an item in the collection should be returned. * @throws If no item is found matching the criteria of the predicate. */ -export function get(source: T[], predicate: (item: T) => boolean): T +export function get(source: T[], predicate: (item: T, index: number) => boolean): T export function get(a: any, b?: any): any { const partial = typeof a === 'function' - const predicate: (item: T) => boolean = partial ? a : b + const predicate: (item: T, index: number) => boolean = partial ? a : b function exec(source: T[]): T | undefined { + let index = 0 for (const item of source) { - if (predicate(item)) { + if (predicate(item, index)) { return item } + index++ } throw new Error('Element not found matching criteria') } @@ -221,42 +233,53 @@ export function get(a: any, b?: any): any { * @param predicate A function to test whether an item in the collection should be returned. * @param source The input collection. */ -export function find(predicate: (item: T) => boolean): (source: T[]) => T | undefined +export function find( + predicate: (item: T, index: number) => boolean +): (source: T[]) => T | undefined /** * Returns the first element for which the given function returns true, otherwise undefined. * @param source The input collection. * @param predicate A function to test whether an item in the collection should be returned. */ -export function find(source: T[], predicate: (item: T) => boolean): T | undefined +export function find(source: T[], predicate: (item: T, index: number) => boolean): T | undefined export function find(a: any, b?: any): any { const partial = typeof a === 'function' - const predicate: (item: T) => boolean = partial ? a : b + const predicate: (item: T, index: number) => boolean = partial ? a : b function exec(source: T[]): T | undefined { + let index = 0 for (const item of source) { - if (predicate(item)) { + if (predicate(item, index)) { return item } + index++ } return undefined } return partial ? exec : exec(a) } -export function groupBy(selector: (item: T) => Key): (source: T[]) => [Key, T[]][] -export function groupBy(source: T[], selector: (item: T) => Key): [Key, T[]][] +export function groupBy( + selector: (item: T, index: number) => Key +): (source: T[]) => [Key, T[]][] +export function groupBy( + source: T[], + selector: (item: T, index: number) => Key +): [Key, T[]][] export function groupBy(a: any, b?: any): any { const partial = typeof a === 'function' - const selector: (item: T) => Key = partial ? a : b + const selector: (item: T, index: number) => Key = partial ? a : b function exec(source: T[]): [Key, T[]][] { const groups = new Map() + let index = 0 for (const item of source) { - const key = selector(item) + const key = selector(item, index) const group = groups.get(key) if (group === undefined) { groups.set(key, [item]) } else { group.push(item) } + index++ } return Array.from(groups.entries()) } diff --git a/test/arrays.test.ts b/test/arrays.test.ts index 220db069..01a316d6 100644 --- a/test/arrays.test.ts +++ b/test/arrays.test.ts @@ -20,6 +20,9 @@ describe('map', () => { test('invoke', () => { expect(Arrays.map([1, 2], x => x * 2)).toEqual([2, 4]) }) + test('with index', () => { + expect(Arrays.map([1, 2], (x, index) => x * index)).toEqual([0, 2]) + }) }) describe('filter', () => { @@ -32,6 +35,9 @@ describe('filter', () => { test('invoke', () => { expect(Arrays.filter([1, 2, 3, 4], x => x % 2 === 0)).toEqual([2, 4]) }) + test('with index', () => { + expect(Arrays.filter([1, 2, 3, 4], (x, index) => index % 2 === 0)).toEqual([1, 3]) + }) }) describe('choose', () => { @@ -43,6 +49,11 @@ describe('choose', () => { test('invoke', () => { expect(Arrays.choose([1, 2, 3], x => (x % 2 === 1 ? x * 2 : undefined))).toEqual([2, 6]) }) + test('with index', () => { + expect( + Arrays.choose([1, 2, 3], (x, index) => (index % 2 === 0 ? x * index : undefined)) + ).toEqual([0, 6]) + }) }) describe('collect', () => { @@ -62,6 +73,13 @@ describe('collect', () => { }) ).toEqual([1, 1, 2, 2]) }) + test('with index', () => { + expect( + Arrays.collect([1, 2], function(x, index) { + return [x, x + index] + }) + ).toEqual([1, 1, 2, 3]) + }) }) describe('append', () => { @@ -101,6 +119,19 @@ describe('distinctBy', () => { ) ).toEqual([{ name: 'amy', id: 1 }, { name: 'bob', id: 2 }, { name: 'cat', id: 3 }]) }) + test('with index', () => { + expect( + pipe( + [ + { name: 'amy', id: 1 }, + { name: 'bob', id: 2 }, + { name: 'bob', id: 3 }, + { name: 'cat', id: 3 } + ], + Arrays.distinctBy((x, index) => Math.floor(index / 2)) + ) + ).toEqual([{ name: 'amy', id: 1 }, { name: 'bob', id: 3 }]) + }) }) describe('exists', () => { @@ -113,6 +144,9 @@ describe('exists', () => { test('invoke', () => { expect(Arrays.exists([1, 2], x => x === 1)).toEqual(true) }) + test('with index', () => { + expect(Arrays.exists([1, 2], (x, index) => index === 1)).toEqual(true) + }) }) describe('get', () => { @@ -135,6 +169,11 @@ describe('get', () => { Arrays.get([{ name: 'amy', id: 1 }, { name: 'bob', id: 2 }], x => x.name === 'bob') ).toEqual({ name: 'bob', id: 2 }) }) + test('by index', () => { + expect( + Arrays.get([{ name: 'amy', id: 1 }, { name: 'bob', id: 2 }], (x, index) => index === 1) + ).toEqual({ name: 'bob', id: 2 }) + }) }) describe('find', () => { @@ -157,6 +196,11 @@ describe('find', () => { Arrays.find([{ name: 'amy', id: 1 }, { name: 'bob', id: 2 }], x => x.name === 'bob') ).toEqual({ name: 'bob', id: 2 }) }) + test('by index', () => { + expect( + Arrays.find([{ name: 'amy', id: 1 }, { name: 'bob', id: 2 }], (x, index) => index === 1) + ).toEqual({ name: 'bob', id: 2 }) + }) }) describe('groupBy', () => { @@ -181,6 +225,17 @@ describe('groupBy', () => { [2, [{ name: 'bob', age: 2 }, { name: 'cat', age: 2 }]] ]) }) + test('with index', () => { + expect( + Arrays.groupBy( + [{ name: 'amy', age: 1 }, { name: 'bob', age: 2 }, { name: 'cat', age: 2 }], + (x, index) => index % 2 + ) + ).toEqual([ + [0, [{ name: 'amy', age: 1 }, { name: 'cat', age: 2 }]], + [1, [{ name: 'bob', age: 2 }]] + ]) + }) }) describe('init', () => {