Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions data_structures/_binary_search_node.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.

import { BSTNode } from "./bst_node.ts";

export type Direction = "left" | "right";

export class BinarySearchNode<T> {
left: BinarySearchNode<T> | null;
right: BinarySearchNode<T> | null;
parent: BinarySearchNode<T> | null;
value: T;
export class BinarySearchNode<T> extends BSTNode<T> {
declare left: BinarySearchNode<T> | null;
declare right: BinarySearchNode<T> | null;
declare parent: BinarySearchNode<T> | null;
declare value: T;

constructor(parent: BinarySearchNode<T> | null, value: T) {
this.left = null;
this.right = null;
this.parent = parent;
this.value = value;
super(parent, value);
}

static from<T>(node: BinarySearchNode<T>): BinarySearchNode<T> {
Expand Down
56 changes: 55 additions & 1 deletion data_structures/binary_search_tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// This module is browser compatible.

import { ascend } from "./comparators.ts";
import type { BSTNode } from "./bst_node.ts";
import { BinarySearchNode } from "./_binary_search_node.ts";
import { internals } from "./_binary_search_tree_internals.ts";

Expand Down Expand Up @@ -93,6 +94,7 @@
export class BinarySearchTree<T> implements Iterable<T> {
#root: BinarySearchNode<T> | null = null;
#size = 0;
#callback: ((node: BSTNode<T>) => void) | null = null;
#compare: (a: T, b: T) => number;

/**
Expand All @@ -103,13 +105,25 @@
*
* @param compare A custom comparison function to sort the values in the tree.
* By default, the values are sorted in ascending order.
* @param callback An optional callback function that is called whenever a change
* is made in the subtree of a node. This is guaranteed to be called in order from
* leaves to the root.
*/
constructor(compare: (a: T, b: T) => number = ascend) {
constructor(
compare: (a: T, b: T) => number = ascend,
callback?: (node: BSTNode<T>) => void,
) {
if (typeof compare !== "function") {
throw new TypeError(
"Cannot construct a BinarySearchTree: the 'compare' parameter is not a function, did you mean to call BinarySearchTree.from?",
);
}
if (callback && typeof callback !== "function") {
throw new TypeError(
"Cannot construct a BinarySearchTree: the 'callback' parameter is not a function",

Check warning on line 123 in data_structures/binary_search_tree.ts

View check run for this annotation

Codecov / codecov/patch

data_structures/binary_search_tree.ts#L122-L123

Added lines #L122 - L123 were not covered by tests
);
}
this.#callback = callback || null;
this.#compare = compare;
}

Expand Down Expand Up @@ -353,6 +367,10 @@
}
replacement[direction] = node;
node.parent = replacement;
if (this.#callback) {
this.#callback(node);
this.#callback(replacement);
}
}

#insertNode(
Expand All @@ -374,6 +392,14 @@
} else {
node[direction] = new Node(node, value);
this.#size++;
if (this.#callback) {
this.#callback(node);
let parentNode = node.parent;
while (parentNode) {
this.#callback(parentNode);
parentNode = parentNode.parent;
}
}
return node[direction];
}
}
Expand Down Expand Up @@ -410,9 +436,37 @@
}

this.#size--;
if (this.#callback) {
let parentNode = flaggedNode.parent;
while (parentNode) {
this.#callback(parentNode);
parentNode = parentNode.parent;
}
}
return flaggedNode;
}

/**
* Get the root node of the binary search tree.
*
* @example Getting the root node of the tree
* ```ts
* import { BinarySearchTree } from "@std/data-structures";
* import { assertEquals } from "@std/assert";
*
* const tree = new BinarySearchTree<number>();
*
* assertEquals(tree.insert(42), true);
* let root = tree.getRoot();
* assertEquals(root?.value, 42);
* ```
*
* @returns A reference to the root node of the binary search tree, or null if the tree is empty.
*/
getRoot(): BSTNode<T> | null {
return this.#root;
}

