import type { CompareFn_ } from "@principia/prelude/Ord";
import { GT } from "@principia/prelude/Ordering";
import type { Lazy, Trampoline } from "../Function";
import { done, matchPredicate, more, trampoline } from "../Function";
import { cons, cons_, list, nil } from "./constructors";
import { isEmpty, isNonEmpty } from "./guards";
import type { LazyList } from "./model";
import { errorEmptyList } from "./model";
/*
* -------------------------------------------
* Combinators
* -------------------------------------------
*/
export const head: (xs: LazyList) => A = matchPredicate(
isNonEmpty,
(l) => l.head(),
(_) => errorEmptyList("head")
);
export const tail: (xs: LazyList) => LazyList = matchPredicate(
isNonEmpty,
(l) => l.tail(),
(_) => errorEmptyList("tail")
);
export const rangeBy_ = (start: number, end: number, step: (n: number) => number): LazyList => {
if (start === end)
return cons_(
() => start,
() => nil
);
return cons_(
() => start,
() => rangeBy_(step(start), end, step)
);
};
export const init: (xs: LazyList) => LazyList = matchPredicate(
isNonEmpty,
(l) =>
isEmpty(tail(l))
? nil
: cons_(
() => head(l),
() => init(tail(l))
),
(_) => errorEmptyList("init")
);
export const last: (xs: LazyList) => A = trampoline(function last(xs: LazyList): Trampoline {
return matchPredicate(
isNonEmpty,
(l) => (isEmpty(tail(l)) ? done(head(l)) : more(() => last(tail(l)))),
(_) => errorEmptyList("last")
)(xs);
});
export const take_ = (xs: LazyList, n: number): LazyList => {
if (n <= 0) return nil;
if (isEmpty(xs)) return nil;
return cons_(
() => head(xs),
() => take_(tail(xs), n - 1)
);
};
export const take = (n: number) => (xs: LazyList) => take_(xs, n);
export const lines: (s: string) => LazyList = matchPredicate(
(s) => s.length === 0,
(_) => nil,
(s) => list(...s.split("\n"))
);
/* eslint-disable @typescript-eslint/no-use-before-define */
export function sortBy_(xs: LazyList, cmp: CompareFn_): LazyList {
const _sequences = (xs: LazyList): Trampoline>> => {
if (isEmpty(xs)) {
return done(list(xs));
}
let as = tail(xs);
if (isEmpty(as)) {
return done(list(xs));
}
const a = head(xs);
const b = head(as);
as = tail(as);
if (cmp(a, b) === GT) {
return _descending(b, list(a), as);
}
return _ascending(b, cons(() => a) as any, as);
};
const _ascending = (
a: A,
fas: (xs: Lazy>) => LazyList,
bbs: LazyList
): Trampoline>> => {
if (isEmpty(bbs)) {
return done(
cons_(
() => fas(() => list(a)),
() => trampoline(_sequences)(bbs)
)
);
}
const b = head(bbs);
const bs = tail(bbs);
const ys = (ys: Lazy>) => fas(() => cons_(() => a, ys));
if (cmp(a, b) !== GT) {
return more(() => _ascending(b, ys, bs));
}
return done(
cons_(
() => fas(() => list(a)),
() => trampoline(_sequences)(bbs)
)
);
};
const _descending = (a: A, as: LazyList, bbs: LazyList): Trampoline>> => {
if (isEmpty(bbs)) {
return done(
cons_(
() =>
cons_(
() => a,
() => as
),
() => trampoline(_sequences)(bbs)
)
);
}
const b = head(bbs);
const bs = tail(bbs);
if (cmp(a, b) === GT) {
return more(() =>
_descending(
b,
cons_(
() => a,
() => as
),
bs
)
);
}
return done(
cons_(
() =>
cons_(
() => a,
() => as
),
() => trampoline(_sequences)(bbs)
)
);
};
const _mergePairs = (as: LazyList>): Trampoline>> => {
if (isEmpty(as)) {
return done(as);
}
let xs = tail(as);
if (isEmpty(xs)) {
return done(as);
}
const a = head(as);
const b = head(xs);
xs = tail(xs);
return done(
cons_(
() => trampoline(_merge)(a, b),
() => trampoline(_mergePairs)(xs)
)
);
};
const _merge = (as: LazyList, bs: LazyList): Trampoline> => {
if (isEmpty(as)) {
return done(bs);
}
if (isEmpty(bs)) {
return done(as);
}
const a = head(as);
const as1 = tail(as);
const b = head(bs);
const bs1 = tail(bs);
if (cmp(a, b) === GT) {
return done(
cons_(
() => b,
() => trampoline(_merge)(as, bs1)
)
);
}
return done(
cons_(
() => a,
() => trampoline(_merge)(as1, bs)
)
);
};
const _mergeAll = (xs: LazyList>): Trampoline> => {
if (isEmpty(tail(xs))) {
return done(head(xs));
}
return more(() => _mergeAll(trampoline(_mergePairs)(xs)));
};
return trampoline(_mergeAll)(trampoline(_sequences)(xs));
}
/* eslint-enable */
export const sortBy = (cmp: CompareFn_) => (xs: LazyList) => sortBy_(xs, cmp);
export const append_ = trampoline(function append(xs: LazyList, ys: LazyList): Trampoline> {
if (isEmpty(xs)) return done(ys);
if (isEmpty(ys)) return done(xs);
return done(
cons_(
() => head(xs),
() => trampoline(append)(tail(xs), ys)
)
);
});