Skip to content

Commit

Permalink
refactor(types)!: [Predicate] use Mapper
Browse files Browse the repository at this point in the history
Signed-off-by: Lexus Drumgold <[email protected]>
  • Loading branch information
unicornware committed Jul 20, 2023
1 parent b22f86f commit e3eaf4b
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 85 deletions.
17 changes: 8 additions & 9 deletions src/types/__tests__/predicate.spec-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@
* @module tutils/types/tests/unit-d/Predicate
*/

import type Vehicle from '#fixtures/types/vehicle'
import type Mapper from '../mapper'
import type Nullable from '../nullable'
import type TestSubject from '../predicate'

describe('unit-d:types/Predicate', () => {
type T = number
it('should Mapper<T, boolean>', () => {
// Arrange
type T = readonly [Vehicle, Nullable<Vehicle>?]

it('should be callable with [T, number, readonly T[]]', () => {
expectTypeOf<TestSubject<T>>().parameters.toEqualTypeOf<
[T, number, readonly T[]]
>()
})

it('should return boolean', () => {
expectTypeOf<TestSubject<T>>().returns.toBeBoolean()
// Expect
expectTypeOf<TestSubject<T>>().toEqualTypeOf<Mapper<T, boolean>>()
})
})
16 changes: 8 additions & 8 deletions src/types/predicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
* @module tutils/types/Predicate
*/

import type Fn from './fn'
import type Mapper from './mapper'

/**
* Function that is called once per each item in `array` and returns a boolean
* indicating if `item` meets a certain condition.
* Function called once per each item in an array and returns a `boolean` value
* that indicates if an item meets certain conditions.
*
* @template T - Array item type
* @template T - Array being evaluated
*
* @param {T} item - Current array item
* @param {T[number]} item - Current array item
* @param {number} index - Index of `item` in `array`
* @param {ReadonlyArray<T>} array - Array being queried
* @return {boolean} `true` if condition is met, `false` otherwise
* @param {T} array - Array being evaluated
* @return {boolean} Boolean indicating if `item` meets given conditions
*/
type Predicate<T = unknown> = Fn<[T, number, readonly T[]], boolean>
type Predicate<T extends readonly unknown[]> = Mapper<T, boolean>

export type { Predicate as default }
17 changes: 6 additions & 11 deletions src/utils/__tests__/count.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,20 @@
* @module tutils/utils/tests/unit/count
*/

import type { Fn } from '#src/types'
import testSubject from '../count'

describe('unit:utils/count', () => {
let array: number[]
let arr: number[]

beforeAll(() => {
array = [0, 1, 2, 3, 4]
arr = [0, 1, 2, 3, 4]
})

it('should return array length if condition is omitted', () => {
expect(testSubject(array)).to.equal(array.length)
it('should return arr.length if condition is omitted', () => {
expect(testSubject(arr)).to.equal(arr.length)
})

it('should return number of items in array that meet condition', () => {
// Arrange
const condition: Fn<[number, number], boolean> = (n: number) => n >= 3

// Act + Expect
expect(testSubject(array, condition)).to.equal(2)
it('should return number of items in arr that meet condition', () => {
expect(testSubject(arr, (n: number) => n >= 3)).to.equal(2)
})
})
19 changes: 19 additions & 0 deletions src/utils/__tests__/fork.spec-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @file Type Tests - fork
* @module tutils/utils/tests/unit-d/fork
*/

import type Vehicle from '#fixtures/types/vehicle'
import type { Times } from '#src/types'
import type testSubject from '../fork'

describe('unit-d:utils/fork', () => {
it('should return [T[number][], T[number][]]', () => {
// Arrange
type T = Readonly<Times<5, Vehicle | Vehicle['vin']>>
type Expect = [T[number][], T[number][]]

// Expect
expectTypeOf<typeof testSubject<T>>().returns.toEqualTypeOf<Expect>()
})
})
11 changes: 3 additions & 8 deletions src/utils/__tests__/fork.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,17 @@
* @module tutils/utils/tests/unit/fork
*/

import type { Fn } from '#src/types'
import testSubject from '../fork'

describe('unit:utils/fork', () => {
let array: number[]
let arr: number[]

beforeAll(() => {
array = [0, 1, 2, 3, 4]
arr = [0, 1, 2, 3, 4]
})

it('should return condition groups', () => {
// Arrange
const condition: Fn<[number, number], boolean> = (n: number) => n >= 3

// Act + Expect
expect(testSubject(array, condition)).to.deep.equal([
expect(testSubject(arr, (n: number) => n >= 3)).to.eql([
[3, 4],
[0, 1, 2]
])
Expand Down
18 changes: 18 additions & 0 deletions src/utils/__tests__/select.spec-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @file Type Tests - select
* @module tutils/utils/tests/unit-d/select
*/

import type Vehicle from '#fixtures/types/vehicle'
import type testSubject from '../select'

describe('unit-d:utils/select', () => {
it('should return U[]', () => {
// Arrange
type T = Vehicle[]
type U = Vehicle['vin']

// Expect
expectTypeOf<typeof testSubject<T, U>>().returns.toEqualTypeOf<U[]>
})
})
12 changes: 10 additions & 2 deletions src/utils/__tests__/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@
import testSubject from '../select'

describe('unit:utils/select', () => {
it('should return array if filter and map are omitted', () => {
it('should return arr if filter and map are omitted', () => {
// Arrange
const array: number[] = [0, 1, 2, 3, 4, 5]

// Expect
expect(testSubject(array)).to.deep.equal(array)
expect(testSubject(array)).to.eql(array)
})

it('should return empty array if arr is null', () => {
expect(testSubject(null)).to.eql([])
})

it('should return empty array if arr is undefined', () => {
expect(testSubject(undefined)).to.eql([])
})
})
29 changes: 16 additions & 13 deletions src/utils/count.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,28 @@
* @module tutils/utils/count
*/

import type { Fn } from '#src/types'
import type { Predicate } from '#src/types'
import constant from './constant'

/**
* Returns the number of items in `array` that meet `condition`.
* Returns the number of items in an array that meet a given `condition`.
*
* If `condition` is omitted, the length of `array` will be returned.
* The length of the array will be returned if a `condition` is not provided.
*
* @template T - Array item type
* @todo examples
*
* @param {ReadonlyArray<T>} array - Array to query
* @param {Fn<[T, number], boolean>} [condition=()=>true] - Condition function
* @return {number} Number of items in `array` that meet `condition`
* @template T - Array to query
*
* @param {T} arr - Array to query
* @param {Predicate<T>} [condition=constant(true)] - Condition function
* @return {number} Number of items in `arr` that meet `condition`
*/
function count<T>(
array: readonly T[],
condition: Fn<[T, number], boolean> = () => true
): number {
return array.reduce((acc: number, curr: T, index: number): number => {
return condition(curr, index) ? acc + 1 : acc
const count = <T extends readonly unknown[]>(
arr: T,
condition: Predicate<T> = constant(true)
): number => {
return arr.reduce<number>((acc, curr, index) => {
return condition(curr, index, arr) ? ++acc : acc
}, 0)
}

Expand Down
31 changes: 18 additions & 13 deletions src/utils/fork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,40 @@
import type { Predicate } from '#src/types'

/**
* Splits `array` into two groups.
* Splits an array into two groups.
*
* The first group will contain items that meet `condition`. The second group
* will contain items that do not meet `condition`.
*
* @template T - Array item type
* @todo examples
*
* @param {ReadonlyArray<T>} array - Array to split
* @template T - Array to split
*
* @param {T} arr - Array to split
* @param {Predicate<T>} condition - Condition function
* @return {[T[], T[]]} Condition groups
* @return {[T[number][], T[number][]]} Condition groups
*/
function fork<T>(array: readonly T[], condition: Predicate<T>): [T[], T[]] {
const fork = <T extends readonly unknown[]>(
arr: T,
condition: Predicate<T>
): [T[number][], T[number][]] => {
/**
* Items in {@linkcode array} that do not meet {@linkcode condition}.
* Items in {@linkcode arr} that do not meet {@linkcode condition}.
*
* @const {T[]} fail
* @const {T[number][]} fail
*/
const fail: T[] = []
const fail: T[number][] = []

/**
* Items in {@linkcode array} that meet {@linkcode condition}.
* Items in {@linkcode arr} that meet {@linkcode condition}.
*
* @const {T[]} pass
* @const {T[number][]} pass
*/
const pass: T[] = []
const pass: T[number][] = []

// split array by condition
for (const [index, item] of array.entries()) {
condition(item, index, array) ? pass.push(item) : fail.push(item)
for (const [index, item] of arr.entries()) {
;(condition(item, index, arr) ? pass : fail).push(item)
}

return [pass, fail]
Expand Down
48 changes: 27 additions & 21 deletions src/utils/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,39 @@
* @module tutils/utils/select
*/

import type { Fn, Nilable } from '#src/types'
import type { Mapper, Nilable, Predicate } from '#src/types'
import cast from './cast'
import constant from './constant'

/**
* Performs `filter` and `map` operations on `array` in one iteration.
* Performs `filter` and `map` operations on an array in one iteration.
*
* Does nothing if `filter` and `map` are omitted.
* If `filter` and `map` are omitted, all array items will be selected.
*
* @template T - Array item type
* @template V - Mapped and filtered array item type
* @see {@linkcode Mapper}
* @see {@linkcode Predicate}
*
* @param {ReadonlyArray<T>} array - Array to filter and map
* @param {Nilable<Fn<[T, number], boolean>>} [filter] - Filter function
* @param {Nilable<Fn<[T, number], V>>} [map] - Map function
* @return {V[]} Filtered and mapped `array`
* @todo examples
*
* @template T - Array to select from
* @template U - Filtered and mapped array item type
*
* @param {Nilable<T>} arr - Array to select from
* @param {Nilable<Predicate<T>>} [filter] - Filter function
* @param {Nilable<Mapper<T, U>>} [map] - Map function
* @return {U[]} Filtered and mapped array
*/
function select<T, V>(
array: readonly T[],
filter?: Nilable<Fn<[T, number], boolean>>,
map?: Nilable<Fn<[T, number], V>>
): V[] {
return [...array]
.reverse()
.reduce<V[]>((acc: V[], curr: T, index: number): V[] => {
return (filter ??= () => true)(curr, index)
? [(map ??= item => item as unknown as V)(curr, index), ...acc]
: acc
}, [])
const select = <T extends readonly unknown[], U = T[number]>(
arr: Nilable<T>,
filter?: Nilable<Predicate<T>>,
map?: Nilable<Mapper<T, U>>
): U[] => {
arr ??= cast<NonNullable<typeof arr>>([])
return [...arr].reduceRight<U[]>((acc, curr, i) => {
return (filter ??= constant(true))(curr, i, cast(arr))
? [(map ?? (item => cast(item)))(curr, i, cast(arr)), ...acc]
: acc
}, [])
}

export default select

0 comments on commit e3eaf4b

Please sign in to comment.