const branchingFactor = 32; const branchBits = 5; const mask = 31; let elementEquals = (a: any, b: any) => { return a === b; }; export function setEquals(equals: (a: any, b: any) => boolean): void { elementEquals = equals; } 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; } /** * 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); } /** * 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]; } function updateNode( node: Node, depth: number, index: number, offset: number, value: any ): Node { const curOffset = (offset >> (depth * branchBits)) & mask; let path = ((index >> (depth * branchBits)) & mask) - curOffset; if (node.sizes !== undefined) { while (node.sizes[path] <= index) { path++; } const traversed = path === 0 ? 0 : node.sizes[path - 1]; index -= traversed; } let array; if (path < 0) { // TOOD: Once `prepend` no longer uses `update` this should be removed array = arrayPrepend(createPath(depth, value), node.array); } else { array = copyArray(node.array); if (depth === 0) { array[path] = value; } else { array[path] = updateNode( array[path], depth - 1, index, path === 0 ? offset : 0, value ); } } return new Node(node.sizes, array); } export type Sizes = number[] | undefined; export class Node { constructor(public sizes: Sizes, public array: any[]) {} } function nodeNthDense( node: Node, depth: number, index: number, offset: number ): any { index += offset; let path; let current = node; for (; depth >= 0; --depth) { path = ((index >> (depth * branchBits)) & mask) - ((offset >> (depth * branchBits)) & mask); if (path !== 0) { offset = 0; } current = current.array[path]; } return current; } function nodeNth(node: Node, depth: number, index: number): any { let path; let current = node; while (current.sizes !== undefined) { path = (index >> (depth * branchBits)) & mask; while (current.sizes[path] <= index) { path++; } const traversed = path === 0 ? 0 : current.sizes[path - 1]; index -= traversed; depth--; current = current.array[path]; } return nodeNthDense(current, depth, index, 0); } function cloneNode({ sizes, array }: Node): Node { return new Node( sizes === undefined ? undefined : copyArray(sizes), copyArray(array) ); } function suffixToNode(suffix: A[]): Node { // FIXME: should take size and copy return new Node(undefined, suffix); } function prefixToNode(prefix: A[]): Node { // FIXME: should take size and copy return new Node(undefined, prefix.reverse()); } 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; } } // 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]; 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; } } // 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)); // } function createBits( depth: number, prefixSize: number, suffixSize: number ): number { return (depth << (affixBits * 2)) | (prefixSize << affixBits) | suffixSize; } /* * 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. */ export class List { constructor( public bits: number, public offset: number, public length: number, public root: Node | undefined, public suffix: A[], public prefix: A[] ) {} space(): number { return ( branchingFactor ** (getDepth(this) + 1) - (this.length - getSuffixSize(this) - getPrefixSize(this) + this.offset) ); } [Symbol.iterator](): Iterator { return new ListIterator(this); } "fantasy-land/equals"(l: List): boolean { return equals(this, l); } "fantasy-land/map"(f: (a: A) => B): List { return map(f, this); } "fantasy-land/filter"(predicate: (a: A) => boolean): List { return filter(predicate, this); } "fantasy-land/empty"(): List { return empty(); } "fantasy-land/concat"(right: List): List { return concat(this, right); } "fantasy-land/reduce"(f: (acc: B, value: A) => B, initial: B): B { return foldl(f, initial, this); } append(value: A): List { return append(value, this); } nth(index: number): A | undefined { return nth(index, this); } } function cloneList(l: List): List { return new List(l.bits, l.offset, l.length, l.root, l.suffix, l.prefix); } const iteratorDone: IteratorResult = { done: true, value: undefined }; class ListIterator implements Iterator { stack: any[][]; indices: number[]; prefixLeft: number; constructor(private list: List) { this.stack = []; this.indices = []; this.prefixLeft = getPrefixSize(list); if (list.root !== undefined) { let currentNode = list.root.array; const depth = getDepth(list); for (let i = 0; i < depth + 1; ++i) { this.stack.push(currentNode); this.indices.push(0); currentNode = arrayFirst(currentNode).array; } this.indices[this.indices.length - 1] = -1; } else { this.indices.push(-1); } } goUp(): void { this.stack.pop(); this.indices.pop(); } remaining(): number { const node = arrayLast(this.stack); const idx = arrayLast(this.indices); return node.length - idx - 1; } incrementIndex(): number { return ++this.indices[this.indices.length - 1]; } nextInTree(): void { while (this.remaining() === 0) { this.goUp(); if (this.stack.length === 0) { return; } } this.incrementIndex(); const depth = getDepth(this.list); for (let i = this.indices.length - 1; i < depth; ++i) { this.stack.push(arrayLast(this.stack)[arrayLast(this.indices)].array); this.indices.push(0); } } next(): IteratorResult { if (this.prefixLeft > 0) { --this.prefixLeft; return { done: false, value: this.list.prefix[this.prefixLeft] }; } else if (this.stack.length !== 0) { this.nextInTree(); if (this.stack.length !== 0) { const leaf = arrayLast(this.stack); const idx = arrayLast(this.indices); const value = leaf[idx]; return { done: false, value }; } else { this.indices.push(-1); } } const suffixSize = getSuffixSize(this.list); if (this.indices[0] < suffixSize - 1) { const idx = this.incrementIndex(); return { done: false, value: this.list.suffix[idx] }; } return iteratorDone; } } // prepend & append export function prepend(value: A, l: List): List { const prefixSize = getPrefixSize(l); if (prefixSize < 32) { return new List( incrementPrefix(l.bits), l.offset, l.length + 1, l.root, l.suffix, affixPush(value, l.prefix, prefixSize) ); } 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; } } /** * 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. Will always be at least 1. * @param leafSize The number of elements in the leaf that will be * inserted. */ function copyLeft(l: List, k: number, leafSize: 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] += leafSize; } } const newNode = cloneNode(currentNode.array[index]); // Install the copied node currentNode.array[index] = newNode; currentNode = newNode; } return currentNode; } function prependSizes(n: number, sizes: Sizes): Sizes { if (sizes === undefined) { return undefined; } else { const newSizes = new Array(sizes.length + 1); newSizes[0] = n; for (let i = 0; i < sizes.length; ++i) { newSizes[i + 1] = sizes[i] + n; } return newSizes; } } /** * Prepends a node to a tree. Either by shifting the nodes in the root * left or by increasing the height */ function prependTopTree(l: List, depth: number, node: Node) { let newOffset; if (l.root!.array.length < branchingFactor) { // There is space in the root newOffset = 32 ** depth - 32; l.root = new Node( prependSizes(32, l.root!.sizes), 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 RRB-tree and a node tail. It then prepends the node to the * tree. * @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: List, 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 - 1, (l.offset - 1) >> 5, l.offset >> 5, 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, 32); for (let i = 0; i < copiedNode.sizes!.length; ++i) { copiedNode.sizes![i] += branchingFactor; } copiedNode.array[0] = prependDense( copiedNode.array[0], depth - nodesTraversed - 1, (l.offset - 1) >> 5, l.offset >> 5, 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, 32); 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; } } function prependDense( node: Node, depth: number, index: number, offset: number, value: any ): Node { const curOffset = (offset >> (depth * branchBits)) & mask; let path = ((index >> (depth * branchBits)) & mask) - curOffset; let array; if (path < 0) { array = arrayPrepend(createPath(depth, value), node.array); } else { array = copyArray(node.array); if (depth === 0) { array[path] = value; } else { array[path] = updateNode( array[path], depth - 1, index, path === 0 ? offset : 0, value ); } } return new Node(node.sizes, array); } export function append(value: A, l: List): List { const suffixSize = getSuffixSize(l); if (suffixSize < 32) { return new List( incrementSuffix(l.bits), l.offset, l.length + 1, l.root, affixPush(value, l.suffix, suffixSize), l.prefix ); } const newSuffix = [value]; const suffixNode = suffixToNode(l.suffix); const newList = cloneList(l); appendNodeToTree(newList, suffixNode); newList.suffix = newSuffix; newList.length++; newList.bits = setSuffix(1, newList.bits); return newList; } export function list(...elements: A[]): List { let l = empty(); for (const element of elements) { l = append(element, l); } return l; } export function pair(first: A, second: A): List { return new List(2, 0, 2, undefined, [first, second], emptyAffix); } export function empty(): List { return new List(0, 0, 0, undefined, emptyAffix, emptyAffix); } export function repeat(value: A, times: number): List { let l = empty(); while (--times >= 0) { l = append(value, l); } return l; } export function length(l: List): number { return l.length; } export function first(l: List): A | undefined { if (getPrefixSize(l) !== 0) { return arrayLast(l.prefix); } else if (getSuffixSize(l) !== 0) { return arrayFirst(l.suffix); } } export function last(l: List): A | undefined { if (getSuffixSize(l) !== 0) { return arrayLast(l.suffix); } else if (getPrefixSize(l) !== 0) { return arrayFirst(l.prefix); } } export function nth(index: number, l: List): A { const prefixSize = getPrefixSize(l); const suffixSize = getSuffixSize(l); const { offset } = l; if (index < prefixSize) { return l.prefix[prefixSize - index - 1]; } else if (index >= l.length - suffixSize) { return l.suffix[index - (l.length - suffixSize)]; } const depth = getDepth(l); return l.root!.sizes === undefined ? nodeNthDense(l.root!, depth, index - prefixSize, offset) : nodeNth(l.root!, depth, index - prefixSize); } // 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 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; } export function map(f: (a: A) => B, l: List): List { return new List( l.bits, l.offset, l.length, l.root === undefined ? undefined : mapNode(f, l.root, getDepth(l)), mapAffix(f, l.suffix, getSuffixSize(l)), mapAffix(f, l.prefix, getPrefixSize(l)) ); } export function pluck(key: K, l: List): List { return map(a => a[key], l); } export function range(start: number, end: number): List { let list = empty(); for (let i = start; i < end; ++i) { list = list.append(i); } return list; } // 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; } export function foldl( f: (acc: B, value: A) => B, initial: B, l: List ): 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); } export const reduce = foldl; export function filter(predicate: (a: A) => boolean, l: List): List { return foldl((acc, a) => (predicate(a) ? append(a, acc) : acc), empty(), l); } export function reject(predicate: (a: A) => boolean, l: List): List { return foldl((acc, a) => (predicate(a) ? acc : append(a, acc)), empty(), l); } export function join(separator: string, l: List): string { return foldl((a, b) => (a.length === 0 ? b : a + separator + b), "", l); } 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; } export function foldr( f: (value: A, acc: B) => B, initial: B, l: List ): 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); } export const reduceRight = foldr; export function flatten(nested: List>): List { return foldl, List>(concat, empty(), nested); } // callback fold type FoldCb = (input: Input, state: State) => boolean; function foldlSuffixCb( cb: FoldCb, state: B, array: A[], length: number ): boolean { for (var i = 0; i < length && cb(array[i], state); ++i) {} return i === length; } function foldlPrefixCb( cb: FoldCb, state: B, array: A[], length: number ): boolean { for (var i = length - 1; 0 <= i && cb(array[i], state); --i) {} return i === -1; } function foldlNodeCb( cb: FoldCb, state: B, node: Node, depth: number ): boolean { const { array } = node; if (depth === 0) { return foldlSuffixCb(cb, state, array, array.length); } for ( var i = 0; i < array.length && foldlNodeCb(cb, state, array[i], depth - 1); ++i ) {} return i === array.length; } /** * 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 suffixSize = getSuffixSize(l); const prefixSize = getPrefixSize(l); if (foldlPrefixCb(cb, state, l.prefix, prefixSize)) { if (l.root !== undefined) { if (foldlNodeCb(cb, state, l.root, getDepth(l))) { foldlSuffixCb(cb, state, l.suffix, suffixSize); } } else { foldlSuffixCb(cb, state, l.suffix, suffixSize); } } return state; } // functions based on foldlCb type PredState = { predicate: (a: any) => boolean; result: any; }; function everyCb(value: A, state: any): boolean { return (state.result = state.predicate(value)); } export function every(predicate: (a: A) => boolean, l: List): boolean { return foldlCb(everyCb, { predicate, result: true }, l).result; } export const all = every; function someCb(value: A, state: any): boolean { return !(state.result = state.predicate(value)); } export function some(predicate: (a: A) => boolean, l: List): boolean { return foldlCb(someCb, { predicate, result: false }, l).result; } // tslint:disable-next-line:variable-name export const any = some; export function none(predicate: (a: A) => boolean, l: List): boolean { return !some(predicate, l); } function findCb(value: A, state: PredState): boolean { if (state.predicate(value)) { state.result = value; return false; } else { return true; } } export function find( predicate: (a: A) => boolean, l: List ): A | undefined { return foldlCb(findCb, { predicate, result: undefined }, l) .result; } type IndexOfState = { element: any; found: boolean; index: number; }; function indexOfCb(value: A, state: IndexOfState): boolean { ++state.index; return !(state.found = elementEquals(value, state.element)); } export function indexOf(element: A, l: List): number { const { found, index } = foldlCb( indexOfCb, { element, found: false, index: -1 }, l ); return found ? index : -1; } 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)); } export function findIndex(predicate: (a: A) => boolean, l: List): number { const { found, index } = foldlCb( findIndexCb, { predicate, found: false, index: -1 }, l ); return found ? index : -1; } 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); } export function includes(element: A, l: List): boolean { containsState.element = element; containsState.result = false; return foldlCb(containsCb, containsState, l).result; } export const contains = includes; type EqualsState = { iterator: Iterator; equals: boolean; }; const equalsState: EqualsState = { iterator: undefined as any, equals: true }; function equalsCb(value2: any, state: EqualsState): boolean { const { value } = state.iterator.next(); return (state.equals = elementEquals(value, value2)); } export function equals(firstList: List, secondList: List): boolean { if (firstList === secondList) { return true; } else if (firstList.length !== secondList.length) { return false; } else { equalsState.iterator = secondList[Symbol.iterator](); equalsState.equals = true; return foldlCb(equalsCb, equalsState, firstList).equals; } } // 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 a node tail. 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 node The node that should be appended to the tree. */ function appendNodeToTree(l: List, node: Node): List { if (l.root === undefined) { // The old list has no content in tree, all content is in affixes if (getPrefixSize(l) === 0) { l.bits = setPrefix(node.array.length, l.bits); l.prefix = reverseArray(node.array); } else { l.root = node; } return l; } const depth = getDepth(l); let index = 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; } } 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, l, nodesToCopy, node.array.length); const leaf = appendEmpty(copiedNode, depth - nodesToCopy); leaf.array.push(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( oldList: List, newList: List, k: number, leafSize: number ): Node { let currentNode = cloneNode(oldList.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; } function appendEmpty(node: Node, depth: number): Node { if (depth === 0) { return node; } let current = new Node(undefined, []); node.array.push(current); for (let i = 1; i < depth; ++i) { let newNode = new Node(undefined, []); current.array[0] = newNode; current = newNode; } return current; } /* function concatSuffix( left: A[], lSize: number, right: A[], rSize: number ): A[] { const newArray = new Array(lSize + rSize); for (let i = 0; i < lSize; ++i) { newArray[i] = left[i]; } for (let i = 0; i < rSize; ++i) { newArray[lSize + i] = right[i]; } return newArray; } */ const concatBuffer = new Array(3); function concatAffixes(left: List, right: List): number { // TODO: Try and find a neat way to reduce the LOC here var nr = 0; var arrIdx = 0; var i = 0; var length = getSuffixSize(left); concatBuffer[nr] = []; for (i = 0; i < length; ++i) { concatBuffer[nr][arrIdx] = left.suffix[i]; if (++arrIdx === 32) { arrIdx = 0; ++nr; concatBuffer[nr] = []; } } length = getPrefixSize(right); for (i = 0; i < length; ++i) { concatBuffer[nr][arrIdx] = right.prefix[right.prefix.length - 1 - i]; if (++arrIdx === 32) { arrIdx = 0; ++nr; concatBuffer[nr] = []; } } length = getSuffixSize(right); for (i = 0; i < length; ++i) { concatBuffer[nr][arrIdx] = right.suffix[i]; if (++arrIdx === 32) { arrIdx = 0; ++nr; concatBuffer[nr] = []; } } return nr; } 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 (var i = 0; i < nrOfAffixes; ++i) { newList = appendNodeToTree(newList, new Node(undefined, 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 { newList = appendNodeToTree(newList, suffixToNode(left.suffix)); newList.length += getSuffixSize(left); newList = appendNodeToTree(newList, prefixToNode(right.prefix)); const newNode = concatSubTree( newList.root!, getDepth(newList), right.root, getDepth(right), true ); const newDepth = getHeight(newNode); setSizes(newNode, newDepth); const bits = createBits(newDepth, getPrefixSize(newList), rightSuffixSize); // FIXME: Return `newList` here return new List(bits, 0, newSize, newNode, right.suffix, newList.prefix); } } export function update(index: number, a: A, l: List): List { 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, l.offset, a ); } return newList; } export function adjust(f: (a: A) => A, index: number, l: List): List { return update(index, f(nth(index, l)), l); } // slice and slice based functions let newAffix: any[]; function sliceLeft( tree: Node, depth: number, index: number, offset: number ): Node | undefined { const curOffset = (offset >> (depth * branchBits)) & mask; let path = ((index >> (depth * branchBits)) & mask) - curOffset; 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 { // slice the child const child = sliceLeft( tree.array[path], depth - 1, index, path === 0 ? offset : 0 ); 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; } } let array = tree.array.slice(path); if (child !== undefined) { array[0] = child; } return new Node(tree.sizes, array); // FIXME: handle the size table } } function sliceRight( tree: Node, depth: number, index: number, offset: number ): Node | undefined { const curOffset = (offset >> (depth * branchBits)) & mask; let path = ((index >> (depth * branchBits)) & mask) - curOffset; if (depth === 0) { newAffix = tree.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( tree.array[path], depth - 1, index, 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. let array = tree.array.slice(0, path + 1); if (child !== undefined) { array[array.length - 1] = child; } return new Node(tree.sizes, array); // FIXME: handle the size table } } function sliceTreeList( from: number, to: number, tree: Node, depth: number, offset: number, l: List ): List { const curOffset = (offset >> (depth * branchBits)) & mask; let pathLeft = ((from >> (depth * branchBits)) & mask) - curOffset; let pathRight = ((to >> (depth * branchBits)) & mask) - curOffset; 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(from, to, tree.array[pathLeft], depth - 1, pathLeft === 0 ? offset : 0, l); const rec = sliceTreeList( from, to, tree.array[pathLeft], depth - 1, pathLeft === 0 ? offset : 0, l ); if (rec.root !== undefined) { rec.root = new Node(undefined, [rec.root]); } return rec; } else { const childLeft = sliceLeft( tree.array[pathLeft], depth - 1, from, pathLeft === 0 ? offset : 0 ); l.bits = setPrefix(newAffix.length, l.bits); l.prefix = newAffix; const childRight = sliceRight(tree.array[pathRight], depth - 1, to, 0); l.bits = setSuffix(newAffix.length, l.bits); l.suffix = newAffix; if (childLeft === undefined) { ++pathLeft; } if (childRight === undefined) { --pathRight; } if (pathLeft > pathRight) { // there is no tree left l.bits = setDepth(0, l.bits); l.root = undefined; // } else if (pathLeft === pathRight) { // height can be reduced // l.bits = decrementDepth(l.bits); // l.root = childLeft === undefined ? childRight : childLeft; } else { let array = tree.array.slice(pathLeft, pathRight + 1); if (childLeft !== undefined) { array[0] = childLeft; } if (childRight !== undefined) { array[array.length - 1] = childRight; } l.root = new Node(tree.sizes, array); } return l; } } export function slice(from: number, to: number, l: List): 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; } 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, undefined, emptyAffix, l.prefix.slice(l.prefix.length - to, l.prefix.length - from) ); } const suffixStart = length - suffixSize; // both indices lie in the suffix if (suffixStart <= from) { return new List( setSuffix(newLength, 0), 0, newLength, undefined, l.suffix.slice(from - suffixStart, to - suffixStart), emptyAffix ); } const newList = cloneList(l); // both indices lie in the tree if (prefixSize <= from && to <= length - suffixSize) { sliceTreeList( from - prefixSize + l.offset, to - prefixSize + l.offset - 1, l.root!, getDepth(l), l.offset, newList ); if (newList.root !== undefined) { newList.offset += from - prefixSize + getPrefixSize(newList); } newList.length = to - from; return newList; } // we need to slice something off of the left if (0 < from) { if (from < prefixSize) { // do a cheap slice by setting prefix length bits = setPrefix(prefixSize - from, bits); } else { // if we're here `to` can't lie in the tree, so we can set the // root newList.root = sliceLeft( newList.root!, getDepth(l), from - prefixSize + l.offset, l.offset ); bits = setPrefix(newAffix.length, bits); newList.offset += from - prefixSize + newAffix.length; prefixSize = newAffix.length; newList.prefix = newAffix; } newList.length -= from; } if (to < length) { if (length - to < suffixSize) { bits = setSuffix(suffixSize - (length - to), bits); } else { newList.root = sliceRight( newList.root!, getDepth(l), to - prefixSize + newList.offset - 1, newList.offset ); if (newList.root === undefined) { bits = setDepth(0, bits); } bits = setSuffix(newAffix.length, bits); newList.suffix = newAffix; } newList.length -= length - to; } newList.bits = bits; return newList; } export function take(n: number, l: List): List { return slice(0, n, l); } type FindNotIndexState = { predicate: (a: any) => boolean; index: number; }; function findNotIndexCb(value: A, state: FindNotIndexState): boolean { ++state.index; return state.predicate(value); } export function takeWhile( predicate: (a: A) => boolean, l: List ): List { const { index } = foldlCb( findNotIndexCb, { predicate, index: -1 }, l ); return slice(0, index, l); } export function dropWhile( predicate: (a: A) => boolean, l: List ): List { const { index } = foldlCb( findNotIndexCb, { predicate, index: -1 }, l ); return slice(index, l.length, l); } export function takeLast(n: number, l: List): List { return slice(l.length - n, l.length, l); } export function splitAt(index: number, l: List): [List, List] { return [slice(0, index, l), slice(index, l.length, l)]; } export function remove(from: number, amount: number, l: List): List { return concat(slice(0, from, l), slice(from + amount, l.length, l)); } export function drop(n: number, l: List): List { return slice(n, l.length, l); } export function dropLast(n: number, l: List): List { return slice(0, l.length - n, l); } export function pop(l: List): List { return slice(0, -1, l); } export const init = pop; export function tail(l: List): List { return slice(1, l.length, l); } function arrayPush(array: A[], a: A): A[] { array.push(a); return array; } export function toArray(l: List): A[] { return foldl(arrayPush, [], l); } export function fromArray(array: A[]): List { let l = empty(); for (let i = 0; i < array.length; ++i) { l = append(array[i], l); } return l; }