// ets_tracing: off /* eslint-disable prefer-const */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable no-var */ import type { Either } from "../../../Either/index.js" import { identity } from "../../../Function/index.js" import * as O from "../../../Option/index.js" import type { Ord } from "../../../Ord/index.js" import * as St from "../../../Structural/index.js" import * as Tp from "../Tuple/index.js" /** * Forked from https://github.com/funkia/list/blob/master/src/index.ts * * All credits to original authors. * * The implementation has been forked to adapt to the double standard pipeable/data first * available in the remaining modules and to remove the fantasy-land bias. */ const branchingFactor = 32 const branchBits = 5 const mask = 31 function elementEquals(a: any, b: any): boolean { if (a === b) { return true } else { return false } } function createPath(depth: number, value: any): any { let current = value for (let i = 0; i < depth; ++i) { current = new Node(undefined, [current]) } return current } // Array helper functions function copyArray(source: any[]): any[] { const array = [] for (let i = 0; i < source.length; ++i) { array[i] = source[i] } return array } function pushElements( source: A[], target: A[], offset: number, amount: number ): void { for (let i = offset; i < offset + amount; ++i) { target.push(source[i]!) } } function copyIndices( source: any[], sourceStart: number, target: any[], targetStart: number, length: number ): void { for (let i = 0; i < length; ++i) { target[targetStart + i] = source[sourceStart + i] } } function arrayPrepend(value: A, array: A[]): A[] { const newLength = array.length + 1 const result = new Array(newLength) result[0] = value for (let i = 1; i < newLength; ++i) { result[i] = array[i - 1] } return result } /** * Create a reverse _copy_ of an array. */ function reverseArray(array: A[]): A[] { return array.slice().reverse() } function arrayFirst(array: A[]): A { return array[0]! } function arrayLast(array: A[]): A { return array[array.length - 1]! } const pathResult = { path: 0, index: 0, updatedOffset: 0 } type PathResult = typeof pathResult function getPath( index: number, offset: number, depth: number, sizes: Sizes ): PathResult { if (sizes === undefined && offset !== 0) { pathResult.updatedOffset = 0 index = handleOffset(depth, offset, index) } let path = (index >> (depth * branchBits)) & mask if (sizes !== undefined) { while (sizes[path]! <= index) { path++ } const traversed = path === 0 ? 0 : sizes[path - 1]! index -= traversed pathResult.updatedOffset = offset } pathResult.path = path pathResult.index = index return pathResult } function updateNode( node: Node, depth: number, index: number, offset: number, value: any ): Node { const { index: newIndex, path, updatedOffset } = getPath(index, offset, depth, node.sizes) const array = copyArray(node.array) array[path] = depth > 0 ? updateNode(array[path], depth - 1, newIndex, updatedOffset, value) : value return new Node(node.sizes, array) } export type Sizes = number[] | undefined export class Node { constructor(public sizes: Sizes, public array: any[]) {} } function cloneNode({ array, sizes }: Node): Node { return new Node(sizes === undefined ? undefined : copyArray(sizes), copyArray(array)) } // This array should not be mutated. Thus a dummy element is placed in // it. Thus the affix will not be owned and thus not mutated. const emptyAffix: any[] = [0] // We store a bit field in list. From right to left, the first five // bits are suffix length, the next five are prefix length and the // rest is depth. The functions below are for working with the bits in // a sane way. const affixBits = 6 const affixMask = 0b111111 function getSuffixSize(l: List): number { return l.bits & affixMask } function getPrefixSize(l: List): number { return (l.bits >> affixBits) & affixMask } function getDepth(l: List): number { return l.bits >> (affixBits * 2) } function setPrefix(size: number, bits: number): number { return (size << affixBits) | (bits & ~(affixMask << affixBits)) } function setSuffix(size: number, bits: number): number { return size | (bits & ~affixMask) } function setDepth(depth: number, bits: number): number { return (depth << (affixBits * 2)) | (bits & (affixMask | (affixMask << affixBits))) } function incrementPrefix(bits: number): number { return bits + (1 << affixBits) } function incrementSuffix(bits: number): number { return bits + 1 } function incrementDepth(bits: number): number { return bits + (1 << (affixBits * 2)) } function decrementDepth(bits: number): number { return bits - (1 << (affixBits * 2)) } /* * Invariants that any list `l` should satisfy * * 1. If `l.root !== undefined` then `getSuffixSize(l) !== 0` and * `getPrefixSize(l) !== 0`. The invariant ensures that `first` and * `last` never have to look in the root and that they therefore * take O(1) time. * 2. If a tree or sub-tree does not have a size-table then all leaf * nodes in the tree are of size 32. */ /** * Represents a list of elements. */ export class List implements Iterable, St.HasEquals, St.HasHash { constructor( readonly bits: number, readonly offset: number, readonly length: number, readonly prefix: A[], readonly root: Node | undefined, readonly suffix: A[] ) {} [Symbol.iterator](): Iterator { return new ForwardListIterator(this) } toJSON(): readonly A[] { return toArray(this) } [St.equalsSym](that: unknown): boolean { return that instanceof List && equalsWith_(this, that, St.equals) } get [St.hashSym](): number { return St.hashIterator(this[Symbol.iterator]()) } } export type MutableList = { -readonly [K in keyof List]: List[K] } & { [Symbol.iterator]: () => Iterator // This property doesn't exist at run-time. It exists to prevent a // MutableList from being assignable to a List. "@@mutable": true } function cloneList(l: List): MutableList { return new List(l.bits, l.offset, l.length, l.prefix, l.root, l.suffix) as any } abstract class ListIterator implements Iterator { stack: any[][] | undefined indices: number[] | undefined idx: number prefixSize: number middleSize: number result: IteratorResult = { done: false, value: undefined as any } constructor(protected l: List, direction: 1 | -1) { this.idx = direction === 1 ? -1 : l.length this.prefixSize = getPrefixSize(l) this.middleSize = l.length - getSuffixSize(l) if (l.root !== undefined) { const depth = getDepth(l) this.stack = new Array(depth + 1) this.indices = new Array(depth + 1) let currentNode = l.root.array for (let i = depth; 0 <= i; --i) { this.stack[i] = currentNode const idx = direction === 1 ? 0 : currentNode.length - 1 this.indices[i] = idx currentNode = currentNode[idx].array } this.indices[0] -= direction } } abstract next(): IteratorResult } class ForwardListIterator extends ListIterator { constructor(l: List) { super(l, 1) } nextInTree(): void { for (var i = 0; ++this.indices![i] === this.stack![i]!.length; ++i) { this.indices![i] = 0 } for (; 0 < i; --i) { this.stack![i - 1] = this.stack![i]![this.indices![i]!].array } } next(): IteratorResult { let newVal const idx = ++this.idx if (idx < this.prefixSize) { newVal = this.l.prefix[this.prefixSize - idx - 1] } else if (idx < this.middleSize) { this.nextInTree() newVal = this.stack![0]![this.indices![0]!] } else if (idx < this.l.length) { newVal = this.l.suffix[idx - this.middleSize] } else { this.result.done = true } this.result.value = newVal return this.result } } class BackwardsListIterator extends ListIterator { constructor(l: List) { super(l, -1) } prevInTree(): void { for (var i = 0; this.indices![i] === 0; ++i) { // } --this.indices![i] for (; 0 < i; --i) { const n = this.stack![i]![this.indices![i]!].array this.stack![i - 1] = n this.indices![i - 1] = n.length - 1 } } next(): IteratorResult { let newVal const idx = --this.idx if (this.middleSize <= idx) { newVal = this.l.suffix[idx - this.middleSize] } else if (this.prefixSize <= idx) { this.prevInTree() newVal = this.stack![0]![this.indices![0]!] } else if (0 <= idx) { newVal = this.l.prefix[this.prefixSize - idx - 1] } else { this.result.done = true } this.result.value = newVal return this.result } } /** * Returns an iterable that iterates backwards over the given list. * * @complexity O(1) */ export function backwards(l: List): Iterable { return { [Symbol.iterator](): Iterator { return new BackwardsListIterator(l) } } } export function emptyPushable(): MutableList { return new List(0, 0, 0, [], undefined, []) as any } /** Appends the value to the list by _mutating_ the list and its content. */ export function push_(l: MutableList, value: A): MutableList { const suffixSize = getSuffixSize(l) if (l.length === 0) { l.bits = setPrefix(1, l.bits) l.prefix = [value] } else if (suffixSize < 32) { l.bits = incrementSuffix(l.bits) l.suffix.push(value) } else if (l.root === undefined) { l.root = new Node(undefined, l.suffix) l.suffix = [value] l.bits = setSuffix(1, l.bits) } else { const newNode = new Node(undefined, l.suffix) const index = l.length - 1 - 32 + 1 let current = l.root! let depth = getDepth(l) l.suffix = [value] l.bits = setSuffix(1, l.bits) if (index - 1 < branchingFactor ** (depth + 1)) { for (; depth >= 0; --depth) { const path = (index >> (depth * branchBits)) & mask if (path < current.array.length) { current = current.array[path] } else { current.array.push(createPath(depth - 1, newNode)) break } } } else { l.bits = incrementDepth(l.bits) l.root = new Node(undefined, [l.root, createPath(depth, newNode)]) } } l.length++ return l } /** * Creates a list of the given elements. * * @complexity O(n) */ export function list(...elements: A[]): List { const l = emptyPushable() for (const element of elements) { push_(l, element) } return l } /** * Creates an empty list. * * @complexity O(1) */ export function empty(): List { return new List(0, 0, 0, emptyAffix, undefined, emptyAffix) } /** * Takes a single arguments and returns a singleton list that contains it. * * @complexity O(1) */ export function of(a: A): List { return list(a) } /** * Takes two arguments and returns a list that contains them. * * @complexity O(1) */ export function pair(second: A): (first: A) => List { return (first: A) => pair_(first, second) } /** * Takes two arguments and returns a list that contains them. * * @complexity O(1) */ export function pair_(first: A, second: A): List { return new List(2, 0, 2, emptyAffix, undefined, [first, second]) } /** * Converts an array, an array-like, or an iterable into a list. * * @complexity O(n) */ export function from(sequence: A[] | ArrayLike | Iterable): List export function from(sequence: any): List { const l = emptyPushable() if (sequence.length > 0 && (sequence[0] !== undefined || 0 in sequence)) { for (let i = 0; i < sequence.length; ++i) { push_(l, sequence[i]) } } else if (Symbol.iterator in sequence) { const iterator = sequence[Symbol.iterator]() let cur // tslint:disable-next-line:no-conditional-assignment while (!(cur = iterator.next()).done) { push_(l, cur.value) } } return l } /** * Returns a list of numbers between an inclusive lower bound and an exclusive upper bound. * * @complexity O(n) */ export function range(end: number): (start: number) => List { return (start) => range_(start, end) } /** * Returns a list of numbers between an inclusive lower bound and an exclusive upper bound. * * @complexity O(n) */ export function range_(start: number, end: number): List { const list = emptyPushable() for (let i = start; i < end; ++i) { push_(list, i) } return list } /** * Returns a list of a given length that contains the specified value * in all positions. * * @complexity O(n) */ export function repeat(times: number): (value: A) => List { return (value) => repeat_(value, times) } /** * Returns a list of a given length that contains the specified value * in all positions. * * @complexity O(n) */ export function repeat_(value: A, times: number): List { const l = emptyPushable() while (--times >= 0) { push_(l, value) } return l } /** * Generates a new list by calling a function with the current index * `n` times. * * @complexity O(n) */ export function times(times: number): (func: (index: number) => A) => List { return (func) => times_(func, times) } /** * Generates a new list by calling a function with the current index * `n` times. * * @complexity O(n) */ export function times_(func: (index: number) => A, times: number): List { const l = emptyPushable() for (let i = 0; i < times; i++) { push_(l, func(i)) } return l } function nodeNthDense(node: Node, depth: number, index: number): any { let current = node for (; depth >= 0; --depth) { current = current.array[(index >> (depth * branchBits)) & mask] } return current } function handleOffset(depth: number, offset: number, index: number): number { index += offset for (; depth >= 0; --depth) { index = index - (offset & (mask << (depth * branchBits))) if (((index >> (depth * branchBits)) & mask) !== 0) { break } } return index } function nodeNth(node: Node, depth: number, offset: number, index: number): any { let path let current = node while (current.sizes !== undefined) { path = (index >> (depth * branchBits)) & mask while (current.sizes[path]! <= index) { path++ } if (path !== 0) { index -= current.sizes[path - 1]! offset = 0 // Offset is discarded if the left spine isn't traversed } depth-- current = current.array[path] } return nodeNthDense( current, depth, offset === 0 ? index : handleOffset(depth, offset, index) ) } /** * Gets the nth element of the list. If `n` is out of bounds * `undefined` is returned. * * @complexity O(log(n)) */ export function unsafeNth_(l: List, index: number): A | undefined { return O.toUndefined(nth_(l, index)) } /** * Gets the nth element of the list. If `n` is out of bounds * `undefined` is returned. * * @complexity O(log(n)) */ export function unsafeNth(index: number): (l: List) => A | undefined { return (l) => unsafeNth_(l, index) } /** * Gets the nth element of the list. If `n` is out of bounds * `undefined` is returned. * * @complexity O(log(n)) */ export function nth_(l: List, index: number): O.Option { if (index < 0 || l.length <= index) { return O.none } const prefixSize = getPrefixSize(l) const suffixSize = getSuffixSize(l) if (index < prefixSize) { return O.some(l.prefix[prefixSize - index - 1]!) } else if (index >= l.length - suffixSize) { return O.some(l.suffix[index - (l.length - suffixSize)]!) } const { offset } = l const depth = getDepth(l) return O.some( l.root!.sizes === undefined ? nodeNthDense( l.root!, depth, offset === 0 ? index - prefixSize : handleOffset(depth, offset, index - prefixSize) ) : nodeNth(l.root!, depth, offset, index - prefixSize) ) } /** * Gets the nth element of the list. If `n` is out of bounds * `undefined` is returned. * * @complexity O(log(n)) */ export function nth(index: number): (l: List) => O.Option { return (l) => nth_(l, index) } function setSizes(node: Node, height: number): Node { let sum = 0 const sizeTable = [] for (let i = 0; i < node.array.length; ++i) { sum += sizeOfSubtree(node.array[i], height - 1) sizeTable[i] = sum } node.sizes = sizeTable return node } /** * Returns the number of elements stored in the node. */ function sizeOfSubtree(node: Node, height: number): number { if (height !== 0) { if (node.sizes !== undefined) { return arrayLast(node.sizes) } else { // the node is leftwise dense so all all but the last child are full const lastSize = sizeOfSubtree(arrayLast(node.array), height - 1) return ((node.array.length - 1) << (height * branchBits)) + lastSize } } else { return node.array.length } } // prepend & append function affixPush(a: A, array: A[], length: number): A[] { if (array.length === length) { array.push(a) return array } else { const newArray: A[] = [] copyIndices(array, 0, newArray, 0, length) newArray.push(a) return newArray } } /** * Prepends an element to the front of a list and returns the new list. * * @complexity O(1) */ export function prepend_(l: List, value: A): List { const prefixSize = getPrefixSize(l) if (prefixSize < 32) { return new List( incrementPrefix(l.bits), l.offset, l.length + 1, affixPush(value, l.prefix, prefixSize), l.root, l.suffix ) } else { const newList = cloneList(l) prependNodeToTree(newList, reverseArray(l.prefix)) const newPrefix = [value] newList.prefix = newPrefix newList.length++ newList.bits = setPrefix(1, newList.bits) return newList } } /** * Prepends an element to the front of a list and returns the new list. * * @complexity O(1) */ export function prepend(value: A): (l: List) => List { return (l) => prepend_(l, value) } /** * Traverses down the left edge of the tree and copies k nodes. * Returns the last copied node. * @param l * @param k The number of nodes to copy. Should always be at least 1. */ function copyLeft(l: MutableList, k: number): Node { let currentNode = cloneNode(l.root!) // copy root l.root = currentNode // install copy of root for (let i = 1; i < k; ++i) { const index = 0 // go left if (currentNode.sizes !== undefined) { for (let i = 0; i < currentNode.sizes.length; ++i) { currentNode.sizes[i] += 32 } } const newNode = cloneNode(currentNode.array[index]) // Install the copied node currentNode.array[index] = newNode currentNode = newNode } return currentNode } /** * Prepends an element to a node */ function nodePrepend(value: any, size: number, node: Node): Node { const array = arrayPrepend(value, node.array) let sizes = undefined if (node.sizes !== undefined) { sizes = new Array(node.sizes.length + 1) sizes[0] = size for (let i = 0; i < node.sizes.length; ++i) { sizes[i + 1] = node.sizes[i]! + size } } return new Node(sizes, array) } /** * Prepends a node to a tree. Either by shifting the nodes in the root * left or by increasing the height */ function prependTopTree(l: MutableList, depth: number, node: Node): number { let newOffset if (l.root!.array.length < branchingFactor) { // There is space in the root, there is never a size table in this // case newOffset = 32 ** depth - 32 l.root = new Node( undefined, arrayPrepend(createPath(depth - 1, node), l.root!.array) ) } else { // We need to create a new root l.bits = incrementDepth(l.bits) const sizes = l.root!.sizes === undefined ? undefined : [32, arrayLast(l.root!.sizes!) + 32] newOffset = depth === 0 ? 0 : 32 ** (depth + 1) - 32 l.root = new Node(sizes, [createPath(depth, node), l.root]) } return newOffset } /** * Takes a list and a node tail. It then prepends the node to the tree * of the list. * @param l The subject for prepending. `l` will be mutated. Nodes in * the tree will _not_ be mutated. * @param node The node that should be prepended to the tree. */ function prependNodeToTree(l: MutableList, array: A[]): List { if (l.root === undefined) { if (getSuffixSize(l) === 0) { // ensure invariant 1 l.bits = setSuffix(array.length, l.bits) l.suffix = array } else { l.root = new Node(undefined, array) } return l } else { const node = new Node(undefined, array) const depth = getDepth(l) let newOffset = 0 if (l.root.sizes === undefined) { if (l.offset !== 0) { newOffset = l.offset - branchingFactor l.root = prependDense(l.root, depth, l.offset, node) } else { // in this case we can be sure that the is not room in the tree // for the new node newOffset = prependTopTree(l, depth, node) } } else { // represents how many nodes _with size-tables_ that we should copy. let copyableCount = 0 // go down while there is size tables let nodesTraversed = 0 let currentNode = l.root while (currentNode.sizes !== undefined && nodesTraversed < depth) { ++nodesTraversed if (currentNode.array.length < 32) { // there is room if offset is > 0 or if the first node does not // contain as many nodes as it possibly can copyableCount = nodesTraversed } currentNode = currentNode.array[0] } if (l.offset !== 0) { const copiedNode = copyLeft(l, nodesTraversed) for (let i = 0; i < copiedNode.sizes!.length; ++i) { copiedNode.sizes![i] += branchingFactor } copiedNode.array[0] = prependDense( copiedNode.array[0], depth - nodesTraversed, l.offset, node ) l.offset = l.offset - branchingFactor return l } else { if (copyableCount === 0) { l.offset = prependTopTree(l, depth, node) } else { let parent: Node | undefined let prependableNode: Node // Copy the part of the path with size tables if (copyableCount > 1) { parent = copyLeft(l, copyableCount - 1) prependableNode = parent.array[0] } else { parent = undefined prependableNode = l.root! } const path = createPath(depth - copyableCount, node) // add offset l.offset = 32 ** (depth - copyableCount + 1) - 32 const prepended = nodePrepend(path, 32, prependableNode) if (parent === undefined) { l.root = prepended } else { parent.array[0] = prepended } } return l } } l.offset = newOffset return l } } /** * Prepends a node to a dense tree. The given `offset` is never zero. */ function prependDense(node: Node, depth: number, offset: number, value: Node): Node { // We're indexing down `offset - 1`. At each step `path` is either 0 or -1. const curOffset = (offset >> (depth * branchBits)) & mask const path = (((offset - 1) >> (depth * branchBits)) & mask) - curOffset if (path < 0) { return new Node(undefined, arrayPrepend(createPath(depth - 1, value), node.array)) } else { const array = copyArray(node.array) array[0] = prependDense(array[0], depth - 1, offset, value) return new Node(undefined, array) } } /** * Appends an element to the end of a list and returns the new list. * * @complexity O(n) */ export function append_(l: List, value: A): List { const suffixSize = getSuffixSize(l) if (suffixSize < 32) { return new List( incrementSuffix(l.bits), l.offset, l.length + 1, l.prefix, l.root, affixPush(value, l.suffix, suffixSize) ) } const newSuffix = [value] const newList = cloneList(l) appendNodeToTree(newList, l.suffix) newList.suffix = newSuffix newList.length++ newList.bits = setSuffix(1, newList.bits) return newList } /** * Appends an element to the end of a list and returns the new list. * * @complexity O(n) */ export function append(value: A): (l: List) => List { return (l) => append_(l, value) } /** * Gets the length of a list. * * @complexity `O(1)` */ export function size(l: List): number { return l.length } /** * Returns the first element of the list. If the list is empty the * function returns undefined. * * @complexity O(1) */ export function unsafeFirst(l: List): A | undefined { return O.toUndefined(first(l)) } /** * Returns the first element of the list. If the list is empty the * function returns undefined. * * @complexity O(1) */ export function first(l: List): O.Option { const prefixSize = getPrefixSize(l) return prefixSize !== 0 ? O.some(l.prefix[prefixSize - 1]!) : l.length !== 0 ? O.some(l.suffix[0]!) : O.none } /** * Returns the last element of the list. If the list is empty the * function returns `undefined`. * * @complexity O(1) */ export function unsafeLast(l: List): A | undefined { return O.toUndefined(last(l)) } /** * Returns the last element of the list. If the list is empty the * function returns `undefined`. * * @complexity O(1) */ export function last(l: List): O.Option { const suffixSize = getSuffixSize(l) return suffixSize !== 0 ? O.some(l.suffix[suffixSize - 1]!) : l.length !== 0 ? O.some(l.prefix[0]!) : O.none } // map function mapArray(f: (a: A) => B, array: A[]): B[] { const result = new Array(array.length) for (let i = 0; i < array.length; ++i) { result[i] = f(array[i]!) } return result } function mapNode(f: (a: A) => B, node: Node, depth: number): Node { if (depth !== 0) { const { array } = node const result = new Array(array.length) for (let i = 0; i < array.length; ++i) { result[i] = mapNode(f, array[i], depth - 1) } return new Node(node.sizes, result) } else { return new Node(undefined, mapArray(f, node.array)) } } function mapPrefix(f: (a: A) => B, prefix: A[], length: number): B[] { const newPrefix = new Array(length) for (let i = length - 1; 0 <= i; --i) { newPrefix[i] = f(prefix[i]!) } return newPrefix } function mapAffix(f: (a: A) => B, suffix: A[], length: number): B[] { const newSuffix = new Array(length) for (let i = 0; i < length; ++i) { newSuffix[i] = f(suffix[i]!) } return newSuffix } /** * Applies a function to each element in the given list and returns a * new list of the values that the function return. * * @complexity O(n) */ export function map_(l: List, f: (a: A) => B): List { return new List( l.bits, l.offset, l.length, mapPrefix(f, l.prefix, getPrefixSize(l)), l.root === undefined ? undefined : mapNode(f, l.root, getDepth(l)), mapAffix(f, l.suffix, getSuffixSize(l)) ) } /** * Applies a function to each element in the given list and returns a * new list of the values that the function return. * * @complexity O(n) */ export function map(f: (a: A) => B): (l: List) => List { return (l) => new List( l.bits, l.offset, l.length, mapPrefix(f, l.prefix, getPrefixSize(l)), l.root === undefined ? undefined : mapNode(f, l.root, getDepth(l)), mapAffix(f, l.suffix, getSuffixSize(l)) ) } /** * Extracts the specified property from each object in the list. */ export function pluck_(l: List, key: K): List { return map_(l, (a) => a[key]) } /** * Extracts the specified property from each object in the list. */ export function pluck(key: K): (l: List) => List { return (l) => pluck_(l, key) } // fold function foldlSuffix( f: (acc: B, value: A) => B, acc: B, array: A[], length: number ): B { for (let i = 0; i < length; ++i) { acc = f(acc, array[i]!) } return acc } function foldlPrefix( f: (acc: B, value: A) => B, acc: B, array: A[], length: number ): B { for (let i = length - 1; 0 <= i; --i) { acc = f(acc, array[i]!) } return acc } function foldlNode( f: (acc: B, value: A) => B, acc: B, node: Node, depth: number ): B { const { array } = node if (depth === 0) { return foldlSuffix(f, acc, array, array.length) } for (let i = 0; i < array.length; ++i) { acc = foldlNode(f, acc, array[i], depth - 1) } return acc } /** * Folds a function over a list. Left-associative. */ export function reduce_(l: List, initial: B, f: (acc: B, value: A) => B): B { const suffixSize = getSuffixSize(l) const prefixSize = getPrefixSize(l) initial = foldlPrefix(f, initial, l.prefix, prefixSize) if (l.root !== undefined) { initial = foldlNode(f, initial, l.root, getDepth(l)) } return foldlSuffix(f, initial, l.suffix, suffixSize) } /** * Folds a function over a list. Left-associative. */ export function reduce( initial: B, f: (acc: B, value: A) => B ): (l: List) => B { return (l) => reduce_(l, initial, f) } /** * Folds a function over a list from left to right while collecting * all the intermediate steps in a resulting list. */ export function scan_( l: List, initial: B, f: (acc: B, value: A) => B ): List { return reduce_(l, push_(emptyPushable(), initial), (l2, a) => push_(l2, f(unsafeLast(l2)!, a)) ) } /** * Folds a function over a list from left to right while collecting * all the intermediate steps in a resulting list. */ export function scan( initial: B, f: (acc: B, value: A) => B ): (l: List) => List { return (l) => scan_(l, initial, f) } /** * Invokes a given callback for each element in the list from left to * right. Returns `undefined`. * * This function is very similar to map. It should be used instead of * `map` when the mapping function has side-effects. Whereas `map` * constructs a new list `forEach` merely returns `undefined`. This * makes `forEach` faster when the new list is unneeded. * * @complexity O(n) */ export function forEach_(l: List, callback: (a: A) => void): void { reduce_(l, undefined as void, (_, element) => callback(element)) } /** * Invokes a given callback for each element in the list from left to * right. Returns `undefined`. * * This function is very similar to map. It should be used instead of * `map` when the mapping function has side-effects. Whereas `map` * constructs a new list `forEach` merely returns `undefined`. This * makes `forEach` faster when the new list is unneeded. * * @complexity O(n) */ export function forEach(callback: (a: A) => void): (l: List) => void { return (l) => forEach_(l, callback) } /** * Returns a new list that only contains the elements of the original * list for which the predicate returns `true`. * * @complexity O(n) */ export function filter_( l: List, predicate: (a: A) => a is B ): List export function filter_(l: List, predicate: (a: A) => boolean): List export function filter_(l: List, predicate: (a: A) => boolean): List { return reduce_(l, emptyPushable(), (acc, a) => (predicate(a) ? push_(acc, a) : acc)) } /** * Returns a new list that only contains the elements of the original * list for which the predicate returns `true`. * * @complexity O(n) */ export function filter( predicate: (a: A) => a is B ): (l: List) => List export function filter(predicate: (a: A) => boolean): (l: List) => List export function filter(predicate: (a: A) => boolean): (l: List) => List { return (l) => reduce_(l, emptyPushable(), (acc, a) => (predicate(a) ? push_(acc, a) : acc)) } /** * Returns a new list that only contains the elements of the original * list for which the f returns `Some`. * * @complexity O(n) */ export function filterMap_(l: List, f: (a: A) => O.Option): List { return reduce_(l, emptyPushable(), (acc, a) => { const fa = f(a) if (fa._tag === "Some") { push_(acc, fa.value) } return acc }) } /** * Returns a new list that only contains the elements of the original * list for which the f returns `Some`. * * @complexity O(n) */ export function filterMap(f: (a: A) => O.Option): (l: List) => List { return (l) => filterMap_(l, f) } /** * Filter out optional values */ export function compact(fa: List>): List { return filterMap((x: O.Option) => x)(fa) } /** * Returns a new list that only contains the elements of the original * list for which the predicate returns `false`. * * @complexity O(n) */ export function filterNot_(l: List, predicate: (a: A) => boolean): List { return reduce_(l, emptyPushable(), (acc, a) => (predicate(a) ? acc : push_(acc, a))) } /** * Returns a new list that only contains the elements of the original * list for which the predicate returns `false`. * * @complexity O(n) */ export function filterNot(predicate: (a: A) => boolean): (l: List) => List { return (l) => filterNot_(l, predicate) } /** * Splits the list into two lists. One list that contains all the * values for which the predicate returns `true` and one containing * the values for which it returns `false`. * * @complexity O(n) */ export function partition_( l: List, predicate: (a: A) => a is B ): Tp.Tuple<[List, List>]> export function partition_( l: List, predicate: (a: A) => boolean ): Tp.Tuple<[List, List]> export function partition_( l: List, predicate: (a: A) => boolean ): Tp.Tuple<[List, List]> { return reduce_( l, Tp.tuple(emptyPushable(), emptyPushable()) as Tp.Tuple< [MutableList, MutableList] >, (arr, a) => (predicate(a) ? push_(arr.get(0), a) : push_(arr.get(1), a), arr) ) } /** * Splits the list into two lists. One list that contains all the * values for which the predicate returns `true` and one containing * the values for which it returns `false`. * * @complexity O(n) */ export function partition( predicate: (a: A) => a is B ): (l: List) => Tp.Tuple<[List, List>]> export function partition( predicate: (a: A) => boolean ): (l: List) => Tp.Tuple<[List, List]> export function partition( predicate: (a: A) => boolean ): (l: List) => Tp.Tuple<[List, List]> { return (l) => partition_(l, predicate) } /** * Splits the list into two lists. One list that contains the lefts * and one contains the rights * * @complexity O(n) */ export function partitionMap_( l: List, f: (_: A) => Either ): Tp.Tuple<[List, List]> { return reduce_( l, Tp.tuple(emptyPushable(), emptyPushable()) as Tp.Tuple< [MutableList, MutableList] >, (arr, a) => { const fa = f(a) if (fa._tag === "Left") { push_(arr.get(0), fa.left) } else { push_(arr.get(1), fa.right) } return arr } ) } /** * Splits the list into two lists. One list that contains the lefts * and one contains the rights * * @complexity O(n) */ export function partitionMap( f: (_: A) => Either ): (l: List) => Tp.Tuple<[List, List]> { return (l) => partitionMap_(l, f) } /** * Splits the list into two lists. One list that contains the lefts * and one contains the rights * * @complexity O(n) */ export function separate(l: List>): Tp.Tuple<[List, List]> { return partitionMap_(l, identity) } /** * Concats the strings in the list separated by a specified separator. */ export function join_(l: List, separator: string): string { return reduce_(l, "", (a, b) => (a.length === 0 ? b : a + separator + b)) } /** * Concats the strings in the list separated by a specified separator. */ export function join(separator: string): (l: List) => string { return (l) => join_(l, separator) } function foldrSuffix( f: (value: A, acc: B) => B, initial: B, array: A[], length: number ): B { let acc = initial for (let i = length - 1; 0 <= i; --i) { acc = f(array[i]!, acc) } return acc } function foldrPrefix( f: (value: A, acc: B) => B, initial: B, array: A[], length: number ): B { let acc = initial for (let i = 0; i < length; ++i) { acc = f(array[i]!, acc) } return acc } function foldrNode( f: (value: A, acc: B) => B, initial: B, { array }: Node, depth: number ): B { if (depth === 0) { return foldrSuffix(f, initial, array, array.length) } let acc = initial for (let i = array.length - 1; 0 <= i; --i) { acc = foldrNode(f, acc, array[i], depth - 1) } return acc } /** * Folds a function over a list. Right-associative. * * @complexity O(n) */ export function reduceRight_( l: List, initial: B, f: (value: A, acc: B) => B ): B { const suffixSize = getSuffixSize(l) const prefixSize = getPrefixSize(l) let acc = foldrSuffix(f, initial, l.suffix, suffixSize) if (l.root !== undefined) { acc = foldrNode(f, acc, l.root, getDepth(l)) } return foldrPrefix(f, acc, l.prefix, prefixSize) } /** * Folds a function over a list. Right-associative. * * @complexity O(n) */ export function reduceRight( initial: B, f: (value: A, acc: B) => B ): (l: List) => B { return (l) => reduceRight_(l, initial, f) } /** * Applies a list of functions to a list of values. */ export function ap_(listF: List<(a: A) => B>, l: List): List { return flatten(map_(listF, (f) => map_(l, f))) } /** * Applies a list of functions to a list of values. */ export function ap(l: List): (listF: List<(a: A) => B>) => List { return (listF) => ap_(listF, l) } /** * Flattens a list of lists into a list. Note that this function does * not flatten recursively. It removes one level of nesting only. * * @complexity O(n * log(m)), where n is the length of the outer list and m the length of the inner lists. */ export function flatten(nested: List>): List { return reduce_, List>(nested, empty(), concat_) } /** * Maps a function over a list and concatenates all the resulting * lists together. */ export function chain_(l: List, f: (a: A) => List): List { return flatten(map_(l, f)) } /** * Maps a function over a list and concatenates all the resulting * lists together. */ export function chain(f: (a: A) => List): (l: List) => List { return (l) => chain_(l, f) } // callback fold type FoldCb = (input: Input, state: State) => boolean function foldlArrayCb( cb: FoldCb, state: B, array: A[], from: number, to: number ): boolean { for (var i = from; i < to && cb(array[i]!, state); ++i) { // } return i === to } function foldrArrayCb( cb: FoldCb, state: B, array: A[], from: number, to: number ): boolean { for (var i = from - 1; to <= i && cb(array[i]!, state); --i) { // } return i === to - 1 } function foldlNodeCb( cb: FoldCb, state: B, node: Node, depth: number ): boolean { const { array } = node if (depth === 0) { return foldlArrayCb(cb, state, array, 0, array.length) } const to = array.length for (let i = 0; i < to; ++i) { if (!foldlNodeCb(cb, state, array[i], depth - 1)) { return false } } return true } /** * This function is a lot like a fold. But the reducer function is * supposed to mutate its state instead of returning it. Instead of * returning a new state it returns a boolean that tells wether or not * to continue the fold. `true` indicates that the folding should * continue. */ function foldlCb(cb: FoldCb, state: B, l: List): B { const prefixSize = getPrefixSize(l) if ( !foldrArrayCb(cb, state, l.prefix, prefixSize, 0) || (l.root !== undefined && !foldlNodeCb(cb, state, l.root, getDepth(l))) ) { return state } const suffixSize = getSuffixSize(l) foldlArrayCb(cb, state, l.suffix, 0, suffixSize) return state } function foldrNodeCb( cb: FoldCb, state: B, node: Node, depth: number ): boolean { const { array } = node if (depth === 0) { return foldrArrayCb(cb, state, array, array.length, 0) } for (let i = array.length - 1; 0 <= i; --i) { if (!foldrNodeCb(cb, state, array[i], depth - 1)) { return false } } return true } function foldrCb(cb: FoldCb, state: B, l: List): B { const suffixSize = getSuffixSize(l) const prefixSize = getPrefixSize(l) if ( !foldrArrayCb(cb, state, l.suffix, suffixSize, 0) || (l.root !== undefined && !foldrNodeCb(cb, state, l.root, getDepth(l))) ) { return state } const prefix = l.prefix foldlArrayCb(cb, state, l.prefix, prefix.length - prefixSize, prefix.length) return state } // functions based on foldlCb type FoldlWhileState = { predicate: (b: B, a: A) => boolean result: B f: (acc: B, value: A) => B } /** * Similar to `foldl`. But, for each element it calls the predicate function * _before_ the folding function and stops folding if it returns `false`. * * @category Folds * @example * const isOdd = (_acc:, x) => x % 2 === 1; * * const xs = L.list(1, 3, 5, 60, 777, 800); * foldlWhile(isOdd, (n, m) => n + m, 0, xs) //=> 9 * * const ys = L.list(2, 4, 6); * foldlWhile(isOdd, (n, m) => n + m, 111, ys) //=> 111 */ function foldlWhileCb(a: A, state: FoldlWhileState): boolean { if (state.predicate(state.result, a) === false) { return false } state.result = state.f(state.result, a) return true } export function reduceWhile_( l: List, initial: B, predicate: (acc: B, value: A) => boolean, f: (acc: B, value: A) => B ): B { return foldlCb>( foldlWhileCb, { predicate, f, result: initial }, l ).result } export function reduceWhile( initial: B, predicate: (acc: B, value: A) => boolean, f: (acc: B, value: A) => B ): (l: List) => B { return (l) => reduceWhile_(l, initial, predicate, f) } type PredState = { predicate: (a: any) => boolean result: any } function everyCb(value: A, state: any): boolean { return (state.result = state.predicate(value)) } /** * Returns `true` if and only if the predicate function returns `true` * for all elements in the given list. * * @complexity O(n) */ export function every_(l: List, predicate: (a: A) => boolean): boolean { return foldlCb(everyCb, { predicate, result: true }, l).result } /** * Returns `true` if and only if the predicate function returns `true` * for all elements in the given list. * * @complexity O(n) */ export function every(predicate: (a: A) => boolean): (l: List) => boolean { return (l) => every_(l, predicate) } function someCb(value: A, state: any): boolean { return !(state.result = state.predicate(value)) } /** * Returns true if and only if there exists an element in the list for * which the predicate returns true. * * @complexity O(n) */ export function some_(l: List, predicate: (a: A) => boolean): boolean { return foldlCb(someCb, { predicate, result: false }, l).result } /** * Returns true if and only if there exists an element in the list for * which the predicate returns true. * * @complexity O(n) */ export function some(predicate: (a: A) => boolean): (l: List) => boolean { return (l) => some_(l, predicate) } /** * Returns `true` if and only if the predicate function returns * `false` for every element in the given list. * * @complexity O(n) */ export function none_(l: List, predicate: (a: A) => boolean): boolean { return !some_(l, predicate) } /** * Returns `true` if and only if the predicate function returns * `false` for every element in the given list. * * @complexity O(n) */ export function none(predicate: (a: A) => boolean): (l: List) => boolean { return (l) => none_(l, predicate) } function findCb(value: A, state: PredState): boolean { if (state.predicate(value)) { state.result = O.some(value) return false } else { return true } } /** * Returns the _first_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function unsafeFind_( l: List, predicate: (a: A) => boolean ): A | undefined { return O.toUndefined(find_(l, predicate)) } /** * Returns the _first_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function unsafeFind( predicate: (a: A) => boolean ): (l: List) => A | undefined { return (l) => unsafeFind_(l, predicate) } /** * Returns the _first_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function find_(l: List, predicate: (a: A) => boolean): O.Option { return foldlCb(findCb, { predicate, result: O.none }, l).result } /** * Returns the _first_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function find(predicate: (a: A) => boolean) { return (l: List) => find_(l, predicate) } /** * Returns the _last_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function unsafeFindLast_( l: List, predicate: (a: A) => boolean ): A | undefined { return O.toUndefined(findLast_(l, predicate)) } /** * Returns the _last_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function unsafeFindLast( predicate: (a: A) => boolean ): (l: List) => A | undefined { return (l) => unsafeFindLast_(l, predicate) } /** * Returns the _last_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function findLast_(l: List, predicate: (a: A) => boolean): O.Option { return foldrCb(findCb, { predicate, result: O.none }, l).result } /** * Returns the _last_ element for which the predicate returns `true`. * If no such element is found the function returns `undefined`. * * @complexity O(n) */ export function findLast(predicate: (a: A) => boolean): (l: List) => O.Option { return (l) => findLast_(l, predicate) } type IndexOfState = { element: any found: boolean index: number } function indexOfCb(value: any, state: IndexOfState): boolean { ++state.index return !(state.found = elementEquals(value, state.element)) } /** * Returns the index of the _first_ element in the list that is equal * to the given element. If no such element is found `-1` is returned. * * @complexity O(n) */ export function indexOf_(l: List, element: A): number { const state = { element, found: false, index: -1 } foldlCb(indexOfCb, state, l) return state.found ? state.index : -1 } /** * Returns the index of the _first_ element in the list that is equal * to the given element. If no such element is found `-1` is returned. * * @complexity O(n) */ export function indexOf(element: A): (l: List) => number { return (l) => indexOf_(l, element) } /** * Returns the index of the _last_ element in the list that is equal * to the given element. If no such element is found `-1` is returned. * * @complexity O(n) */ export function lastIndexOf_(l: List, element: A): number { const state = { element, found: false, index: 0 } foldrCb(indexOfCb, state, l) return state.found ? l.length - state.index : -1 } /** * Returns the index of the _last_ element in the list that is equal * to the given element. If no such element is found `-1` is returned. * * @complexity O(n) */ export function lastIndexOf(element: A): (l: List) => number { return (l) => lastIndexOf_(l, element) } type FindIndexState = { predicate: (a: any) => boolean found: boolean index: number } function findIndexCb(value: A, state: FindIndexState): boolean { ++state.index return !(state.found = state.predicate(value)) } /** * Returns the index of the `first` element for which the predicate * returns true. If no such element is found the function returns * `-1`. * * @complexity O(n) */ export function findIndex_(l: List, predicate: (a: A) => boolean): number { const { found, index } = foldlCb( findIndexCb, { predicate, found: false, index: -1 }, l ) return found ? index : -1 } /** * Returns the index of the `first` element for which the predicate * returns true. If no such element is found the function returns * `-1`. * * @complexity O(n) */ export function findIndex(predicate: (a: A) => boolean): (l: List) => number { return (l) => findIndex_(l, predicate) } type ContainsState = { element: any result: boolean } const containsState: ContainsState = { element: undefined, result: false } function containsCb(value: any, state: ContainsState): boolean { return !(state.result = value === state.element) } /** * Returns `true` if the list contains the specified element. * Otherwise it returns `false`. * * @complexity O(n) */ export function contains_(l: List, element: A): boolean { containsState.element = element containsState.result = false return foldlCb(containsCb, containsState, l).result } /** * Returns `true` if the list contains the specified element. * Otherwise it returns `false`. * * @complexity O(n) */ export function contains(element: A): (l: List) => boolean { return (l) => contains_(l, element) } type EqualsState = { iterator: Iterator f: (a: A, b: A) => boolean equals: boolean } function equalsCb(value2: A, state: EqualsState): boolean { const { value } = state.iterator.next() return (state.equals = state.f(value, value2)) } /** * Returns true if the two lists are equivalent. * * @complexity O(n) */ export function equals_(l1: List, l2: List): boolean { return equalsWith_(l1, l2, elementEquals) } /** * Returns true if the two lists are equivalent. * * @complexity O(n) */ export function equals(l2: List): (l1: List) => boolean { return (l1) => equals_(l1, l2) } /** * Returns true if the two lists are equivalent when comparing each * pair of elements with the given comparison function. * * @complexity O(n) */ export function equalsWith_( l1: List, l2: List, f: (a: A, b: A) => boolean ): boolean { if (l1 === l2) { return true } else if (l1.length !== l2.length) { return false } else { const s = { iterator: l2[Symbol.iterator](), equals: true, f } return foldlCb>(equalsCb, s, l1).equals } } /** * Returns true if the two lists are equivalent when comparing each * pair of elements with the given comparison function. * * @complexity O(n) */ export function equalsWith( l2: List, f: (a: A, b: A) => boolean ): (l1: List) => boolean { return (l1) => equalsWith_(l1, l2, f) } // concat const eMax = 2 function createConcatPlan(array: Node[]): number[] | undefined { const sizes = [] let sum = 0 for (let i = 0; i < array.length; ++i) { sum += array[i]!.array.length // FIXME: maybe only access array once sizes[i] = array[i]!.array.length } const optimalLength = Math.ceil(sum / branchingFactor) let n = array.length let i = 0 if (optimalLength + eMax >= n) { return undefined // no rebalancing needed } while (optimalLength + eMax < n) { while (sizes[i]! > branchingFactor - eMax / 2) { // Skip nodes that are already sufficiently balanced ++i } // the node at this index is too short let remaining = sizes[i]! // number of elements to re-distribute do { const size = Math.min(remaining + sizes[i + 1]!, branchingFactor) sizes[i] = size remaining = remaining - (size - sizes[i + 1]!) ++i } while (remaining > 0) // Shift nodes after for (let j = i; j <= n - 1; ++j) { sizes[j] = sizes[j + 1]! } --i --n } sizes.length = n return sizes } /** * Combines the children of three nodes into an array. The last child * of `left` and the first child of `right is ignored as they've been * concatenated into `center`. */ function concatNodeMerge( left: Node | undefined, center: Node, right: Node | undefined ): Node[] { const array = [] if (left !== undefined) { for (let i = 0; i < left.array.length - 1; ++i) { array.push(left.array[i]) } } for (let i = 0; i < center.array.length; ++i) { array.push(center.array[i]) } if (right !== undefined) { for (let i = 1; i < right.array.length; ++i) { array.push(right.array[i]) } } return array } function executeConcatPlan(merged: Node[], plan: number[], height: number): any[] { const result = [] let sourceIdx = 0 // the current node we're copying from let offset = 0 // elements in source already used for (let toMove of plan) { let source = merged[sourceIdx]!.array if (toMove === source.length && offset === 0) { // source matches target exactly, reuse source result.push(merged[sourceIdx]) ++sourceIdx } else { const node = new Node(undefined, []) while (toMove > 0) { const available = source.length - offset const itemsToCopy = Math.min(toMove, available) pushElements(source, node.array, offset, itemsToCopy) if (toMove >= available) { ++sourceIdx source = merged[sourceIdx]!.array offset = 0 } else { offset += itemsToCopy } toMove -= itemsToCopy } if (height > 1) { // Set sizes on children unless they are leaf nodes setSizes(node, height - 1) } result.push(node) } } return result } /** * Takes three nodes and returns a new node with the content of the * three nodes. Note: The returned node does not have its size table * set correctly. The caller must do that. */ function rebalance( left: Node | undefined, center: Node, right: Node | undefined, height: number, top: boolean ): Node { const merged = concatNodeMerge(left, center, right) const plan = createConcatPlan(merged) const balanced = plan !== undefined ? executeConcatPlan(merged, plan, height) : merged if (balanced.length <= branchingFactor) { if (top === true) { return new Node(undefined, balanced) } else { // Return a single node with extra height for balancing at next // level return new Node(undefined, [setSizes(new Node(undefined, balanced), height)]) } } else { return new Node(undefined, [ setSizes(new Node(undefined, balanced.slice(0, branchingFactor)), height), setSizes(new Node(undefined, balanced.slice(branchingFactor)), height) ]) } } function concatSubTree( left: Node, lDepth: number, right: Node, rDepth: number, isTop: boolean ): Node { if (lDepth > rDepth) { const c = concatSubTree(arrayLast(left.array), lDepth - 1, right, rDepth, false) return rebalance(left, c, undefined, lDepth, isTop) } else if (lDepth < rDepth) { const c = concatSubTree(left, lDepth, arrayFirst(right.array), rDepth - 1, false) return rebalance(undefined, c, right, rDepth, isTop) } else if (lDepth === 0) { return new Node(undefined, [left, right]) } else { const c = concatSubTree( arrayLast(left.array), lDepth - 1, arrayFirst(right.array), rDepth - 1, false ) return rebalance(left, c, right, lDepth, isTop) } } function getHeight(node: Node): number { if (node.array[0] instanceof Node) { return 1 + getHeight(node.array[0]) } else { return 0 } } /** * Takes a RRB-tree and an affix. It then appends the node to the * tree. * @param l The subject for appending. `l` will be mutated. Nodes in * the tree will _not_ be mutated. * @param array The affix that should be appended to the tree. */ function appendNodeToTree(l: MutableList, array: A[]): MutableList { if (l.root === undefined) { // The old list has no content in tree, all content is in affixes if (getPrefixSize(l) === 0) { l.bits = setPrefix(array.length, l.bits) l.prefix = reverseArray(array) } else { l.root = new Node(undefined, array) } return l } const depth = getDepth(l) let index = handleOffset(depth, l.offset, l.length - 1 - getPrefixSize(l)) let nodesToCopy = 0 let nodesVisited = 0 let shift = depth * 5 let currentNode = l.root if (32 ** (depth + 1) < index) { shift = 0 // there is no room nodesVisited = depth } while (shift > 5) { let childIndex: number if (currentNode.sizes === undefined) { // does not have size table childIndex = (index >> shift) & mask index &= ~(mask << shift) // wipe just used bits } else { childIndex = currentNode.array.length - 1 index -= currentNode.sizes[childIndex - 1]! } nodesVisited++ if (childIndex < mask) { // we are not going down the far right path, this implies that // there is still room in the current node nodesToCopy = nodesVisited } currentNode = currentNode.array[childIndex] if (currentNode === undefined) { // This will only happened in a pvec subtree. The index does not // exist so we'll have to create a new path from here on. nodesToCopy = nodesVisited shift = 5 // Set shift to break out of the while-loop } shift -= 5 } if (shift !== 0) { nodesVisited++ if (currentNode.array.length < branchingFactor) { // there is room in the found node nodesToCopy = nodesVisited } } const node = new Node(undefined, array) if (nodesToCopy === 0) { // there was no room in the found node const newPath = nodesVisited === 0 ? node : createPath(nodesVisited, node) const newRoot = new Node(undefined, [l.root, newPath]) l.root = newRoot l.bits = incrementDepth(l.bits) } else { const copiedNode = copyFirstK(l, nodesToCopy, array.length) copiedNode.array.push(createPath(depth - nodesToCopy, node)) } return l } /** * Traverses down the right edge of the tree and copies k nodes. * @param oldList * @param newList * @param k The number of nodes to copy. Will always be at least 1. * @param leafSize The number of elements in the leaf that will be inserted. */ function copyFirstK(newList: MutableList, k: number, leafSize: number): Node { let currentNode = cloneNode(newList.root!) // copy root newList.root = currentNode // install root for (let i = 1; i < k; ++i) { const index = currentNode.array.length - 1 if (currentNode.sizes !== undefined) { currentNode.sizes[index] += leafSize } const newNode = cloneNode(currentNode.array[index]) // Install the copied node currentNode.array[index] = newNode currentNode = newNode } if (currentNode.sizes !== undefined) { currentNode.sizes.push(arrayLast(currentNode.sizes) + leafSize) } return currentNode } const concatBuffer = new Array(3) function concatAffixes(left: List, right: List): number { // TODO: Try and find a neat way to reduce the LOC here let nr = 0 let arrIdx = 0 let i = 0 let length = getSuffixSize(left) concatBuffer[nr] = [] for (i = 0; i < length; ++i) { concatBuffer[nr][arrIdx++] = left.suffix[i] } length = getPrefixSize(right) for (i = 0; i < length; ++i) { if (arrIdx === 32) { arrIdx = 0 ++nr concatBuffer[nr] = [] } concatBuffer[nr][arrIdx++] = right.prefix[length - 1 - i] } length = getSuffixSize(right) for (i = 0; i < length; ++i) { if (arrIdx === 32) { arrIdx = 0 ++nr concatBuffer[nr] = [] } concatBuffer[nr][arrIdx++] = right.suffix[i] } return nr } /** * Concatenates two lists. * * @complexity O(log(n)) */ export function concat_(left: List, right: List): List { if (left.length === 0) { return right } else if (right.length === 0) { return left } const newSize = left.length + right.length const rightSuffixSize = getSuffixSize(right) let newList = cloneList(left) if (right.root === undefined) { // right is nothing but a prefix and a suffix const nrOfAffixes = concatAffixes(left, right) for (let i = 0; i < nrOfAffixes; ++i) { newList = appendNodeToTree(newList, concatBuffer[i]) newList.length += concatBuffer[i].length // wipe pointer, otherwise it might end up keeping the array alive concatBuffer[i] = undefined } newList.length = newSize newList.suffix = concatBuffer[nrOfAffixes] newList.bits = setSuffix(concatBuffer[nrOfAffixes].length, newList.bits) concatBuffer[nrOfAffixes] = undefined return newList } else { const leftSuffixSize = getSuffixSize(left) if (leftSuffixSize > 0) { newList = appendNodeToTree(newList, left.suffix.slice(0, leftSuffixSize)) newList.length += leftSuffixSize } newList = appendNodeToTree( newList, right.prefix.slice(0, getPrefixSize(right)).reverse() ) const newNode = concatSubTree( newList.root!, getDepth(newList), right.root, getDepth(right), true ) const newDepth = getHeight(newNode) setSizes(newNode, newDepth) newList.root = newNode newList.offset &= ~(mask << (getDepth(left) * branchBits)) newList.length = newSize newList.bits = setSuffix(rightSuffixSize, setDepth(newDepth, newList.bits)) newList.suffix = right.suffix return newList } } /** * Concatenates two lists. * * @complexity O(log(n)) */ export function concat(right: List): (left: List) => List { return (left) => concat_(left, right) } /** * Returns a list that has the entry specified by the index replaced with the given value. * * If the index is out of bounds the given list is returned unchanged. * * @complexity O(log(n)) */ export function update_(l: List, index: number, a: A): List { if (index < 0 || l.length <= index) { return l } const prefixSize = getPrefixSize(l) const suffixSize = getSuffixSize(l) const newList = cloneList(l) if (index < prefixSize) { const newPrefix = copyArray(newList.prefix) newPrefix[newPrefix.length - index - 1] = a newList.prefix = newPrefix } else if (index >= l.length - suffixSize) { const newSuffix = copyArray(newList.suffix) newSuffix[index - (l.length - suffixSize)] = a newList.suffix = newSuffix } else { newList.root = updateNode(l.root!, getDepth(l), index - prefixSize, l.offset, a) } return newList } /** * Returns a list that has the entry specified by the index replaced with the given value. * * If the index is out of bounds the given list is returned unchanged. * * @complexity O(log(n)) */ export function update(index: number, a: A): (l: List) => List { return (l) => update_(l, index, a) } /** * Returns a list that has the entry specified by the index replaced with * the value returned by applying the function to the value. * * If the index is out of bounds the given list is * returned unchanged. * * @complexity `O(log(n))` */ export function adjust_(l: List, index: number, f: (a: A) => A): List { if (index < 0 || l.length <= index) { return l } return update_(l, index, f(unsafeNth_(l, index)!)) } /** * Returns a list that has the entry specified by the index replaced with * the value returned by applying the function to the value. * * If the index is out of bounds the given list is * returned unchanged. * * @complexity `O(log(n))` */ export function adjust(index: number, f: (a: A) => A): (l: List) => List { return (l) => adjust_(l, index, f) } // slice and slice based functions let newAffix: any[] // function getBitsForDepth(n: number, depth: number): number { // return n & ~(~0 << ((depth + 1) * branchBits)); // } function sliceNode( node: Node, index: number, depth: number, pathLeft: number, pathRight: number, childLeft: Node | undefined, childRight: Node | undefined ): Node { const array = node.array.slice(pathLeft, pathRight + 1) if (childLeft !== undefined) { array[0] = childLeft } if (childRight !== undefined) { array[array.length - 1] = childRight } let sizes = node.sizes if (sizes !== undefined) { sizes = sizes.slice(pathLeft, pathRight + 1) let slicedOffLeft = pathLeft !== 0 ? node.sizes![pathLeft - 1]! : 0 if (childLeft !== undefined) { // If the left child has been sliced into a new child we need to know // how many elements have been removed from the child. if (childLeft.sizes !== undefined) { // If the left child has a size table we can simply look at that. const oldChild: Node = node.array[pathLeft] slicedOffLeft += arrayLast(oldChild.sizes!) - arrayLast(childLeft.sizes) } else { // If the left child does not have a size table we can // calculate how many elements have been removed from it by // looking at the index. Note that when we slice into a leaf // the leaf is moved up as a prefix. Thus slicing, for // instance, at index 20 will remove 32 elements from the // child. Similarly slicing at index 50 will remove 64 // elements at slicing at 64 will remove 92 elements. slicedOffLeft += ((index - slicedOffLeft) & ~0b011111) + 32 } } for (let i = 0; i < sizes.length; ++i) { sizes[i] -= slicedOffLeft } if (childRight !== undefined) { const slicedOffRight = sizeOfSubtree(node.array[pathRight], depth - 1) - sizeOfSubtree(childRight, depth - 1) sizes[sizes.length - 1] -= slicedOffRight } } return new Node(sizes, array) } let newOffset = 0 function sliceLeft( tree: Node, depth: number, index: number, offset: number, top: boolean ): Node | undefined { let { index: newIndex, path, updatedOffset } = getPath(index, offset, depth, tree.sizes) if (depth === 0) { newAffix = tree.array.slice(path).reverse() // This leaf node is moved up as a suffix so there is nothing here // after slicing return undefined } else { const child = sliceLeft(tree.array[path], depth - 1, newIndex, updatedOffset, false) if (child === undefined) { // There is nothing in the child after slicing so we don't include it ++path if (path === tree.array.length) { return undefined } } // If we've sliced something away and it's not a the root, update offset if (tree.sizes === undefined && top === false) { newOffset |= (32 - (tree.array.length - path)) << (depth * branchBits) } return sliceNode(tree, index, depth, path, tree.array.length - 1, child, undefined) } } /** Slice elements off of a tree from the right */ function sliceRight( node: Node, depth: number, index: number, offset: number ): Node | undefined { let { index: newIndex, path } = getPath(index, offset, depth, node.sizes) if (depth === 0) { newAffix = node.array.slice(0, path + 1) // this leaf node is moved up as a suffix so there is nothing here // after slicing return undefined } else { // slice the child, note that we subtract 1 then the radix lookup // algorithm can find the last element that we want to include // and sliceRight will do a slice that is inclusive on the index. const child = sliceRight( node.array[path], depth - 1, newIndex, path === 0 ? offset : 0 ) if (child === undefined) { // there is nothing in the child after slicing so we don't include it --path if (path === -1) { return undefined } } // note that we add 1 to the path since we want the slice to be // inclusive on the end index. Only at the leaf level do we want // to do an exclusive slice. const array = node.array.slice(0, path + 1) if (child !== undefined) { array[array.length - 1] = child } let sizes: Sizes | undefined = node.sizes if (sizes !== undefined) { sizes = sizes.slice(0, path + 1) if (child !== undefined) { const slicedOff = sizeOfSubtree(node.array[path], depth - 1) - sizeOfSubtree(child, depth - 1) sizes[sizes.length - 1] -= slicedOff } } return new Node(sizes, array) } } function sliceTreeList( from: number, to: number, tree: Node, depth: number, offset: number, l: MutableList ): List { const sizes = tree.sizes let { index: newFrom, path: pathLeft } = getPath(from, offset, depth, sizes) let { index: newTo, path: pathRight } = getPath(to, offset, depth, sizes) if (depth === 0) { // we are slicing a piece off a leaf node l.prefix = emptyAffix l.suffix = tree.array.slice(pathLeft, pathRight + 1) l.root = undefined l.bits = setSuffix(pathRight - pathLeft + 1, 0) return l } else if (pathLeft === pathRight) { // Both ends are located in the same subtree, this means that we // can reduce the height l.bits = decrementDepth(l.bits) return sliceTreeList( newFrom, newTo, tree.array[pathLeft], depth - 1, pathLeft === 0 ? offset : 0, l ) } else { const childRight = sliceRight(tree.array[pathRight], depth - 1, newTo, 0) l.bits = setSuffix(newAffix.length, l.bits) l.suffix = newAffix if (childRight === undefined) { --pathRight } newOffset = 0 const childLeft = sliceLeft( tree.array[pathLeft], depth - 1, newFrom, pathLeft === 0 ? offset : 0, pathLeft === pathRight ) l.offset = newOffset l.bits = setPrefix(newAffix.length, l.bits) l.prefix = newAffix if (childLeft === undefined) { ++pathLeft } if (pathLeft >= pathRight) { if (pathLeft > pathRight) { // This only happens when `pathLeft` originally was equal to // `pathRight + 1` and `childLeft === childRight === undefined`. // In this case there is no tree left. l.bits = setDepth(0, l.bits) l.root = undefined } else { // Height can be reduced l.bits = decrementDepth(l.bits) const newRoot = childRight !== undefined ? childRight : childLeft !== undefined ? childLeft : tree.array[pathLeft] l.root = new Node(newRoot.sizes, newRoot.array) // Is this size handling good enough? } } else { l.root = sliceNode(tree, from, depth, pathLeft, pathRight, childLeft, childRight) } return l } } /** * Returns a slice of a list. Elements are removed from the beginning and * end. Both the indices can be negative in which case they will count * from the right end of the list. * * @complexity `O(log(n))` */ export function slice_(l: List, from: number, to: number): List { let { bits, length } = l to = Math.min(length, to) // Handle negative indices if (from < 0) { from = length + from } if (to < 0) { to = length + to } // Should we just return the empty list? if (to <= from || to <= 0 || length <= from) { return empty() } // Return list unchanged if we are slicing nothing off if (from <= 0 && length <= to) { return l } const newLength = to - from let prefixSize = getPrefixSize(l) const suffixSize = getSuffixSize(l) // Both indices lie in the prefix if (to <= prefixSize) { return new List( setPrefix(newLength, 0), 0, newLength, l.prefix.slice(prefixSize - to, prefixSize - from), undefined, emptyAffix ) } const suffixStart = length - suffixSize // Both indices lie in the suffix if (suffixStart <= from) { return new List( setSuffix(newLength, 0), 0, newLength, emptyAffix, undefined, l.suffix.slice(from - suffixStart, to - suffixStart) ) } const newList = cloneList(l) newList.length = newLength // Both indices lie in the tree if (prefixSize <= from && to <= suffixStart) { sliceTreeList( from - prefixSize + l.offset, to - prefixSize + l.offset - 1, l.root!, getDepth(l), l.offset, newList ) return newList } if (0 < from) { // we need to slice something off of the left if (from < prefixSize) { // shorten the prefix even though it's not strictly needed, // so that referenced items can be GC'd newList.prefix = l.prefix.slice(0, prefixSize - from) bits = setPrefix(prefixSize - from, bits) } else { // if we're here `to` can't lie in the tree, so we can set the // root newOffset = 0 newList.root = sliceLeft( newList.root!, getDepth(l), from - prefixSize, l.offset, true ) newList.offset = newOffset if (newList.root === undefined) { bits = setDepth(0, bits) } bits = setPrefix(newAffix.length, bits) prefixSize = newAffix.length newList.prefix = newAffix } } if (to < length) { // we need to slice something off of the right if (length - to < suffixSize) { bits = setSuffix(suffixSize - (length - to), bits) // slice the suffix even though it's not strictly needed, // to allow the removed items to be GC'd newList.suffix = l.suffix.slice(0, suffixSize - (length - to)) } else { newList.root = sliceRight( newList.root!, getDepth(l), to - prefixSize - 1, newList.offset ) if (newList.root === undefined) { bits = setDepth(0, bits) newList.offset = 0 } bits = setSuffix(newAffix.length, bits) newList.suffix = newAffix } } newList.bits = bits return newList } /** * Returns a slice of a list. Elements are removed from the beginning and * end. Both the indices can be negative in which case they will count * from the right end of the list. * * @complexity `O(log(n))` */ export function slice(from: number, to: number): (l: List) => List { return (l) => slice_(l, from, to) } /** * Takes the first `n` elements from a list and returns them in a new list. * * @complexity `O(log(n))` */ export function take_(l: List, n: number): List { return slice_(l, 0, n) } /** * Takes the first `n` elements from a list and returns them in a new list. * * @complexity `O(log(n))` */ export function take(n: number): (l: List) => List { return (l) => take_(l, n) } type FindNotIndexState = { predicate: (a: any) => boolean index: number } function findNotIndexCb(value: any, state: FindNotIndexState): boolean { if (state.predicate(value)) { ++state.index return true } else { return false } } /** * Takes the first elements in the list for which the predicate returns * `true`. * * @complexity `O(k + log(n))` where `k` is the number of elements satisfying * the predicate. */ export function takeWhile_(l: List, predicate: (a: A) => boolean): List { const { index } = foldlCb(findNotIndexCb, { predicate, index: 0 }, l) return slice_(l, 0, index) } /** * Takes the first elements in the list for which the predicate returns * `true`. * * @complexity `O(k + log(n))` where `k` is the number of elements satisfying * the predicate. */ export function takeWhile(predicate: (a: A) => boolean): (l: List) => List { return (l) => takeWhile_(l, predicate) } /** * Takes the last elements in the list for which the predicate returns * `true`. * * @complexity `O(k + log(n))` where `k` is the number of elements * satisfying the predicate. */ export function takeLastWhile_(l: List, predicate: (a: A) => boolean): List { const { index } = foldrCb(findNotIndexCb, { predicate, index: 0 }, l) return slice_(l, l.length - index, l.length) } /** * Takes the last elements in the list for which the predicate returns * `true`. * * @complexity `O(k + log(n))` where `k` is the number of elements * satisfying the predicate. */ export function takeLastWhile( predicate: (a: A) => boolean ): (l: List) => List { return (l) => takeLastWhile_(l, predicate) } /** * Removes the first elements in the list for which the predicate returns * `true`. * * @complexity `O(k + log(n))` where `k` is the number of elements * satisfying the predicate. */ export function dropWhile_(l: List, predicate: (a: A) => boolean): List { const { index } = foldlCb(findNotIndexCb, { predicate, index: 0 }, l) return slice_(l, index, l.length) } /** * Removes the first elements in the list for which the predicate returns * `true`. * * @complexity `O(k + log(n))` where `k` is the number of elements * satisfying the predicate. */ export function dropWhile(predicate: (a: A) => boolean): (l: List) => List { return (l) => dropWhile_(l, predicate) } /** * Returns a new list without repeated elements. * * @complexity `O(n)` */ export function dropRepeats(l: List): List { return dropRepeatsWith_(l, elementEquals) } /** * Returns a new list without repeated elements by using the given * function to determine when elements are equal. * * @complexity `O(n)` */ export function dropRepeatsWith_( l: List, predicate: (a: A, b: A) => boolean ): List { return reduce_(l, emptyPushable(), (acc, a) => acc.length !== 0 && predicate(unsafeLast(acc)!, a) ? acc : push_(acc, a) ) } /** * Returns a new list without repeated elements by using the given * function to determine when elements are equal. * * @complexity `O(n)` */ export function dropRepeatsWith( predicate: (a: A, b: A) => boolean ): (l: List) => List { return (l) => dropRepeatsWith_(l, predicate) } /** * Takes the last `n` elements from a list and returns them in a new * list. * * @complexity `O(log(n))` */ export function takeLast_(l: List, n: number): List { return slice_(l, l.length - n, l.length) } /** * Takes the last `n` elements from a list and returns them in a new * list. * * @complexity `O(log(n))` */ export function takeLast(n: number): (l: List) => List { return (l) => takeLast_(l, n) } /** * Splits a list at the given index and return the two sides in a pair. * The left side will contain all elements before but not including the * element at the given index. The right side contains the element at the * index and all elements after it. * * @complexity `O(log(n))` */ export function splitAt_(l: List, index: number): [List, List] { return [slice_(l, 0, index), slice_(l, index, l.length)] } /** * Splits a list at the given index and return the two sides in a pair. * The left side will contain all elements before but not including the * element at the given index. The right side contains the element at the * index and all elements after it. * * @complexity `O(log(n))` */ export function splitAt(index: number): (l: List) => [List, List] { return (l) => splitAt_(l, index) } /** * Splits a list at the first element in the list for which the given * predicate returns `true`. * * @complexity `O(n)` */ export function splitWhen_( l: List, predicate: (a: A) => boolean ): [List, List] { const idx = findIndex_(l, predicate) return idx === -1 ? [l, empty()] : splitAt_(l, idx) } /** * Splits a list at the first element in the list for which the given * predicate returns `true`. * * @complexity `O(n)` */ export function splitWhen( predicate: (a: A) => boolean ): (l: List) => [List, List] { return (l) => splitWhen_(l, predicate) } /** * Splits the list into chunks of the given size. */ export function splitEvery_(l: List, size: number): List> { const { buffer, l2 } = reduce_( l, { l2: emptyPushable>(), buffer: emptyPushable() }, ({ buffer, l2 }, elm) => { push_(buffer, elm) if (buffer.length === size) { return { l2: push_(l2, buffer), buffer: emptyPushable() } } else { return { l2, buffer } } } ) return buffer.length === 0 ? l2 : push_(l2, buffer) } /** * Splits the list into chunks of the given size. */ export function splitEvery(size: number): (l: List) => List> { return (l) => splitEvery_(l, size) } /** * Takes an index, a number of elements to remove and a list. Returns a * new list with the given amount of elements removed from the specified * index. * * @complexity `O(log(n))` */ export function remove_(l: List, from: number, amount: number): List { return concat_(slice_(l, 0, from), slice_(l, from + amount, l.length)) } /** * Takes an index, a number of elements to remove and a list. Returns a * new list with the given amount of elements removed from the specified * index. * * @complexity `O(log(n))` */ export function remove(from: number, amount: number): (l: List) => List { return (l) => remove_(l, from, amount) } /** * Returns a new list without the first `n` elements. * * @complexity `O(log(n))` */ export function drop_(l: List, n: number): List { return slice_(l, n, l.length) } /** * Returns a new list without the first `n` elements. * * @complexity `O(log(n))` */ export function drop(n: number): (l: List) => List { return (l) => drop_(l, n) } /** * Returns a new list without the last `n` elements. * * @complexity `O(log(n))` */ export function dropLast_(l: List, n: number): List { return slice_(l, 0, l.length - n) } /** * Returns a new list without the last `n` elements. * * @complexity `O(log(n))` */ export function dropLast(n: number): (l: List) => List { return (l) => dropLast_(l, n) } /** * Returns a new list with the last element removed. If the list is * empty the empty list is returned. * * @complexity `O(1)` */ export function pop(l: List): List { return slice_(l, 0, -1) } /** * Returns a new list with the first element removed. If the list is * empty the empty list is returned. * * @complexity `O(1)` */ export function tail(l: List): List { return slice_(l, 1, l.length) } function arrayPush(array: A[], a: A): A[] { array.push(a) return array } /** * Converts a list into an array. * * @complexity `O(n)` */ export function toArray(l: List): readonly A[] { return reduce_(l, [], arrayPush) } /** * Inserts the given element at the given index in the list. * * @complexity O(log(n)) */ export function insert_(l: List, index: number, element: A): List { return concat_(append_(slice_(l, 0, index), element), slice_(l, index, l.length)) } /** * Inserts the given element at the given index in the list. * * @complexity O(log(n)) */ export function insert(index: number, element: A): (l: List) => List { return (l) => insert_(l, index, element) } /** * Inserts the given list of elements at the given index in the list. * * @complexity `O(log(n))` */ export function insertAll_(l: List, index: number, elements: List): List { return concat_(concat_(slice_(l, 0, index), elements), slice_(l, index, l.length)) } /** * Inserts the given list of elements at the given index in the list. * * @complexity `O(log(n))` */ export function insertAll( index: number, elements: List ): (l: List) => List { return (l) => insertAll_(l, index, elements) } /** * Reverses a list. * @complexity O(n) */ export function reverse(l: List): List { return reduce_(l, empty(), (newL, element) => prepend_(newL, element)) } /** * Returns `true` if the given argument is a list and `false` * otherwise. * * @complexity O(1) */ export function isList(l: any): l is List { return typeof l === "object" && Array.isArray(l.suffix) } /** * Iterate over two lists in parallel and collect the pairs. * * @complexity `O(log(n))`, where `n` is the length of the smallest * list. */ export function zip_(as: List, bs: List): List> { return zipWith_(as, bs, Tp.tuple) } /** * Iterate over two lists in parallel and collect the pairs. * * @complexity `O(log(n))`, where `n` is the length of the smallest * list. */ export function zip(bs: List): (as: List) => List> { return (as) => zip_(as, bs) } /** * This is like mapping over two lists at the same time. The two lists * are iterated over in parallel and each pair of elements is passed * to the function. The returned values are assembled into a new list. * * The shortest list determines the size of the result. * * @complexity `O(log(n))` where `n` is the length of the smallest * list. */ export function zipWith_( as: List, bs: List, f: (a: A, b: B) => C ): List { const swapped = bs.length < as.length const iterator = (swapped ? as : bs)[Symbol.iterator]() return map_((swapped ? bs : as) as any, (a: any) => { const b: any = iterator.next().value return swapped ? f(b, a) : f(a, b) }) } /** * This is like mapping over two lists at the same time. The two lists * are iterated over in parallel and each pair of elements is passed * to the function. The returned values are assembled into a new list. * * The shortest list determines the size of the result. * * @complexity `O(log(n))` where `n` is the length of the smallest * list. */ export function zipWith( bs: List, f: (a: A, b: B) => C ): (as: List) => List { return (as) => zipWith_(as, bs, f) } /** * Sort the given list by comparing values using the given function. * The function receieves two values and should return `-1` if the * first value is stricty larger than the second, `0` is they are * equal and `1` if the first values is strictly smaller than the * second. * * @complexity O(n * log(n)) */ export function sortWith_(l: List, ord: Ord): List { const arr: { idx: number; elm: A }[] = [] let i = 0 forEach_(l, (elm) => arr.push({ idx: i++, elm })) arr.sort(({ elm: a, idx: i }, { elm: b, idx: j }) => { const c = ord.compare(a, b) return c !== 0 ? c : i < j ? -1 : 1 }) const newL = emptyPushable() for (let i = 0; i < arr.length; ++i) { push_(newL, arr[i]!.elm) } return newL } /** * Sort the given list by comparing values using the given function. * The function receieves two values and should return `-1` if the * first value is stricty larger than the second, `0` is they are * equal and `1` if the first values is strictly smaller than the * second. * * @complexity O(n * log(n)) */ export function sortWith(ord: Ord): (l: List) => List { return (l) => sortWith_(l, ord) } /** * Returns a list of lists where each sublist's elements are all * equal. */ export function group(l: List): List> { return groupWith_(l, elementEquals) } /** * Returns a list of lists where each sublist's elements are pairwise * equal based on the given comparison function. * * Note that only adjacent elements are compared for equality. If all * equal elements should be grouped together the list should be sorted * before grouping. */ export function groupWith_(l: List, f: (a: A, b: A) => boolean): List> { const result = emptyPushable>() let buffer = emptyPushable() forEach_(l, (a) => { if (buffer.length !== 0 && !f(unsafeLast(buffer)!, a)) { push_(result, buffer) buffer = emptyPushable() } push_(buffer, a) }) return buffer.length === 0 ? result : push_(result, buffer) } /** * Returns a list of lists where each sublist's elements are pairwise * equal based on the given comparison function. * * Note that only adjacent elements are compared for equality. If all * equal elements should be grouped together the list should be sorted * before grouping. */ export function groupWith( f: (a: A, b: A) => boolean ): (l: List) => List> { return (l) => groupWith_(l, f) } /** * Inserts a separator between each element in a list. */ export function intersperse_(l: List, separator: A): List { return pop(reduce_(l, emptyPushable(), (l2, a) => push_(push_(l2, a), separator))) } /** * Inserts a separator between each element in a list. */ export function intersperse(separator: A): (l: List) => List { return (l) => intersperse_(l, separator) } /** * Returns `true` if the given list is empty and `false` otherwise. */ export function isEmpty(l: List): boolean { return l.length === 0 } /** * Builder */ export function builder() { return new ListBuilder(emptyPushable()) } export class ListBuilder { constructor(private chunk: MutableList) {} append(a: A): ListBuilder { push_(this.chunk, a) return this } build() { return this.chunk } }