/**
* Add a value to the binary search tree if it does not already exist in the
* tree.
Expand Down
42 changes: 27 additions & 15 deletions data_structures/binary_search_tree_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
assertStrictEquals,
assertThrows,
} from "@std/assert";
import type { BSTNode } from "./bst_node.ts";
import { BinarySearchTree } from "./binary_search_tree.ts";
import { ascend, descend } from "./comparators.ts";

Expand All @@ -17,6 +18,14 @@ class MyMath {
interface Container {
id: number;
values: number[];
stSize: number;
}

function callback(n: BSTNode<Container>) {
let totalSize = 1;
totalSize += n.left?.value.stSize || 0;
totalSize += n.right?.value.stSize || 0;
n.value.stSize = totalSize;
}

Deno.test("BinarySearchTree throws if compare is not a function", () => {
Expand Down Expand Up @@ -271,28 +280,30 @@ Deno.test("BinarySearchTree contains objects", () => {
const tree: BinarySearchTree<Container> = new BinarySearchTree((
a: Container,
b: Container,
) => ascend(a.id, b.id));
) => ascend(a.id, b.id), callback);
const ids = [-10, 9, -1, 100, 1, 0, -100, 10, -9];

for (const [i, id] of ids.entries()) {
const newContainer: Container = { id, values: [] };
const newContainer: Container = { id, values: [], stSize: 1 };
assertEquals(tree.find(newContainer), null);
assertEquals(tree.insert(newContainer), true);
newContainer.values.push(i - 1, i, i + 1);
assertStrictEquals(tree.find({ id, values: [] }), newContainer);
assertStrictEquals(tree.find({ id, values: [], stSize: 1 }), newContainer);
assertEquals(tree.size, i + 1);
assertEquals(tree.isEmpty(), false);
assertEquals(tree.getRoot()?.value.stSize, i + 1);
}
for (const [i, id] of ids.entries()) {
const newContainer: Container = { id, values: [] };
assertEquals(tree.getRoot()?.value.stSize, ids.length);
for (const [_i, id] of ids.entries()) {
const newContainer: Container = { id, values: [], stSize: 1 };
assertEquals(
tree.find({ id } as Container),
{ id, values: [i - 1, i, i + 1] },
tree.find({ id } as Container)?.id,
id,
);
assertEquals(tree.insert(newContainer), false);
assertEquals(
tree.find({ id, values: [] }),
{ id, values: [i - 1, i, i + 1] },
tree.find({ id, values: [], stSize: 1 })?.id,
id,
);
assertEquals(tree.size, ids.length);
assertEquals(tree.isEmpty(), false);
Expand All @@ -310,18 +321,19 @@ Deno.test("BinarySearchTree contains objects", () => {
assertEquals(tree.size, ids.length - i);
assertEquals(tree.isEmpty(), false);
assertEquals(
tree.find({ id, values: [] }),
{ id, values: [i - 1, i, i + 1] },
tree.find({ id, values: [], stSize: 1 })?.id,
id,
);

assertEquals(tree.remove({ id, values: [] }), true);
assertEquals(tree.remove({ id, values: [], stSize: 1 }), true);
assertEquals(tree.getRoot()?.value.stSize || 0, ids.length - i - 1);
expected.splice(expected.indexOf(id), 1);
assertEquals([...tree].map((container) => container.id), expected);
assertEquals(tree.find({ id, values: [] }), null);
assertEquals(tree.find({ id, values: [], stSize: 1 }), null);

assertEquals(tree.remove({ id, values: [] }), false);
assertEquals(tree.remove({ id, values: [], stSize: 1 }), false);
assertEquals([...tree].map((container) => container.id), expected);
assertEquals(tree.find({ id, values: [] }), null);
assertEquals(tree.find({ id, values: [], stSize: 1 }), null);
}
assertEquals(tree.size, 0);
assertEquals(tree.isEmpty(), true);
Expand Down
113 changes: 113 additions & 0 deletions data_structures/bst_node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.

/**
* A generic Binary Search Tree (BST) node class.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nicely documented 👍

*
* @example Creating a new BSTNode.
* ```ts
* import { BSTNode } from "@std/data-structures";
* import { assertEquals } from "@std/assert";
*
* const node = new BSTNode<number>(null, 42);
* assertEquals(node.value, 42);
* assertEquals(node.left, null);
* assertEquals(node.right, null);
* assertEquals(node.parent, null);
* ```
*
* @typeparam T The type of the values stored in the binary tree.
*/
export class BSTNode<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually avoid using this type of acronym in API names. BinarySearchTree used to be BSTree but we renamed it later #2400 I think this should be named BinarySearchTreeNode.

