From 629da90fd19f29ec95b4c555f4be8a2d8a123d6e Mon Sep 17 00:00:00 2001 From: Nikita Volodin Date: Tue, 19 Oct 2021 23:50:25 -0400 Subject: [PATCH] Add filterCollect to Record, Map and variants --- docs/modules/Map.ts.md | 11 +++++++ docs/modules/ReadonlyMap.ts.md | 13 ++++++++ docs/modules/ReadonlyRecord.ts.md | 35 ++++++++++++++++++++ docs/modules/Record.ts.md | 34 +++++++++++++++++++ src/Map.ts | 7 ++++ src/ReadonlyMap.ts | 19 +++++++++++ src/ReadonlyRecord.ts | 35 ++++++++++++++++++++ src/Record.ts | 22 +++++++++++++ test/Map.ts | 55 +++++++++++++++++++++++++++++++ test/ReadonlyMap.ts | 55 +++++++++++++++++++++++++++++++ test/ReadonlyRecord.ts | 10 ++++++ test/Record.ts | 10 ++++++ 12 files changed, 306 insertions(+) diff --git a/docs/modules/Map.ts.md b/docs/modules/Map.ts.md index 03af530f9..ff53e0ef4 100644 --- a/docs/modules/Map.ts.md +++ b/docs/modules/Map.ts.md @@ -58,6 +58,7 @@ Added in v2.0.0 - [collect](#collect) - [difference](#difference) - [elem](#elem) + - [filterCollect](#filtercollect) - [foldMap](#foldmap) - [foldMapWithIndex](#foldmapwithindex) - [intersection](#intersection) @@ -537,6 +538,16 @@ export declare const elem: (E: Eq) => { (a: A): (m: Map) => boole Added in v2.0.0 +## filterCollect + +**Signature** + +```ts +export declare const filterCollect: (O: Ord) => (f: (k: K, a: A) => O.Option) => (m: Map) => B[] +``` + +Added in v2.12.0 + ## foldMap **Signature** diff --git a/docs/modules/ReadonlyMap.ts.md b/docs/modules/ReadonlyMap.ts.md index 5b025a855..2e226b330 100644 --- a/docs/modules/ReadonlyMap.ts.md +++ b/docs/modules/ReadonlyMap.ts.md @@ -67,6 +67,7 @@ Added in v2.5.0 - [difference](#difference) - [elem](#elem) - [empty](#empty) + - [filterCollect](#filtercollect) - [foldMap](#foldmap) - [foldMapWithIndex](#foldmapwithindex) - [intersection](#intersection) @@ -655,6 +656,18 @@ export declare const empty: ReadonlyMap Added in v2.5.0 +## filterCollect + +**Signature** + +```ts +export declare function filterCollect( + O: Ord +): (f: (k: K, a: A) => Option) => (m: ReadonlyMap) => ReadonlyArray +``` + +Added in v2.12.0 + ## foldMap **Signature** diff --git a/docs/modules/ReadonlyRecord.ts.md b/docs/modules/ReadonlyRecord.ts.md index caae504fd..7dbbb67b4 100644 --- a/docs/modules/ReadonlyRecord.ts.md +++ b/docs/modules/ReadonlyRecord.ts.md @@ -78,6 +78,7 @@ Added in v2.5.0 - [elem](#elem) - [empty](#empty) - [every](#every) + - [filterCollect](#filtercollect) - [filterWithIndex](#filterwithindex) - [foldMapWithIndex](#foldmapwithindex) - [fromFoldable](#fromfoldable) @@ -792,6 +793,40 @@ export declare function every(predicate: Predicate): (r: ReadonlyRecord`. + +**Signature** + +```ts +export declare function filterCollect( + O: Ord +): (f: (k: K, a: A) => Option) => (r: ReadonlyRecord) => ReadonlyArray +``` + +**Example** + +```ts +import { none, some } from 'fp-ts/Option' +import { filterCollect } from 'fp-ts/ReadonlyRecord' +import { Ord } from 'fp-ts/string' + +const x: { readonly a: string; readonly b: boolean; readonly c: number } = { a: 'c', b: false, c: 123 } +assert.deepStrictEqual( + filterCollect(Ord)((key, value) => (typeof value === 'boolean' ? none : some({ key, value })))(x), + [ + { key: 'a', value: 'c' }, + { key: 'c', value: 123 }, + ] +) +``` + +Added in v2.12.0 + ## filterWithIndex **Signature** diff --git a/docs/modules/Record.ts.md b/docs/modules/Record.ts.md index e492749cf..ae07c23cf 100644 --- a/docs/modules/Record.ts.md +++ b/docs/modules/Record.ts.md @@ -64,6 +64,7 @@ Added in v2.0.0 - [deleteAt](#deleteat) - [elem](#elem) - [every](#every) + - [filterCollect](#filtercollect) - [filterMapWithIndex](#filtermapwithindex) - [filterWithIndex](#filterwithindex) - [foldMapWithIndex](#foldmapwithindex) @@ -641,6 +642,39 @@ export declare const every: (predicate: Predicate) => (r: Record`. + +**Signature** + +```ts +export declare const filterCollect: ( + O: Ord +) => (f: (k: K, a: A) => Option) => (r: Record) => B[] +``` + +**Example** + +```ts +import { none, some } from 'fp-ts/Option' +import { filterCollect } from 'fp-ts/Record' +import { Ord } from 'fp-ts/string' + +const x: { readonly a: string; readonly b: boolean; readonly c: number } = { a: 'c', b: false, c: 123 } +assert.deepStrictEqual( + filterCollect(Ord)((key, value) => (typeof value === 'boolean' ? none : some({ key, value })))(x), + [ + { key: 'a', value: 'c' }, + { key: 'c', value: 123 }, + ] +) +``` + +Added in v2.12.0 + ## filterMapWithIndex **Signature** diff --git a/src/Map.ts b/src/Map.ts index 03c49cbe0..4b3b81c91 100644 --- a/src/Map.ts +++ b/src/Map.ts @@ -104,6 +104,13 @@ export function collect(O: Ord): (f: (k: K, a: A) => B) => (m: Map( + O: Ord +) => (f: (k: K, a: A) => Option) => (m: Map) => Array = RM.filterCollect as any + /** * Get a sorted `Array` of the key/value pairs contained in a `Map`. * diff --git a/src/ReadonlyMap.ts b/src/ReadonlyMap.ts index f52c80a0f..0555ee648 100644 --- a/src/ReadonlyMap.ts +++ b/src/ReadonlyMap.ts @@ -168,6 +168,25 @@ export function collect(O: Ord): (f: (k: K, a: A) => B) => (m: Reado } } +/** + * @since 2.12.0 + */ +export function filterCollect( + O: Ord +): (f: (k: K, a: A) => Option) => (m: ReadonlyMap) => ReadonlyArray { + const keysO = keys(O) + return (f: (k: K, a: A) => Option) => (m: ReadonlyMap): ReadonlyArray => { + const out: Array = [] + for (const key of keysO(m)) { + const x = f(key, m.get(key)!) + if (_.isSome(x)) { + out.push(x.value) + } + } + return out + } +} + /** * Get a sorted `ReadonlyArray` of the key/value pairs contained in a `ReadonlyMap`. * diff --git a/src/ReadonlyRecord.ts b/src/ReadonlyRecord.ts index ab1b663df..9fbb7b128 100644 --- a/src/ReadonlyRecord.ts +++ b/src/ReadonlyRecord.ts @@ -128,6 +128,41 @@ export function collect( } } +/** + * Map a `ReadonlyRecord` into a `ReadonlyArray` passing a key/value pair to the + * iterating function and filtering out undesired results. The keys in the + * resulting record are sorted according to the passed instance of + * `Ord`. + * + * @example + * import { none, some } from 'fp-ts/Option' + * import { filterCollect } from 'fp-ts/ReadonlyRecord' + * import { Ord } from 'fp-ts/string' + * + * const x: { readonly a: string, readonly b: boolean, readonly c: number } = { a: 'c', b: false, c: 123 } + * assert.deepStrictEqual( + * filterCollect(Ord)((key, value) => typeof value === 'boolean' ? none : some({ key, value }))(x), + * [{ key: 'a', value: 'c' }, { key: 'c', value: 123 }] + * ) + * + * @since 2.12.0 + */ +export function filterCollect( + O: Ord +): (f: (k: K, a: A) => Option) => (r: ReadonlyRecord) => ReadonlyArray { + const keysO = keys_(O) + return (f: (k: K, a: A) => Option) => (r: ReadonlyRecord): ReadonlyArray => { + const out: Array = [] + for (const key of keysO(r)) { + const x = f(key, r[key]) + if (_.isSome(x)) { + out.push(x.value) + } + } + return out + } +} + /** * Get a sorted `ReadonlyArray` of the key/value pairs contained in a `ReadonlyRecord`. * diff --git a/src/Record.ts b/src/Record.ts index 11d481f49..7dcb8b5a6 100644 --- a/src/Record.ts +++ b/src/Record.ts @@ -98,6 +98,28 @@ export function collect( } } +/** + * Map a `Record` into an `Array` passing a key/value pair to the iterating + * function and filtering out undesired results. The keys in the resulting + * record are sorted according to the passed instance of `Ord`. + * + * @example + * import { none, some } from 'fp-ts/Option' + * import { filterCollect } from 'fp-ts/Record' + * import { Ord } from 'fp-ts/string' + * + * const x: { readonly a: string, readonly b: boolean, readonly c: number } = { a: 'c', b: false, c: 123 } + * assert.deepStrictEqual( + * filterCollect(Ord)((key, value) => typeof value === 'boolean' ? none : some({ key, value }))(x), + * [{ key: 'a', value: 'c' }, { key: 'c', value: 123 }] + * ) + * + * @since 2.12.0 + */ +export const filterCollect: ( + O: Ord +) => (f: (k: K, a: A) => Option) => (r: Record) => Array = RR.filterCollect as any + /** * Get a sorted `Array` of the key/value pairs contained in a `Record`. * diff --git a/test/Map.ts b/test/Map.ts index fee42208d..d913786af 100644 --- a/test/Map.ts +++ b/test/Map.ts @@ -225,6 +225,61 @@ describe('Map', () => { ) }) + it('filterCollect', () => { + const collectO = _.filterCollect(ordUser) + const f = (_k: User, a: number): O.Option => (a % 2 === 0 ? O.none : O.some(a + 1)) + U.deepStrictEqual( + collectO(f)( + new Map([ + [{ id: 'a' }, 1], + [{ id: 'b' }, 2], + [{ id: 'c' }, 3] + ]) + ), + [2, 4] + ) + U.deepStrictEqual( + collectO(f)( + new Map([ + [{ id: 'b' }, 2], + [{ id: 'a' }, 1], + [{ id: 'c' }, 3] + ]) + ), + [2, 4] + ) + + const collect = _.filterCollect(ordKey) + const g = (k: Key, a: Value): O.Option => + k.id % 2 === 0 ? O.some([k.id, a.value]) : O.none + U.deepStrictEqual( + collect(g)( + new Map([ + [{ id: 1 }, { value: 1 }], + [{ id: 2 }, { value: 2 }], + [{ id: 4 }, { value: 4 }] + ]) + ), + [ + [4, 4], + [2, 2] + ] + ) + U.deepStrictEqual( + collect(g)( + new Map([ + [{ id: 2 }, { value: 2 }], + [{ id: 1 }, { value: 1 }], + [{ id: 4 }, { value: 4 }] + ]) + ), + [ + [4, 4], + [2, 2] + ] + ) + }) + it('toArray', () => { const m1 = new Map([ [{ id: 'a' }, 1], diff --git a/test/ReadonlyMap.ts b/test/ReadonlyMap.ts index ea6a24388..040c13cd0 100644 --- a/test/ReadonlyMap.ts +++ b/test/ReadonlyMap.ts @@ -321,6 +321,61 @@ describe('ReadonlyMap', () => { ) }) + it('filterCollect', () => { + const collectO = _.filterCollect(ordUser) + const f = (_k: User, a: number): O.Option => (a % 2 === 0 ? O.none : O.some(a + 1)) + U.deepStrictEqual( + collectO(f)( + new Map([ + [{ id: 'a' }, 1], + [{ id: 'b' }, 2], + [{ id: 'c' }, 3] + ]) + ), + [2, 4] + ) + U.deepStrictEqual( + collectO(f)( + new Map([ + [{ id: 'b' }, 2], + [{ id: 'a' }, 1], + [{ id: 'c' }, 3] + ]) + ), + [2, 4] + ) + + const collect = _.filterCollect(ordKey) + const g = (k: Key, a: Value): O.Option => + k.id % 2 === 0 ? O.some([k.id, a.value]) : O.none + U.deepStrictEqual( + collect(g)( + new Map([ + [{ id: 1 }, { value: 1 }], + [{ id: 2 }, { value: 2 }], + [{ id: 4 }, { value: 4 }] + ]) + ), + [ + [4, 4], + [2, 2] + ] + ) + U.deepStrictEqual( + collect(g)( + new Map([ + [{ id: 2 }, { value: 2 }], + [{ id: 1 }, { value: 1 }], + [{ id: 4 }, { value: 4 }] + ]) + ), + [ + [4, 4], + [2, 2] + ] + ) + }) + it('toReadonlyArray', () => { const m1 = new Map([ [{ id: 'a' }, 1], diff --git a/test/ReadonlyRecord.ts b/test/ReadonlyRecord.ts index abbe8cca5..1f329cfbe 100644 --- a/test/ReadonlyRecord.ts +++ b/test/ReadonlyRecord.ts @@ -31,6 +31,16 @@ describe('ReadonlyRecord', () => { { key: 'b', value: false } ]) }) + it('filterCollect', () => { + const x: { readonly a: string; readonly b: boolean; readonly c: number } = { a: 'c', b: false, c: 123 } + U.deepStrictEqual( + _.filterCollect(S.Ord)((key, value) => (typeof value === 'boolean' ? O.none : O.some({ key, value })))(x), + [ + { key: 'a', value: 'c' }, + { key: 'c', value: 123 } + ] + ) + }) it('map', () => { U.deepStrictEqual(pipe({ k1: 1, k2: 2 }, _.map(U.double)), { k1: 2, k2: 4 }) diff --git a/test/Record.ts b/test/Record.ts index 100300190..d0220323f 100644 --- a/test/Record.ts +++ b/test/Record.ts @@ -31,6 +31,16 @@ describe('Record', () => { { key: 'b', value: false } ]) }) + it('filterCollect', () => { + const x = { a: 'c', b: false, c: 123 } + U.deepStrictEqual( + _.filterCollect(S.Ord)((key, value) => (typeof value === 'boolean' ? O.none : O.some({ key, value })))(x), + [ + { key: 'a', value: 'c' }, + { key: 'c', value: 123 } + ] + ) + }) it('map', () => { U.deepStrictEqual(pipe({ k1: 1, k2: 2 }, _.map(U.double)), { k1: 2, k2: 4 })