// An affix is a list that can only have length 0 to 4. It is a
// structure used internally in the finger tree.
export class Affix {
constructor(
public size: number,
public len: number,
public a: A,
public b?: A,
public c?: A,
public d?: A
) { }
toArray(): A[] {
switch (this.len) {
case 0: return [];
case 1: return [this.a];
case 2: return [this.a, this.b!];
case 3: return [this.a, this.b!, this.c!];
default: return [this.a, this.b!, this.c!, this.d!];
}
}
get(idx: number): A {
switch (idx) {
case 0: return this.a;
case 1: return this.b!;
case 2: return this.c!;
default: return this.d!;
}
}
}
function affixIntoArray(affix: Affix, offset: number, arr: A[]): void {
switch (affix.len) {
case 0: return;
case 1: arr[offset] = affix.a; return;
case 2: arr[offset] = affix.a, arr[offset + 1] = affix.b!; return;
case 3: arr[offset] = affix.a, arr[offset + 1] = affix.b!, arr[offset + 2] = affix.c!; return;
default: arr[offset] = affix.a, arr[offset + 1] = affix.b!, arr[offset + 2] = affix.c!, arr[offset + 3] = affix.d!; return;
}
}
function affixIntoArrayRev(affix: Affix, offset: number, arr: A[]): void {
switch (affix.len) {
case 0: return;
case 1: arr[offset] = affix.a; return;
case 2: arr[offset] = affix.b!, arr[offset + 1] = affix.a!; return;
case 3: arr[offset] = affix.c!, arr[offset + 1] = affix.b!, arr[offset + 2] = affix.a!; return;
default: arr[offset] = affix.d!, arr[offset + 1] = affix.c!, arr[offset + 2] = affix.b!, arr[offset + 3] = affix.a; return;
}
}
const emptyAffix: Affix = new Affix(0, 0, undefined);
function affixPrepend(size: number, a: A, as: Affix): Affix {
return new Affix(as.size + size, as.len + 1, a, as.a, as.b, as.c);
}
// Node in a 2-3 tree
// export type NNode = [A, A] | [A, A, A];
export class NNode {
constructor(
public size: number, // number of elements in tree
public three: boolean, // true if the node has three elements
public a: A,
public b: A,
public c?: A
) { }
get(idx: number): A {
switch (idx) {
case 0: return this.a;
case 1: return this.b;
default: return this.c!;
}
}
}
export class FingerTree {
constructor(
public depth: number,
public size: number,
public prefix: Affix,
public deeper: FingerTree>,
public suffix: Affix
) { }
}
export const nil = new FingerTree(
0, 0, undefined, undefined, undefined
);
function deep(
depth: number, size: number, prefix: Affix,
deeper: FingerTree>, suffix: Affix
): FingerTree {
return new FingerTree(depth, size, prefix, deeper, suffix);
}
export function prepend(a: A, t: FingerTree): FingerTree {
return nrPrepend(0, 1, a, t);
}
export function nrPrepend(depth: number, size: number, a: A, t: FingerTree): FingerTree {
if (t.size === 0) {
return deep(depth, size, new Affix(size, 1, a), nil, emptyAffix);
} else {
return nrPrependDeep(t.prefix, depth, t, size, a);
}
}
function nrPrependDeep(p: Affix, depth: number, t: FingerTree, s: number, a: A): FingerTree {
if (p.len < 4) {
return deep(depth, t.size + s, affixPrepend(s, a, t.prefix), t.deeper, t.suffix);
} else if (t.suffix === emptyAffix) {
return deep(depth, t.size + s, new Affix(s, 1, a), t.deeper, new Affix(p.size, 4, p.d!, p.c!, p.b!, p.a));
} else {
const num = depth === 0 ? 1 : (p.a).size;
const node = new NNode(p.size - num, true, p.b!, p.c!, p.d!);
return deep(
depth, t.size + s, new Affix(s + num, 2, a, p.a), nrPrepend(depth + 1, node.size, node, t.deeper), t.suffix
);
}
}
export function append(a: A, t: FingerTree): FingerTree {
return nrAppend(0, 1, a, t);
}
function nrAppend(depth: number, size: number, a: A, t: FingerTree): FingerTree {
if (t.size === 0) {
return deep(depth, size, emptyAffix, nil, new Affix(size, 1, a));
} else {
return nrAppendDeep(t.suffix, depth, t, size, a);
}
}
function nrAppendDeep(suf: Affix, depth: number, t: FingerTree, s: number, a: A): FingerTree {
if (suf.len < 4) {
return deep(depth, t.size + s, t.prefix, t.deeper, affixPrepend(s, a, t.suffix));
} else if (t.prefix === emptyAffix) {
return deep(depth, t.size + s, new Affix(suf.size, 4, suf.d!, suf.c!, suf.b!, suf.a), t.deeper, new Affix(s, 1, a));
} else {
const num = depth ? (suf.a).size : 1;
const node = new NNode(suf.size - num, true, suf.d!, suf.c!, suf.b!);
return deep(depth, t.size + s, t.prefix, nrAppend(depth + 1, node.size, node, t.deeper), new Affix(num + s, 2, a, suf.a));
}
}
export function size(t: FingerTree): number {
return t.size;
}
// Concat
const buffer = new Array(12);
const digitBuffer = new Array(4);
let digitSize = 0;
let digitLen = 0;
function copy(b: any[], d: any[], left: number): void {
b[left] = d[0];
b[left + 1] = d[1];
b[left + 2] = d[2];
b[left + 3] = d[3];
}
function nodes(deep: boolean, suffix: Affix, prefix: Affix): void {
let left = suffix.len;
affixIntoArrayRev(suffix, 0, buffer);
copy(buffer, digitBuffer, left);
left += digitLen;
affixIntoArray(prefix, left, buffer);
left += prefix.len;
let idx = 0;
digitLen = 0;
digitSize = 0;
while (left > 4 || left === 3) {
const size = deep === true ? buffer[idx].size + buffer[idx + 1].size + buffer[idx + 2].size : 3;
digitBuffer[digitLen++] = new NNode(size, true, buffer[idx], buffer[idx + 1], buffer[idx + 2]);
left -= 3;
idx += 3;
digitSize += size;
}
while (left !== 0) {
const size = deep === true ? buffer[idx].size + buffer[idx + 1].size : 2;
digitBuffer[digitLen++] = new NNode(size, false, buffer[idx], buffer[idx + 1]);
digitSize += size;
idx += 2;
left -= 2;
}
}
export function concat(t1: FingerTree, t2: FingerTree): FingerTree {
if (t1 === nil) { return t2; }
if (t2 === nil) { return t1; }
digitSize = digitLen = 0;
let topTree = deep(0, t1.size + t2.size, t1.prefix, nil, t2.suffix);
nodes(false, t1.suffix, t2.prefix);
t1 = t1.deeper;
t2 = t2.deeper;
let curTree = topTree;
let depth = 1;
while (t1 !== nil && t2 !== nil) {
let newTree = deep(depth, t1.size + t2.size + digitSize, t1.prefix, nil, t2.suffix);
nodes(true, t1.suffix, t2.prefix);
t1 = t1.deeper;
t2 = t2.deeper;
curTree.deeper = newTree;
curTree = newTree;
depth++;
}
if (t1 === nil) {
for (let i = digitLen - 1; i >= 0; --i) {
t2 = nrPrepend(depth, digitBuffer[i].size, digitBuffer[i], t2);
}
curTree.deeper = t2;
} else {
for (let i = 0; i < digitLen; ++i) {
t1 = nrAppend(depth, digitBuffer[i].size, digitBuffer[i], t1);
}
curTree.deeper = t1;
}
return topTree;
}
// Get
function affixGet(depth: number, idx: number, affix: Affix): A {
const { len, size, a, b, c, d } = affix;
if (len === size) {
return affix.get(idx);
}
let elm: any = a;
let delta = a.size;
while (idx >= delta) {
delta += b.size;
if (idx < delta) { elm = b; break; }
delta += c.size;
if (idx < delta) { elm = c; break; }
delta += d.size;
elm = d;
break;
}
return nodeGet(depth, idx - delta + elm.size, elm);
}
function affixGetRev(depth: number, idx: number, affix: Affix): A {
const { len, size, a, b, c, d } = affix;
if (len === size) {
return affix.get(len - 1 - idx);
}
let elm: any = a;
let delta = size - a.size;
while (idx < delta) {
delta -= b.size;
if (delta <= idx) { elm = b; break; }
delta -= c.size;
if (delta <= idx) { elm = c; break; }
delta -= d.size;
elm = d;
break;
}
return nodeGet(depth, idx - delta, elm);
}
function nodeGet(depth: number, idx: number, node: NNode): A {
while (--depth > 0) {
let size = 0;
if (idx < node.a.size) {
node = node.a;
idx -= size;
continue;
}
size += node.a.size;
if (idx < size + node.b.size) {
node = node.b;
idx -= size;
continue;
}
size += node.b.size;
if (idx < size + node.c.size) {
node = node.c;
idx -= size;
continue;
}
}
return node.get(idx);
}
export function get(idx: number, tree: FingerTree): A {
let { size, prefix } = tree;
if (size === 0) {
return undefined;
}
let prefSize = tree.prefix.size;
let deepSize = prefSize + tree.deeper.size;
while (prefSize <= idx && idx < deepSize) {
idx = idx - prefSize;
tree = tree.deeper;
prefix = tree.prefix;
prefSize = prefix.size;
deepSize = prefSize + tree.deeper.size;
}
const { depth } = tree;
if (idx < prefSize) {
return affixGet(depth, idx, prefix);
} else {
return affixGetRev(depth, idx - deepSize, tree.suffix);
}
}
// Fold
function nodeFoldl(f: (b: B, a: A) => B, initial: B, node: Affix, depth: number): B {
if (depth === 1) {
return f(f(f(initial, node.a), node.b), node.c);
} else {
const foldedA = nodeFoldl(f, initial, node.a, depth - 1);
const foldedB = nodeFoldl(f, foldedA, node.b, depth - 1);
const foldedC = nodeFoldl(f, foldedB, node.c, depth - 1);
return foldedC;
}
}
function affixFoldr(f: (a: A, b: B) => B, initial: B, affix: Affix): B {
switch (affix.len) {
case 0: return initial;
case 1: return f(affix.a, initial);
case 2: return f(affix.a, f(affix.b, initial));
case 3: return f(affix.a, f(affix.b, f(affix.c, initial)));
default: return f(affix.a, f(affix.b, f(affix.c, f(affix.d, initial))));
}
}
function affixFoldl(f: (b: B, a: A) => B, initial: B, affix: Affix, depth: number): B {
if (depth === 0) {
switch (affix.len) {
case 0: return initial;
case 1: return f(initial, affix.a);
case 2: return f(f(initial, affix.a), affix.b);
case 3: return f(f(f(initial, affix.a), affix.b), affix.c);
default: return f(f(f(f(initial, affix.a), affix.b), affix.c), affix.d);
}
} else {
switch (affix.len) {
case 0: return initial;
case 1: return nodeFoldl(f, initial, affix.a, depth);
case 2: return nodeFoldl(f, nodeFoldl(f, initial, affix.a, depth), affix.b, depth);
case 3: return nodeFoldl(f, nodeFoldl(f, nodeFoldl(f, initial, affix.a, depth), affix.b, depth), affix.c, depth);
default: return nodeFoldl(f, nodeFoldl(f, nodeFoldl(f, nodeFoldl(f, initial, affix.a, depth), affix.b, depth), affix.c, depth), affix.d, depth);
}
}
}
function affixFoldlRev(f: (b: B, a: A) => B, initial: B, affix: Affix, depth: number): B {
if (depth === 0) {
switch (affix.len) {
case 0: return initial;
case 1: return f(initial, affix.a);
case 2: return f(f(initial, affix.b), affix.a);
case 3: return f(f(f(initial, affix.c), affix.b), affix.a);
default: return f(f(f(f(initial, affix.d), affix.c), affix.b), affix.a);
}
} else {
switch (affix.len) {
case 0: return initial;
case 1: return nodeFoldl(f, initial, affix.a, depth);
case 2: return nodeFoldl(f, nodeFoldl(f, initial, affix.b, depth), affix.a, depth);
case 3: return nodeFoldl(f, nodeFoldl(f, nodeFoldl(f, initial, affix.c, depth), affix.b, depth), affix.a, depth);
default: return nodeFoldl(f, nodeFoldl(f, nodeFoldl(f, nodeFoldl(f, initial, affix.d, depth), affix.c, depth), affix.b, depth), affix.a, depth);
}
}
}
export function foldl(f: (b: B, a: A) => B, initial: B, list: FingerTree): B {
const { size, prefix, deeper, suffix, depth } = list;
if (size === 0) {
return initial;
} else {
const foldedSuffix = suffix === undefined ? initial : affixFoldlRev(f, initial, suffix, depth);
const foldedMiddle = deeper === undefined ? foldedSuffix : foldl(f, foldedSuffix, deeper);
return prefix === undefined ? foldedMiddle : affixFoldl(f, foldedMiddle, prefix, depth);
}
}
function flatten(a: NNode[]): A[] {
let array: A[] = [];
for (let i = 0; i < a.length; ++i) {
const e = a[i];
array.push(e.a);
array.push(e.b);
if (e.three === true) {
array.push(e.c!);
}
}
return array;
}
export function toArray(t: FingerTree): A[] {
if (t.size === 0) {
return [];
} else {
return t.prefix.toArray().concat(flatten(toArray(t.deeper))).concat(t.suffix.toArray().reverse());
}
}