import * as Ap from 'fp-ts/Apply'
import * as E from 'fp-ts/Eq'
import { constTrue, pipe } from 'fp-ts/function'
import * as RA from 'fp-ts/ReadonlyArray'
import * as RNEA from 'fp-ts/ReadonlyNonEmptyArray'
import * as $E from './Eq'
import { curry } from './function'
/**
* {@link https://en.wikipedia.org/wiki/Cartesian_product}
*/
export const cartesian = Ap.sequenceT(RA.Apply)
/**
* {@link https://en.wikipedia.org/wiki/Word_(group_theory)}
*
* @example
* import { readonlyArray } from 'fp-ts'
* import { pipe } from 'fp-ts/function'
*
* // https://en.wikipedia.org/wiki/Rock_paper_scissors#Additional_weapons
* const shapes = ['Rock', 'Paper', 'Scissors', 'Spock', 'Lizard'] as const
* const win = (a: number, b: number): boolean =>
* !!{ 1: true, 3: true }[(5 + a - b) % 5]
*
* expect(
* pipe(
* shapes,
* readonlyArray.mapWithIndex((i, a) => [i, a] as const),
* words(2),
* readonlyArray.filter(([[a], [b]]) => win(a, b)),
* readonlyArray.map(([[, a], [, b]]) => `${a} defeats ${b}`)
* )
* ).toStrictEqual([
* 'Rock defeats Scissors',
* 'Rock defeats Lizard',
* 'Paper defeats Rock',
* 'Paper defeats Spock',
* 'Scissors defeats Paper',
* 'Scissors defeats Lizard',
* 'Spock defeats Rock',
* 'Spock defeats Scissors',
* 'Lizard defeats Paper',
* 'Lizard defeats Spock',
* ])
*/
export function words(
size: 3,
): (alphabeth: ReadonlyArray) => ReadonlyArray>
export function words(
size: 2,
): (alphabeth: ReadonlyArray) => ReadonlyArray>
export function words(
size: 1,
): (alphabeth: ReadonlyArray) => ReadonlyArray>
export function words(
size: number,
): (
alphabeth: ReadonlyArray,
) =>
| Readonly<[Readonly<[]>]>
| ReadonlyArray]>>
export function words(size: number) {
return (
alphabet: ReadonlyArray,
):
| Readonly<[Readonly<[]>]>
| ReadonlyArray]>> =>
size < 1
? ([[]] as const)
: pipe(alphabet, curry(RA.replicate)(size), ([head, ...tail]) =>
cartesian(head, ...tail),
)
}
export const allElems =
(E: E.Eq) =>
(...elems: RNEA.ReadonlyNonEmptyArray) =>
(as: ReadonlyArray): as is RNEA.ReadonlyNonEmptyArray =>
pipe(elems, RA.intersection(E)(as), curry($E.getEqSize(RA).equals)(elems))
export const anyElem =
(E: E.Eq) =>
(...elems: RNEA.ReadonlyNonEmptyArray) =>
(as: ReadonlyArray): as is RNEA.ReadonlyNonEmptyArray =>
pipe(
elems,
RA.some((elem) => pipe(as, RA.elem(E)(elem))),
)
/**
* @example
* import { eq, string } from 'fp-ts'
* import { Eq } from 'fp-ts/Eq'
* import { pipe } from 'fp-ts/function'
*
* type User = { name: string; mother: string; father: string }
*
* const eqParents: Eq = pipe(
* eq.tuple(string.Eq, string.Eq),
* eq.contramap(({ mother, father }) => [mother, father] as const)
* )
* const siblings = same(eqParents)
*
* expect(
* siblings([
* { name: 'Thomas', mother: 'Edith', father: 'Matthew' },
* { name: 'Thomas', mother: 'Edith', father: 'Richard' },
* ])
* ).toBe(false)
* expect(
* siblings([
* { name: 'Thomas', mother: 'Edith', father: 'Matthew' },
* { name: 'William', mother: 'Edith', father: 'Matthew' },
* ])
* ).toBe(true)
*/
export const same =
(E: E.Eq) =>
(as: ReadonlyArray): boolean =>
pipe(
as,
RA.matchLeft(constTrue, (head, tail) =>
pipe(tail, RA.every(curry(E.equals)(head))),
),
)