Skip to content

Commit

Permalink
BREAKING(data_structures): docs & private internals
Browse files Browse the repository at this point in the history
This commit completes documentation for the
data_structures module, and makes the internals of
`BinarySearchTree` properly private, so that
`BinarySearchNode` is not leaked.
  • Loading branch information
lucacasonato committed May 21, 2024
1 parent 16162eb commit ba1e8f2
Show file tree
Hide file tree
Showing 8 changed files with 854 additions and 145 deletions.
1 change: 1 addition & 0 deletions _tools/check_docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const ENTRY_POINTS = [
"../collections/mod.ts",
"../internal/mod.ts",
"../media_types/mod.ts",
"../data_structures/mod.ts",
] as const;

const MD_SNIPPET = /(?<=```ts\n)(\n|.)*(?=\n```)/g;
Expand Down
39 changes: 39 additions & 0 deletions data_structures/_binary_search_tree_private.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { BinarySearchNode } from "./_binary_search_node.ts";
import type { Direction } from "./_red_black_node.ts";
import type { BinarySearchTree } from "./binary_search_tree.ts";

// These are the private methods and properties that are shared between the
// binary search tree and red-black tree implementations. They are not meant
// to be used outside of the data structures module.
export const internals: {
/** Returns the root node of the binary search tree. */
getRoot<T>(tree: BinarySearchTree<T>): BinarySearchNode<T> | null;
/** Sets the root node of the binary search tree. */
setRoot<T>(
tree: BinarySearchTree<T>,
node: BinarySearchNode<T> | null,
): void;
getCompare<T>(tree: BinarySearchTree<T>): (a: T, b: T) => number;
setCompare<T>(
tree: BinarySearchTree<T>,
compare: (a: T, b: T) => number,
): void;
findNode<T>(
tree: BinarySearchTree<T>,
value: T,
): BinarySearchNode<T> | null;
rotateNode<T>(
tree: BinarySearchTree<T>,
node: BinarySearchNode<T>,
direction: Direction,
): void;
insertNode<T>(
tree: BinarySearchTree<T>,
Node: typeof BinarySearchNode,
value: T,
): BinarySearchNode<T> | null;
removeNode<T>(
tree: BinarySearchTree<T>,
node: BinarySearchNode<T>,
): BinarySearchNode<T> | null;
} = {} as typeof internals;
256 changes: 237 additions & 19 deletions data_structures/binary_heap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,120 @@ function getParentIndex(index: number) {
* assertEquals([...words], ["truck", "tank", "car"]);
* assertEquals([...words], []);
* ```
*
* @typeparam T The type of the values stored in the binary heap.
*/
export class BinaryHeap<T> implements Iterable<T> {
#data: T[] = [];
constructor(private compare: (a: T, b: T) => number = descend) {}
/** Returns the underlying cloned array in arbitrary order without sorting */
#compare: (a: T, b: T) => number;

/**
* Construct an empty binary heap.
*
* @example Creating an empty binary heap
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = new BinaryHeap<number>();
* ```
*
* @example Creating a binary heap with a custom comparison function
* ```ts
* import { BinaryHeap, ascend } from "@std/data-structures";
* const heap = new BinaryHeap(ascend);
* ```
*
* @param compare A custom comparison function to sort the values in the heap. By default, the values are sorted in descending order.
*/
constructor(compare: (a: T, b: T) => number = descend) {
this.#compare = compare;
}

/**
* Returns the underlying cloned array in arbitrary order without sorting.
*
* @example Getting the underlying array
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = BinaryHeap.from([4, 1, 3, 5, 2]);
* heap.toArray(); // [ 5, 4, 3, 1, 2 ]
* ```
*
* @returns An array containing the values in the binary heap.
*/
toArray(): T[] {
return Array.from(this.#data);
}
/** Creates a new binary heap from an array like or iterable object. */
static from<T>(
collection: ArrayLike<T> | Iterable<T> | BinaryHeap<T>,
): BinaryHeap<T>;

/**
* Creates a new binary heap from an array like, an iterable object, or an
* existing binary heap.
*
* A custom comparison function can be provided to sort the values in a
* specific order. By default, the values are sorted in descending order,
* unless a {@link BinaryHeap} is passed, in which case the comparison
* function is copied from the input heap.
*
* @example Creating a binary heap from an array like
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = BinaryHeap.from([4, 1, 3, 5, 2]);
* ```
*
* @example Creating a binary heap from an iterable object
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = BinaryHeap.from((function*() { yield* [4, 1, 3, 5, 2]; })());
* ```
*
* @example Creating a binary heap from an existing binary heap
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = BinaryHeap.from([4, 1, 3, 5, 2]);
* const copy = BinaryHeap.from(heap);
* ```
*
* @example Creating a binary heap from an array like with a custom comparison function
* ```ts
* import { BinaryHeap, ascend } from "@std/data-structures";
* const heap = BinaryHeap.from([4, 1, 3, 5, 2], { compare: ascend });
* ```
*
* @typeparam T The type of the values stored in the binary heap.
* @param collection An array like, an iterable object, or an existing binary heap.
* @param options An optional options object to customize the comparison function.
*/
static from<T>(
collection: ArrayLike<T> | Iterable<T> | BinaryHeap<T>,
options: {
options?: {
compare?: (a: T, b: T) => number;
},
): BinaryHeap<T>;
static from<T, U, V>(
/**
* Creates a new binary heap from an array like, an iterable object, or an
* existing binary heap.
*
* A custom mapping function can be provided to transform the values before
* inserting them into the heap.
*
* A custom comparison function can be provided to sort the values in a
* specific order. By default, the values are sorted in descending order,
* unless a {@link BinaryHeap} is passed, in which case the comparison
* function is copied from the input heap. The comparison operator is used to
* sort the values in the heap after mapping the values.
*
* @example Creating a binary heap from an array like with a custom mapping function
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = BinaryHeap.from([4, 1, 3, 5, 2], { map: (value) => value * 2 });
* ```
*
* @typeparam T The type of the values in the passed collection.
* @typeparam U The type of the values stored in the binary heap.
* @typeparam V The type of the `this` value when calling the mapping function. Defaults to `undefined`.
* @param collection An array like, an iterable object, or an existing binary heap.
* @param options The options object to customize the mapping and comparison functions. The `thisArg` property can be used to set the `this` value when calling the mapping function.
*/
static from<T, U, V = undefined>(
collection: ArrayLike<T> | Iterable<T> | BinaryHeap<T>,
options: {
compare?: (a: U, b: U) => number;
Expand All @@ -94,7 +189,7 @@ export class BinaryHeap<T> implements Iterable<T> {
let unmappedValues: ArrayLike<T> | Iterable<T> = [];
if (collection instanceof BinaryHeap) {
result = new BinaryHeap(
options?.compare ?? (collection as unknown as BinaryHeap<U>).compare,
options?.compare ?? (collection as unknown as BinaryHeap<U>).#compare,
);
if (options?.compare || options?.map) {
unmappedValues = collection.#data;
Expand All @@ -114,17 +209,67 @@ export class BinaryHeap<T> implements Iterable<T> {
return result;
}

/** The amount of values stored in the binary heap. */
/**
* The count of values stored in the binary heap.
*
* The complexity of this operation is O(1).
*
* @returns The count of values stored in the binary heap.
*/
get length(): number {
return this.#data.length;
}

/** Returns the greatest value in the binary heap, or undefined if it is empty. */
/**
* Get the greatest value from the binary heap without removing it, or
* undefined if the heap is empty.
*
* The complexity of this operation is O(1).
*
* @example Getting the greatest value from the binary heap
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = BinaryHeap.from([4, 1, 3, 5, 2]);
* heap.peek(); // 5
* ```
*
* @example Getting the greatest value from an empty binary heap
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = new BinaryHeap<number>();
* heap.peek(); // undefined
* ```
*
* @returns The greatest value from the binary heap, or undefined if it is empty.
*/
peek(): T | undefined {
return this.#data[0];
}

/** Removes the greatest value from the binary heap and returns it, or null if it is empty. */
/**
* Remove the greatest value from the binary heap and return it, or return
* undefined if the heap is empty.
*
* @example Removing the greatest value from the binary heap
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = BinaryHeap.from([4, 1, 3, 5, 2]);
* heap.pop(); // 5
* [...heap]; // [ 4, 3, 2, 1 ]
* ```
*
* The complexity of this operation is on average and worst case O(log n),
* where n is the count of values stored in the binary heap.
*
* @example Removing the greatest value from an empty binary heap
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = new BinaryHeap<number>();
* heap.pop(); // undefined
* ```
*
* @returns The greatest value from the binary heap, or undefined if the heap is empty.
*/
pop(): T | undefined {
const size: number = this.#data.length - 1;
swap(this.#data, 0, size);
Expand All @@ -133,10 +278,10 @@ export class BinaryHeap<T> implements Iterable<T> {
let left: number = right - 1;
while (left < size) {
const greatestChild = right === size ||
this.compare(this.#data[left]!, this.#data[right]!) <= 0
this.#compare(this.#data[left]!, this.#data[right]!) <= 0
? left
: right;
if (this.compare(this.#data[greatestChild]!, this.#data[parent]!) < 0) {
if (this.#compare(this.#data[greatestChild]!, this.#data[parent]!) < 0) {
swap(this.#data, parent, greatestChild);
parent = greatestChild;
} else {
Expand All @@ -148,14 +293,32 @@ export class BinaryHeap<T> implements Iterable<T> {
return this.#data.pop();
}

/** Adds values to the binary heap. */
/**
* Add one or more values to the binary heap, returning the new length of the
* heap.
*
* The complexity of this operation is O(1) on average and O(log n) in the
* worst case, where n is the count of values stored in the binary heap.
*
* @example Adding values to the binary heap
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = BinaryHeap.from([4, 1, 3, 2]);
* heap.push(5);
* [...heap]; // [ 5, 4, 3, 1, 2 ]
* ```
*
* @param values The values to add to the binary heap.
* @returns The new length of the binary heap.
*/
push(...values: T[]): number {
for (const value of values) {
let index: number = this.#data.length;
let parent: number = getParentIndex(index);
this.#data.push(value);
while (
index !== 0 && this.compare(this.#data[index]!, this.#data[parent]!) < 0
index !== 0 &&
this.#compare(this.#data[index]!, this.#data[parent]!) < 0
) {
swap(this.#data, parent, index);
index = parent;
Expand All @@ -165,23 +328,78 @@ export class BinaryHeap<T> implements Iterable<T> {
return this.#data.length;
}

/** Removes all values from the binary heap. */
/**
* Remove all values from the binary heap.
*
* @example Clearing the binary heap
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = BinaryHeap.from([4, 1, 3, 5, 2]);
* heap.clear();
* [...heap]; // []
* ```
*/
clear() {
this.#data = [];
}

/** Checks if the binary heap is empty. */
/**
* Check if the binary heap is empty.
*
* @example Checking if the binary heap is empty
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = new BinaryHeap<number>();
* heap.isEmpty(); // true
* heap.push(42);
* heap.isEmpty(); // false
* ```
*
* @returns true if the binary heap is empty, otherwise false.
*/
isEmpty(): boolean {
return this.#data.length === 0;
}

/** Returns an iterator for retrieving and removing values from the binary heap. */
/**
* Create an iterator that retrieves values from the binary heap in order
* from greatest to least. The binary heap is drained in the process.
*
* To avoid draining the binary heap, create a copy using
* {@link BinaryHeap.from} and then call {@link BinaryHeap#drain} on the copy.
*
* @example Draining the binary heap
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = BinaryHeap.from([4, 1, 3, 5, 2]);
* [...heap.drain()]; // [ 5, 4, 3, 2, 1 ]
* [...heap.drain()]; // []
* ```
*
* @returns An iterator for retrieving and removing values from the binary heap.
*/
*drain(): IterableIterator<T> {
while (!this.isEmpty()) {
yield this.pop() as T;
}
}

/**
* Create an iterator that retrieves values from the binary heap in order
* from greatest to least. The binary heap is drained in the process.
*
* See {@link BinaryHeap#values}.
*
* @example Getting an iterator for the binary heap
* ```ts
* import { BinaryHeap } from "@std/data-structures";
* const heap = BinaryHeap.from([4, 1, 3, 5, 2]);
* [...heap]; // [ 5, 4, 3, 2, 1 ]
* [...heap]; // []
* ```
*
* @returns An iterator for retrieving and removing values from the binary heap.
*/
*[Symbol.iterator](): IterableIterator<T> {
yield* this.drain();
}
Expand Down
Loading

0 comments on commit ba1e8f2

Please sign in to comment.