Skip to content

Commit

Permalink
feat: Add SegTree (#204)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikuroXina committed May 11, 2024
1 parent afb8063 commit b999a4b
Show file tree
Hide file tree
Showing 3 changed files with 289 additions and 0 deletions.
1 change: 1 addition & 0 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export * as MonadReader from "./src/reader/monad.ts";
export * as Record from "./src/record.ts";
export * as Result from "./src/result.ts";
export * as Reverse from "./src/reverse.ts";
export * as SegTree from "./src/seg-tree.ts";
export * as Serial from "./src/serial.ts";
export * as Seq from "./src/seq.ts";
export * as FingerTree from "./src/seq/finger-tree.ts";
Expand Down
71 changes: 71 additions & 0 deletions src/seg-tree.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { assertEquals } from "../deps.ts";
import { doMut, readMutRef } from "./mut.ts";
import {
clear,
get,
insert,
intoItems,
intoMut,
query,
withItems,
} from "./seg-tree.ts";
import { addMonoid } from "./type-class/monoid.ts";

Deno.test("query", () => {
const src = [1, 4, 2, 3, 5, 2, 3];
const seq = withItems(addMonoid)(src);
for (let from = -1; from <= 7; ++from) {
for (let to = 0; to <= 8; ++to) {
let expected = 0;
for (let i = from; i < to; ++i) {
expected += i in src ? src[i] : 0;
}
assertEquals(query(from)(to)(seq), expected);
}
}
});

Deno.test("get", () => {
const src = [1, 4, 2, 3, 5, 2, 3];
const seq = withItems(addMonoid)(src);
for (let i = 0; i < src.length; ++i) {
assertEquals(get(i)(seq), src[i]);
}
});

Deno.test("intoItems", () => {
const src = [1, 4, 2, 3, 5, 2, 3];
const seq = withItems(addMonoid)(src);
const actual = intoItems(seq);
assertEquals(actual.length, src.length);
for (let i = 0; i < src.length; ++i) {
assertEquals(actual[i], src[i]);
}
});

Deno.test("clear", () => {
const src = [1, 4, 2, 3, 5, 2, 3];
const seq = withItems(addMonoid)(src);
const cleared = doMut((cat) =>
cat.addM("ref", intoMut(seq))
.runWith(({ ref }) => clear(ref))
.finishM(({ ref }) => readMutRef(ref))
);
const actual = intoItems(cleared);
assertEquals(actual.length, src.length);
for (let i = 0; i < src.length; ++i) {
assertEquals(actual[i], 0);
}
});

Deno.test("insert", () => {
const src = [1, 4, 2, 3, 5, 2, 3];
const seq = withItems(addMonoid)(src);
assertEquals(query(1)(3)(seq), 6);
const inserted = doMut((cat) =>
cat.addM("ref", intoMut(seq))
.runWith(({ ref }) => insert(ref)(1)(7))
.finishM(({ ref }) => readMutRef(ref))
);
assertEquals(query(1)(3)(inserted), 9);
});
217 changes: 217 additions & 0 deletions src/seg-tree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/**
* This modules provides a data structure `SegTree`, the implementation of Segment Tree. This allows you that finding the monoid sum for the range in `O(log n)` time complexity.
*
* @packageDocumentation
* @module
*/

import { modifyMutRef, type Mut, type MutRef, newMutRef } from "./mut.ts";
import type { Monoid } from "./type-class/monoid.ts";

export type SegTreeInner<T> = {
/**
* Tree of items calculated by `monoid.combine`. It defaults to `monoid.identity`.
*
* At the element of `index`, the relationships are:
*
* - Parent: `Math.floor((index - 1) / 2)`,
* - First child: `index * 2 + 1`,
* - Second child: `index * 2 + 2`.
*/
items: T[];
/**
* The length of source array.
*/
srcLen: number;
/**
* The number of the actual stored items, or capacity. Primary data are stored from `items[size - 1]` to `items[size - 1 + srcLen]`.
*/
actualLen: number;
/**
* The `Monoid` instance for `T`.
*/
monoid: Monoid<T>;
};
/**
* A segment tree, the data structure which can query the monoid sum of items and update an element in `O(log n)`.
*/
export type SegTree<T> = Readonly<SegTreeInner<T>>;

const parentOf = (index: number): number => Math.floor((index - 1) / 2);
const firstChildOf = (index: number): number => index * 2 + 1;
const secondChildOf = (index: number): number => index * 2 + 2;

const I32_MAX = 2147483649;
const MAX_LEN = (I32_MAX + 1) / 2;
const nextPowerOfTwo = (x: number): number => {
if (x <= 0) {
return 1;
}
if (MAX_LEN < x) {
return MAX_LEN;
}
x = x | 0;
--x;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
++x;
return x;
};

/**
* Creates a new segment tree with the monoid instance for `T` and its length.
*
* @param monoid - The `Monoid` instance for `T`, you want to calculate quickly.
* @param len - The length limit on inserting items after.
* @returns The new segment tree.
*/
export const withLen = <T>(monoid: Monoid<T>) => (len: number): SegTree<T> => {
const size = nextPowerOfTwo(len);
return {
items: [...new Array(size * 2 - 1)].map(() => monoid.identity),
srcLen: len,
actualLen: size,
monoid,
};
};

/**
* Creates a new segment tree with the monoid instance for `T` and existing items.
*
* @param monoid - The `Monoid` instance for `T`, you want to calculate quickly.
* @param items - The items to be stored.
* @returns The new segment tree.
*/
export const withItems =
<T>(monoid: Monoid<T>) => (items: readonly T[]): SegTree<T> => {
if (MAX_LEN < items.length) {
throw new Error("too much items");
}
const size = nextPowerOfTwo(items.length);
const caches = [...new Array(size * 2 - 1)].map(() => monoid.identity)
.toSpliced(
size - 1,
items.length,
...items,
);
const go = (visiting: number) => {
if (size - 1 <= visiting) {
return;
}
const left = firstChildOf(visiting);
go(left);
const right = secondChildOf(visiting);
go(right);
caches[visiting] = monoid.combine(caches[left], caches[right]);
};
go(0);
return {
items: caches,
srcLen: items.length,
actualLen: size,
monoid,
};
};

/**
* Calculates the monoid sum of items for the querying range between `start` (inclusive) and `end` (exclusive).
*
* @param start - The start index (inclusive) of the querying range.
* @param end - The end index (exclusive) of the querying range.
* @param tree - To be queried.
* @returns The monoid sum of items for the querying range.
*/
export const query =
(start: number) => (end: number) => <T>(tree: SegTree<T>): T => {
const go = (
visiting: number,
lookingStart: number,
lookingEnd: number,
): T => {
if (lookingEnd <= start || end <= lookingStart) {
// looking doesn't contain querying range
return tree.monoid.identity;
}
if (start <= lookingStart && lookingEnd <= end) {
// looking completely contains querying range
return tree.items[visiting];
}
const mid = lookingStart +
Math.floor((lookingEnd - lookingStart) / 2);
const left = go(firstChildOf(visiting), lookingStart, mid);
const right = go(secondChildOf(visiting), mid, lookingEnd);
return tree.monoid.combine(left, right);
};
return go(0, 0, tree.actualLen);
};

/**
* Gets the element at the index.
*
* @param index - The index of items.
* @param tree - To be queried.
* @returns The stored item, or the identity of monoid if not found.
*/
export const get = (index: number): <T>(tree: SegTree<T>) => T =>
query(index)(index + 1);

/**
* Extracts the stored items from the segment tree.
*
* @param tree - To be extracted.
* @returns The shallow copy of stored items.
*/
export const intoItems = <T>(tree: SegTree<T>): T[] =>
tree.items.slice(tree.actualLen - 1, tree.actualLen - 1 + tree.srcLen);

/**
* Makes the segment tree into the mutable reference on `Mut` environment.
*
* @param tree - To be transformed.
* @returns The mutable variable reference on `Mut`.
*/
export const intoMut = <S, T>(
tree: SegTree<T>,
): Mut<S, MutRef<S, SegTree<T>>> =>
newMutRef({ ...tree, items: [...tree.items] });

/**
* Clears the items in the segment tree on `Mut`.
*
* @param ref - The reference to a segment tree.
* @returns The clearing operation.
*/
export const clear = <S, T>(ref: MutRef<S, SegTree<T>>): Mut<S, never[]> =>
modifyMutRef(ref)((tree) => {
tree.items.fill(tree.monoid.identity);
return tree;
});

/**
* Updates an item at `index` with `value` in the segment tree on `Mut`.
*
* @param ref - The reference to a segment tree.
* @param index - The position index to insert the value.
* @param value - To be inserted.
* @returns The inserting operation.
*/
export const insert =
<S, T>(ref: MutRef<S, SegTree<T>>) =>
(index: number) =>
(value: T): Mut<S, never[]> =>
modifyMutRef(ref)((tree) => {
index += tree.actualLen - 1;
tree.items[index] = value;
while (0 < index) {
index = parentOf(index);
tree.items[index] = tree.monoid.combine(
tree.items[firstChildOf(index)],
tree.items[secondChildOf(index)],
);
}
return tree;
});

0 comments on commit b999a4b

Please sign in to comment.