Also I think this should be typescript interface instead of class. We can avoid unnecessary runtime overhead by making this interface

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, renamed to BinarySearchTreeNode and made into an interface.

/**
* The left child node, or null if there is no left child.
*
* @example Checking the left child of a node in a binary search tree.
* ```ts
* import { BinarySearchTree } from "@std/data-structures";
* import { assertEquals } from "@std/assert";
*
* const tree = new BinarySearchTree<number>();
*
* assertEquals(tree.insert(42), true);
* assertEquals(tree.insert(21), true);
*
* const root = tree.getRoot();
* const leftChild = root?.left;
*
* assertEquals(leftChild?.value, 21);
* ```
*/
left: BSTNode<T> | null;

/**
* The right child node, or null if there is no right child.
*
* @example Checking the right child of a node in a binary search tree.
* ```ts
* import { BinarySearchTree } from "@std/data-structures";
* import { assertEquals } from "@std/assert";
*
* const tree = new BinarySearchTree<number>();
*
* assertEquals(tree.insert(21), true);
* assertEquals(tree.insert(42), true);
*
* const root = tree.getRoot();
* const leftChild = root?.left;
*
* assertEquals(leftChild?.value, 42);
* ```
*/
right: BSTNode<T> | null;

/**
* The parent of this node, or null if there is no parent.
*
* @example Checking the parent of a node in a binary search tree.
* ```ts
* import { BinarySearchTree } from "@std/data-structures";
* import { assertEquals } from "@std/assert";
*
* const tree = new BinarySearchTree<number>();
*
* assertEquals(tree.insert(42), true);
* assertEquals(tree.insert(21), true);
*
* const root = tree.getRoot();
* const leftChild = root?.left;
*
* assertEquals(leftChild?.parent?.value, 42);
* ```
*/
parent: BSTNode<T> | null;

/**
* The value stored at this node.
*
* @example Accessing the value of a node in a binary search tree.
* ```ts
* import { BinarySearchTree } from "@std/data-structures";
* import { assertEquals } from "@std/assert";
*
* const tree = new BinarySearchTree<number>();
* assertEquals(tree.insert(42), true);
*
* const root = tree.getRoot();
* assertEquals(root?.value, 42);
* ```
*/
value: T;

/**
* Creates a new BSTNode.
* @param parent The parent node, or null if this is the root node.
* @param value The value of the node.
*/
constructor(parent: BSTNode<T> | null, value: T) {
this.left = null;
this.right = null;
this.parent = parent;
this.value = value;
}
}
26 changes: 26 additions & 0 deletions data_structures/bst_node_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.

import { assertStrictEquals } from "@std/assert";
import { BSTNode } from "./bst_node.ts";

let parent: BSTNode<number>;
let child: BSTNode<number>;
function beforeEach() {
parent = new BSTNode(null, 5);
child = new BSTNode(parent, 7);
parent.right = child;
}

Deno.test("BSTNode", () => {
beforeEach();
assertStrictEquals(parent.parent, null);
assertStrictEquals(parent.left, null);
assertStrictEquals(parent.right, child);
assertStrictEquals(parent.value, 5);

assertStrictEquals(child.parent, parent);
assertStrictEquals(child.left, null);
assertStrictEquals(child.right, null);
assertStrictEquals(child.value, 7);
});
1 change: 1 addition & 0 deletions data_structures/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@

export * from "./binary_heap.ts";
export * from "./binary_search_tree.ts";
export * from "./bst_node.ts";
export * from "./comparators.ts";
export * from "./red_black_tree.ts";
Loading
Loading