From d9443d57d5cdfe99387b538932518f4e5932d421 Mon Sep 17 00:00:00 2001 From: Abhinav Tamaskar Date: Fri, 20 Jun 2025 14:23:03 -0700 Subject: [PATCH 1/6] feat: add callback functionality for binary search tree Signed-off-by: Abhinav Tamaskar --- data_structures/_test_utils.ts | 1 + data_structures/binary_search_tree.ts | 35 ++++++++++++++++++- data_structures/binary_search_tree_test.ts | 40 ++++++++++++++-------- data_structures/red_black_tree.ts | 10 ++++-- data_structures/red_black_tree_test.ts | 39 +++++++++++++-------- 5 files changed, 94 insertions(+), 31 deletions(-) diff --git a/data_structures/_test_utils.ts b/data_structures/_test_utils.ts index 3c26b5e66f08..cc8367633cf9 100644 --- a/data_structures/_test_utils.ts +++ b/data_structures/_test_utils.ts @@ -8,4 +8,5 @@ export class MyMath { export interface Container { id: number; values: number[]; + st_size: number; } diff --git a/data_structures/binary_search_tree.ts b/data_structures/binary_search_tree.ts index 209653d79ec8..93736ecf2a32 100644 --- a/data_structures/binary_search_tree.ts +++ b/data_structures/binary_search_tree.ts @@ -93,6 +93,7 @@ type Direction = "left" | "right"; export class BinarySearchTree implements Iterable { #root: BinarySearchNode | null = null; #size = 0; + #callback: ((node: BinarySearchNode) => void) | null = null; #compare: (a: T, b: T) => number; /** @@ -104,12 +105,18 @@ export class BinarySearchTree implements Iterable { * @param compare A custom comparison function to sort the values in the tree. * By default, the values are sorted in ascending order. */ - constructor(compare: (a: T, b: T) => number = ascend) { + constructor(compare: (a: T, b: T) => number = ascend, callback?: (node: BinarySearchNode) => 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.", + ); + } + this.#callback = callback || null; this.#compare = compare; } @@ -353,6 +360,10 @@ export class BinarySearchTree implements Iterable { } replacement[direction] = node; node.parent = replacement; + if (this.#callback) { + this.#callback(node); + this.#callback(replacement); + } } #insertNode( @@ -374,6 +385,14 @@ export class BinarySearchTree implements Iterable { } 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]; } } @@ -410,9 +429,23 @@ export class BinarySearchTree implements Iterable { } 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. + */ + getRoot(): BinarySearchNode | null { + return this.#root; + } + /** * Add a value to the binary search tree if it does not already exist in the * tree. diff --git a/data_structures/binary_search_tree_test.ts b/data_structures/binary_search_tree_test.ts index 4ae14e4f170d..bec260a48bce 100644 --- a/data_structures/binary_search_tree_test.ts +++ b/data_structures/binary_search_tree_test.ts @@ -5,6 +5,7 @@ import { assertStrictEquals, assertThrows, } from "@std/assert"; +import { BinarySearchNode } from "./_binary_search_node.ts"; import { BinarySearchTree } from "./binary_search_tree.ts"; import { ascend, descend } from "./comparators.ts"; @@ -17,6 +18,14 @@ class MyMath { interface Container { id: number; values: number[]; + st_size: number; +} + +function callback(n: BinarySearchNode) { + let total_size = 1; + total_size += n.left?.value.st_size || 0; + total_size += n.right?.value.st_size || 0; + n.value.st_size = total_size; } Deno.test("BinarySearchTree throws if compare is not a function", () => { @@ -271,28 +280,30 @@ Deno.test("BinarySearchTree contains objects", () => { const tree: BinarySearchTree = 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: [], st_size: 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: [], st_size: 1 }), newContainer); assertEquals(tree.size, i + 1); assertEquals(tree.isEmpty(), false); + assertEquals(tree.getRoot()?.value.st_size, i+1); } + assertEquals(tree.getRoot()?.value.st_size, ids.length); for (const [i, id] of ids.entries()) { - const newContainer: Container = { id, values: [] }; + const newContainer: Container = { id, values: [], st_size: 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: [], st_size: 1 })?.id, + id, ); assertEquals(tree.size, ids.length); assertEquals(tree.isEmpty(), false); @@ -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: [], st_size: 1 })?.id, + id, ); - assertEquals(tree.remove({ id, values: [] }), true); + assertEquals(tree.remove({ id, values: [], st_size: 1 }), true); + assertEquals(tree.getRoot()?.value.st_size || 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: [], st_size: 1 }), null); - assertEquals(tree.remove({ id, values: [] }), false); + assertEquals(tree.remove({ id, values: [], st_size: 1 }), false); assertEquals([...tree].map((container) => container.id), expected); - assertEquals(tree.find({ id, values: [] }), null); + assertEquals(tree.find({ id, values: [], st_size: 1 }), null); } assertEquals(tree.size, 0); assertEquals(tree.isEmpty(), true); diff --git a/data_structures/red_black_tree.ts b/data_structures/red_black_tree.ts index 21c0751ee76b..a5597b6642e8 100644 --- a/data_structures/red_black_tree.ts +++ b/data_structures/red_black_tree.ts @@ -3,6 +3,7 @@ import { ascend } from "./comparators.ts"; import { BinarySearchTree } from "./binary_search_tree.ts"; +import { BinarySearchNode } from "./_binary_search_node.ts"; import { type Direction, RedBlackNode } from "./_red_black_node.ts"; import { internals } from "./_binary_search_tree_internals.ts"; @@ -107,13 +108,18 @@ export class RedBlackTree extends BinarySearchTree { * * @param compare A custom comparison function for the values. The default comparison function sorts by ascending order. */ - constructor(compare: (a: T, b: T) => number = ascend) { + constructor(compare: (a: T, b: T) => number = ascend, callback?: (node: BinarySearchNode) => void) { if (typeof compare !== "function") { throw new TypeError( "Cannot construct a RedBlackTree: the 'compare' parameter is not a function, did you mean to call RedBlackTree.from?", ); } - super(compare); + if (callback && typeof callback !== "function") { + throw new TypeError( + "Cannot construct a BinarySearchTree: the 'callback' parameter is not a function.", + ); + } + super(compare, callback); } /** diff --git a/data_structures/red_black_tree_test.ts b/data_structures/red_black_tree_test.ts index 9b518588833f..ced3d8c3379c 100644 --- a/data_structures/red_black_tree_test.ts +++ b/data_structures/red_black_tree_test.ts @@ -1,5 +1,7 @@ // Copyright 2018-2025 the Deno authors. MIT license. import { assertEquals, assertStrictEquals, assertThrows } from "@std/assert"; +import { BinarySearchNode } from "./_binary_search_node.ts"; +import { RedBlackNode } from "./_red_black_node.ts"; import { RedBlackTree } from "./red_black_tree.ts"; import { ascend, descend } from "./comparators.ts"; import { type Container, MyMath } from "./_test_utils.ts"; @@ -252,32 +254,40 @@ Deno.test("RedBlackTree works as exepcted with descend comparator", () => { } }); +function callback(n: BinarySearchNode) { + let total_size = 1; + total_size += n.left?.value.st_size || 0; + total_size += n.right?.value.st_size || 0; + n.value.st_size = total_size; +} + Deno.test("RedBlackTree works with object items", () => { const tree: RedBlackTree = new RedBlackTree(( a: Container, b: Container, - ) => ascend(a.id, b.id)); + ) => ascend(a.id, b.id), callback); const ids: number[] = [-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: [], st_size: 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: [], st_size: 1 }), newContainer); assertEquals(tree.size, i + 1); + assertEquals(tree.getRoot()?.value.st_size, i + 1); assertEquals(tree.isEmpty(), false); } for (const [i, id] of ids.entries()) { - const newContainer: Container = { id, values: [] }; + const newContainer: Container = { id, values: [], st_size: 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: [], st_size: 1 })?.id, + id, ); assertEquals(tree.size, ids.length); assertEquals(tree.isEmpty(), false); @@ -295,18 +305,19 @@ Deno.test("RedBlackTree works with object items", () => { 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: [], st_size: 1 })?.id, + id, ); - assertEquals(tree.remove({ id, values: [] }), true); + assertEquals(tree.remove({ id, values: [], st_size: 1 }), true); + assertEquals(tree.getRoot()?.value.st_size || 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: [], st_size: 1 }), null); - assertEquals(tree.remove({ id, values: [] }), false); + assertEquals(tree.remove({ id, values: [], st_size: 1 }), false); assertEquals([...tree].map((container) => container.id), expected); - assertEquals(tree.find({ id, values: [] }), null); + assertEquals(tree.find({ id, values: [], st_size: 1 }), null); } assertEquals(tree.size, 0); assertEquals(tree.isEmpty(), true); From e3231dbeb194ca36b14d03fbd091d4f18e696c13 Mon Sep 17 00:00:00 2001 From: Abhinav Tamaskar Date: Sun, 22 Jun 2025 21:03:32 -0700 Subject: [PATCH 2/6] address comments --- data_structures/_test_utils.ts | 1 - data_structures/binary_search_tree.ts | 8 ++++- data_structures/binary_search_tree_test.ts | 32 +++++++++---------- data_structures/red_black_tree.ts | 6 +++- data_structures/red_black_tree_test.ts | 36 +++++++++++++--------- 5 files changed, 49 insertions(+), 34 deletions(-) diff --git a/data_structures/_test_utils.ts b/data_structures/_test_utils.ts index cc8367633cf9..3c26b5e66f08 100644 --- a/data_structures/_test_utils.ts +++ b/data_structures/_test_utils.ts @@ -8,5 +8,4 @@ export class MyMath { export interface Container { id: number; values: number[]; - st_size: number; } diff --git a/data_structures/binary_search_tree.ts b/data_structures/binary_search_tree.ts index 93736ecf2a32..f08433aa2707 100644 --- a/data_structures/binary_search_tree.ts +++ b/data_structures/binary_search_tree.ts @@ -104,8 +104,14 @@ export class BinarySearchTree implements Iterable { * * @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, callback?: (node: BinarySearchNode) => void) { + constructor( + compare: (a: T, b: T) => number = ascend, + callback?: (node: BinarySearchNode) => 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?", diff --git a/data_structures/binary_search_tree_test.ts b/data_structures/binary_search_tree_test.ts index bec260a48bce..4a74d385595c 100644 --- a/data_structures/binary_search_tree_test.ts +++ b/data_structures/binary_search_tree_test.ts @@ -18,14 +18,14 @@ class MyMath { interface Container { id: number; values: number[]; - st_size: number; + stSize: number; } function callback(n: BinarySearchNode) { let total_size = 1; - total_size += n.left?.value.st_size || 0; - total_size += n.right?.value.st_size || 0; - n.value.st_size = total_size; + total_size += n.left?.value.stSize || 0; + total_size += n.right?.value.stSize || 0; + n.value.stSize = total_size; } Deno.test("BinarySearchTree throws if compare is not a function", () => { @@ -284,25 +284,25 @@ Deno.test("BinarySearchTree contains objects", () => { const ids = [-10, 9, -1, 100, 1, 0, -100, 10, -9]; for (const [i, id] of ids.entries()) { - const newContainer: Container = { id, values: [], st_size: 1 }; + 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: [], st_size: 1 }), newContainer); + assertStrictEquals(tree.find({ id, values: [], stSize: 1 }), newContainer); assertEquals(tree.size, i + 1); assertEquals(tree.isEmpty(), false); - assertEquals(tree.getRoot()?.value.st_size, i+1); + assertEquals(tree.getRoot()?.value.stSize, i + 1); } - assertEquals(tree.getRoot()?.value.st_size, ids.length); + assertEquals(tree.getRoot()?.value.stSize, ids.length); for (const [i, id] of ids.entries()) { - const newContainer: Container = { id, values: [], st_size: 1}; + const newContainer: Container = { id, values: [], stSize: 1 }; assertEquals( tree.find({ id } as Container)?.id, id, ); assertEquals(tree.insert(newContainer), false); assertEquals( - tree.find({ id, values: [], st_size: 1 })?.id, + tree.find({ id, values: [], stSize: 1 })?.id, id, ); assertEquals(tree.size, ids.length); @@ -321,19 +321,19 @@ Deno.test("BinarySearchTree contains objects", () => { assertEquals(tree.size, ids.length - i); assertEquals(tree.isEmpty(), false); assertEquals( - tree.find({ id, values: [], st_size: 1 })?.id, + tree.find({ id, values: [], stSize: 1 })?.id, id, ); - assertEquals(tree.remove({ id, values: [], st_size: 1 }), true); - assertEquals(tree.getRoot()?.value.st_size || 0, ids.length - i - 1); + 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: [], st_size: 1 }), null); + assertEquals(tree.find({ id, values: [], stSize: 1 }), null); - assertEquals(tree.remove({ id, values: [], st_size: 1 }), false); + assertEquals(tree.remove({ id, values: [], stSize: 1 }), false); assertEquals([...tree].map((container) => container.id), expected); - assertEquals(tree.find({ id, values: [], st_size: 1 }), null); + assertEquals(tree.find({ id, values: [], stSize: 1 }), null); } assertEquals(tree.size, 0); assertEquals(tree.isEmpty(), true); diff --git a/data_structures/red_black_tree.ts b/data_structures/red_black_tree.ts index a5597b6642e8..571e12b301fa 100644 --- a/data_structures/red_black_tree.ts +++ b/data_structures/red_black_tree.ts @@ -107,8 +107,12 @@ export class RedBlackTree extends BinarySearchTree { * Construct an empty red-black tree. * * @param compare A custom comparison function for the values. The default comparison function sorts by 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, callback?: (node: BinarySearchNode) => void) { + constructor( + compare: (a: T, b: T) => number = ascend, + callback?: (node: BinarySearchNode) => void, + ) { if (typeof compare !== "function") { throw new TypeError( "Cannot construct a RedBlackTree: the 'compare' parameter is not a function, did you mean to call RedBlackTree.from?", diff --git a/data_structures/red_black_tree_test.ts b/data_structures/red_black_tree_test.ts index ced3d8c3379c..a7786f41424d 100644 --- a/data_structures/red_black_tree_test.ts +++ b/data_structures/red_black_tree_test.ts @@ -4,7 +4,13 @@ import { BinarySearchNode } from "./_binary_search_node.ts"; import { RedBlackNode } from "./_red_black_node.ts"; import { RedBlackTree } from "./red_black_tree.ts"; import { ascend, descend } from "./comparators.ts"; -import { type Container, MyMath } from "./_test_utils.ts"; +import { MyMath } from "./_test_utils.ts"; + +export interface Container { + id: number; + values: number[]; + stSize: number; +} Deno.test("RedBlackTree throws if compare is not a function", () => { assertThrows( @@ -256,9 +262,9 @@ Deno.test("RedBlackTree works as exepcted with descend comparator", () => { function callback(n: BinarySearchNode) { let total_size = 1; - total_size += n.left?.value.st_size || 0; - total_size += n.right?.value.st_size || 0; - n.value.st_size = total_size; + total_size += n.left?.value.stSize || 0; + total_size += n.right?.value.stSize || 0; + n.value.stSize = total_size; } Deno.test("RedBlackTree works with object items", () => { @@ -269,24 +275,24 @@ Deno.test("RedBlackTree works with object items", () => { const ids: number[] = [-10, 9, -1, 100, 1, 0, -100, 10, -9]; for (const [i, id] of ids.entries()) { - const newContainer: Container = { id, values: [], st_size: 1 }; + 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: [], st_size: 1 }), newContainer); + assertStrictEquals(tree.find({ id, values: [], stSize: 1 }), newContainer); assertEquals(tree.size, i + 1); - assertEquals(tree.getRoot()?.value.st_size, i + 1); + assertEquals(tree.getRoot()?.value.stSize, i + 1); assertEquals(tree.isEmpty(), false); } for (const [i, id] of ids.entries()) { - const newContainer: Container = { id, values: [], st_size: 1 }; + const newContainer: Container = { id, values: [], stSize: 1 }; assertEquals( tree.find({ id } as Container)?.id, id, ); assertEquals(tree.insert(newContainer), false); assertEquals( - tree.find({ id, values: [], st_size: 1 })?.id, + tree.find({ id, values: [], stSize: 1 })?.id, id, ); assertEquals(tree.size, ids.length); @@ -305,19 +311,19 @@ Deno.test("RedBlackTree works with object items", () => { assertEquals(tree.size, ids.length - i); assertEquals(tree.isEmpty(), false); assertEquals( - tree.find({ id, values: [], st_size: 1 })?.id, + tree.find({ id, values: [], stSize: 1 })?.id, id, ); - assertEquals(tree.remove({ id, values: [], st_size: 1 }), true); - assertEquals(tree.getRoot()?.value.st_size || 0, ids.length - i - 1); + 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: [], st_size: 1 }), null); + assertEquals(tree.find({ id, values: [], stSize: 1 }), null); - assertEquals(tree.remove({ id, values: [], st_size: 1 }), false); + assertEquals(tree.remove({ id, values: [], stSize: 1 }), false); assertEquals([...tree].map((container) => container.id), expected); - assertEquals(tree.find({ id, values: [], st_size: 1 }), null); + assertEquals(tree.find({ id, values: [], stSize: 1 }), null); } assertEquals(tree.size, 0); assertEquals(tree.isEmpty(), true); From b8b071ca4af99a0e8bd2265ba532e034e3b34366 Mon Sep 17 00:00:00 2001 From: Abhinav Tamaskar Date: Sun, 22 Jun 2025 22:20:20 -0700 Subject: [PATCH 3/6] add BSTNode class --- data_structures/_binary_search_node.ts | 17 ++++++++------- data_structures/binary_search_tree.ts | 5 +++-- data_structures/binary_search_tree_test.ts | 14 ++++++------- data_structures/bst_node.ts | 16 +++++++++++++++ data_structures/bst_node_test.ts | 24 ++++++++++++++++++++++ data_structures/red_black_tree.ts | 6 +++--- data_structures/red_black_tree_test.ts | 15 +++++++------- 7 files changed, 68 insertions(+), 29 deletions(-) create mode 100644 data_structures/bst_node.ts create mode 100644 data_structures/bst_node_test.ts diff --git a/data_structures/_binary_search_node.ts b/data_structures/_binary_search_node.ts index f91ba2ff4911..64019d58b56e 100644 --- a/data_structures/_binary_search_node.ts +++ b/data_structures/_binary_search_node.ts @@ -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 { - left: BinarySearchNode | null; - right: BinarySearchNode | null; - parent: BinarySearchNode | null; - value: T; +export class BinarySearchNode extends BSTNode { + declare left: BinarySearchNode | null; + declare right: BinarySearchNode | null; + declare parent: BinarySearchNode | null; + declare value: T; constructor(parent: BinarySearchNode | null, value: T) { - this.left = null; - this.right = null; - this.parent = parent; - this.value = value; + super(parent, value); } static from(node: BinarySearchNode): BinarySearchNode { diff --git a/data_structures/binary_search_tree.ts b/data_structures/binary_search_tree.ts index f08433aa2707..2cece273df4b 100644 --- a/data_structures/binary_search_tree.ts +++ b/data_structures/binary_search_tree.ts @@ -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"; @@ -110,7 +111,7 @@ export class BinarySearchTree implements Iterable { */ constructor( compare: (a: T, b: T) => number = ascend, - callback?: (node: BinarySearchNode) => void, + callback?: (node: BSTNode) => void, ) { if (typeof compare !== "function") { throw new TypeError( @@ -119,7 +120,7 @@ export class BinarySearchTree implements Iterable { } if (callback && typeof callback !== "function") { throw new TypeError( - "Cannot construct a BinarySearchTree: the 'callback' parameter is not a function.", + "Cannot construct a BinarySearchTree: the 'callback' parameter is not a function", ); } this.#callback = callback || null; diff --git a/data_structures/binary_search_tree_test.ts b/data_structures/binary_search_tree_test.ts index 4a74d385595c..c9322d195371 100644 --- a/data_structures/binary_search_tree_test.ts +++ b/data_structures/binary_search_tree_test.ts @@ -5,7 +5,7 @@ import { assertStrictEquals, assertThrows, } from "@std/assert"; -import { BinarySearchNode } from "./_binary_search_node.ts"; +import type { BSTNode } from "./bst_node.ts"; import { BinarySearchTree } from "./binary_search_tree.ts"; import { ascend, descend } from "./comparators.ts"; @@ -21,11 +21,11 @@ interface Container { stSize: number; } -function callback(n: BinarySearchNode) { - let total_size = 1; - total_size += n.left?.value.stSize || 0; - total_size += n.right?.value.stSize || 0; - n.value.stSize = total_size; +function callback(n: BSTNode) { + 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", () => { @@ -294,7 +294,7 @@ Deno.test("BinarySearchTree contains objects", () => { assertEquals(tree.getRoot()?.value.stSize, i + 1); } assertEquals(tree.getRoot()?.value.stSize, ids.length); - for (const [i, id] of ids.entries()) { + for (const [_i, id] of ids.entries()) { const newContainer: Container = { id, values: [], stSize: 1 }; assertEquals( tree.find({ id } as Container)?.id, diff --git a/data_structures/bst_node.ts b/data_structures/bst_node.ts new file mode 100644 index 000000000000..f316acf9eccf --- /dev/null +++ b/data_structures/bst_node.ts @@ -0,0 +1,16 @@ +// Copyright 2025 the Deno authors. MIT license. +// This module is browser compatible. + +export class BSTNode { + left: BSTNode | null; + right: BSTNode | null; + parent: BSTNode | null; + value: T; + + constructor(parent: BSTNode | null, value: T) { + this.left = null; + this.right = null; + this.parent = parent; + this.value = value; + } +} diff --git a/data_structures/bst_node_test.ts b/data_structures/bst_node_test.ts new file mode 100644 index 000000000000..29f7fafc35bb --- /dev/null +++ b/data_structures/bst_node_test.ts @@ -0,0 +1,24 @@ +// Copyright 2025 the Deno authors. MIT license. +import { assertStrictEquals } from "@std/assert"; +import { BSTNode } from "./bst_node.ts"; + +let parent: BSTNode; +let child: BSTNode; +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); +}); diff --git a/data_structures/red_black_tree.ts b/data_structures/red_black_tree.ts index 571e12b301fa..7d3b83392e76 100644 --- a/data_structures/red_black_tree.ts +++ b/data_structures/red_black_tree.ts @@ -3,7 +3,7 @@ import { ascend } from "./comparators.ts"; import { BinarySearchTree } from "./binary_search_tree.ts"; -import { BinarySearchNode } from "./_binary_search_node.ts"; +import type { BSTNode } from "./bst_node.ts"; import { type Direction, RedBlackNode } from "./_red_black_node.ts"; import { internals } from "./_binary_search_tree_internals.ts"; @@ -111,7 +111,7 @@ export class RedBlackTree extends BinarySearchTree { */ constructor( compare: (a: T, b: T) => number = ascend, - callback?: (node: BinarySearchNode) => void, + callback?: (node: BSTNode) => void, ) { if (typeof compare !== "function") { throw new TypeError( @@ -120,7 +120,7 @@ export class RedBlackTree extends BinarySearchTree { } if (callback && typeof callback !== "function") { throw new TypeError( - "Cannot construct a BinarySearchTree: the 'callback' parameter is not a function.", + "Cannot construct a RedBlackTree: the 'callback' parameter is not a function", ); } super(compare, callback); diff --git a/data_structures/red_black_tree_test.ts b/data_structures/red_black_tree_test.ts index a7786f41424d..00dfa9723ebe 100644 --- a/data_structures/red_black_tree_test.ts +++ b/data_structures/red_black_tree_test.ts @@ -1,7 +1,6 @@ // Copyright 2018-2025 the Deno authors. MIT license. import { assertEquals, assertStrictEquals, assertThrows } from "@std/assert"; -import { BinarySearchNode } from "./_binary_search_node.ts"; -import { RedBlackNode } from "./_red_black_node.ts"; +import type { BSTNode } from "./bst_node.ts"; import { RedBlackTree } from "./red_black_tree.ts"; import { ascend, descend } from "./comparators.ts"; import { MyMath } from "./_test_utils.ts"; @@ -260,11 +259,11 @@ Deno.test("RedBlackTree works as exepcted with descend comparator", () => { } }); -function callback(n: BinarySearchNode) { - let total_size = 1; - total_size += n.left?.value.stSize || 0; - total_size += n.right?.value.stSize || 0; - n.value.stSize = total_size; +function callback(n: BSTNode) { + let totalSize = 1; + totalSize += n.left?.value.stSize || 0; + totalSize += n.right?.value.stSize || 0; + n.value.stSize = totalSize; } Deno.test("RedBlackTree works with object items", () => { @@ -284,7 +283,7 @@ Deno.test("RedBlackTree works with object items", () => { assertEquals(tree.getRoot()?.value.stSize, i + 1); assertEquals(tree.isEmpty(), false); } - for (const [i, id] of ids.entries()) { + for (const [_i, id] of ids.entries()) { const newContainer: Container = { id, values: [], stSize: 1 }; assertEquals( tree.find({ id } as Container)?.id, From 20f91c21cc97e18d82d2604dd99b780eb0d4e9e6 Mon Sep 17 00:00:00 2001 From: Abhinav Tamaskar Date: Sun, 22 Jun 2025 22:54:19 -0700 Subject: [PATCH 4/6] documentation --- data_structures/binary_search_tree.ts | 16 ++++- data_structures/bst_node.ts | 99 ++++++++++++++++++++++++++- data_structures/bst_node_test.ts | 4 +- data_structures/mod.ts | 1 + 4 files changed, 117 insertions(+), 3 deletions(-) diff --git a/data_structures/binary_search_tree.ts b/data_structures/binary_search_tree.ts index 2cece273df4b..d4394a7dd313 100644 --- a/data_structures/binary_search_tree.ts +++ b/data_structures/binary_search_tree.ts @@ -448,8 +448,22 @@ export class BinarySearchTree implements Iterable { /** * 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(); + * + * 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(): BinarySearchNode | null { + getRoot(): BSTNode | null { return this.#root; } diff --git a/data_structures/bst_node.ts b/data_structures/bst_node.ts index f316acf9eccf..e214b33b9294 100644 --- a/data_structures/bst_node.ts +++ b/data_structures/bst_node.ts @@ -1,12 +1,109 @@ -// Copyright 2025 the Deno authors. MIT license. +// Copyright 2018-2025 the Deno authors. MIT license. // This module is browser compatible. +/** + * A generic Binary Search Tree (BST) node class. + * + * @example Creating a new BSTNode. + * ```ts + * import { BSTNode } from "@std/data-structures"; + * import { assertEquals } from "@std/assert"; + * + * const node = new BSTNode(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 { + /** + * 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(); + * + * assertEquals(tree.insert(42), true); + * assertEquals(tree.insert(21), true); + * + * const root = tree.getRoot(); + * const leftChild = root?.left; + * + * assertEquals(leftChild?.value, 21); + * ``` + */ left: BSTNode | 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(); + * + * assertEquals(tree.insert(21), true); + * assertEquals(tree.insert(42), true); + * + * const root = tree.getRoot(); + * const leftChild = root?.left; + * + * assertEquals(leftChild?.value, 42); + * ``` + */ right: BSTNode | 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(); + * + * 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 | 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(); + * 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 | null, value: T) { this.left = null; this.right = null; diff --git a/data_structures/bst_node_test.ts b/data_structures/bst_node_test.ts index 29f7fafc35bb..25eb2a2e5552 100644 --- a/data_structures/bst_node_test.ts +++ b/data_structures/bst_node_test.ts @@ -1,4 +1,6 @@ -// Copyright 2025 the Deno authors. MIT license. +// Copyright 2018-2025 the Deno authors. MIT license. +// This module is browser compatible. + import { assertStrictEquals } from "@std/assert"; import { BSTNode } from "./bst_node.ts"; diff --git a/data_structures/mod.ts b/data_structures/mod.ts index cd606c43f1bf..1af1ddcdbdd4 100644 --- a/data_structures/mod.ts +++ b/data_structures/mod.ts @@ -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"; From a2fd74c77154c15eef8d685a0094a87f9591af15 Mon Sep 17 00:00:00 2001 From: Abhinav Tamaskar Date: Sun, 22 Jun 2025 22:57:45 -0700 Subject: [PATCH 5/6] type --- data_structures/binary_search_tree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structures/binary_search_tree.ts b/data_structures/binary_search_tree.ts index d4394a7dd313..ed3ac20a8304 100644 --- a/data_structures/binary_search_tree.ts +++ b/data_structures/binary_search_tree.ts @@ -94,7 +94,7 @@ type Direction = "left" | "right"; export class BinarySearchTree implements Iterable { #root: BinarySearchNode | null = null; #size = 0; - #callback: ((node: BinarySearchNode) => void) | null = null; + #callback: ((node: BSTNode) => void) | null = null; #compare: (a: T, b: T) => number; /** From fd4426ae3de9c7282b8bb9c78a1230dd7d45a1d2 Mon Sep 17 00:00:00 2001 From: Abhinav Tamaskar Date: Mon, 23 Jun 2025 09:21:33 -0700 Subject: [PATCH 6/6] interface --- data_structures/_binary_search_node.ts | 9 +++-- data_structures/binary_search_tree.ts | 8 ++--- ...bst_node.ts => binary_search_tree_node.ts} | 35 ++++++++----------- data_structures/binary_search_tree_test.ts | 4 +-- data_structures/bst_node_test.ts | 26 -------------- data_structures/mod.ts | 2 +- data_structures/red_black_tree.ts | 4 +-- data_structures/red_black_tree_test.ts | 4 +-- 8 files changed, 32 insertions(+), 60 deletions(-) rename data_structures/{bst_node.ts => binary_search_tree_node.ts} (80%) delete mode 100644 data_structures/bst_node_test.ts diff --git a/data_structures/_binary_search_node.ts b/data_structures/_binary_search_node.ts index 64019d58b56e..d7133daf5026 100644 --- a/data_structures/_binary_search_node.ts +++ b/data_structures/_binary_search_node.ts @@ -1,18 +1,21 @@ // Copyright 2018-2025 the Deno authors. MIT license. // This module is browser compatible. -import { BSTNode } from "./bst_node.ts"; +import type { BinarySearchTreeNode } from "./binary_search_tree_node.ts"; export type Direction = "left" | "right"; -export class BinarySearchNode extends BSTNode { +export class BinarySearchNode implements BinarySearchTreeNode { declare left: BinarySearchNode | null; declare right: BinarySearchNode | null; declare parent: BinarySearchNode | null; declare value: T; constructor(parent: BinarySearchNode | null, value: T) { - super(parent, value); + this.left = null; + this.right = null; + this.parent = parent; + this.value = value; } static from(node: BinarySearchNode): BinarySearchNode { diff --git a/data_structures/binary_search_tree.ts b/data_structures/binary_search_tree.ts index ed3ac20a8304..aee4b25d4ec4 100644 --- a/data_structures/binary_search_tree.ts +++ b/data_structures/binary_search_tree.ts @@ -2,7 +2,7 @@ // This module is browser compatible. import { ascend } from "./comparators.ts"; -import type { BSTNode } from "./bst_node.ts"; +import type { BinarySearchTreeNode } from "./binary_search_tree_node.ts"; import { BinarySearchNode } from "./_binary_search_node.ts"; import { internals } from "./_binary_search_tree_internals.ts"; @@ -94,7 +94,7 @@ type Direction = "left" | "right"; export class BinarySearchTree implements Iterable { #root: BinarySearchNode | null = null; #size = 0; - #callback: ((node: BSTNode) => void) | null = null; + #callback: ((node: BinarySearchTreeNode) => void) | null = null; #compare: (a: T, b: T) => number; /** @@ -111,7 +111,7 @@ export class BinarySearchTree implements Iterable { */ constructor( compare: (a: T, b: T) => number = ascend, - callback?: (node: BSTNode) => void, + callback?: (node: BinarySearchTreeNode) => void, ) { if (typeof compare !== "function") { throw new TypeError( @@ -463,7 +463,7 @@ export class BinarySearchTree implements Iterable { * * @returns A reference to the root node of the binary search tree, or null if the tree is empty. */ - getRoot(): BSTNode | null { + getRoot(): BinarySearchTreeNode | null { return this.#root; } diff --git a/data_structures/bst_node.ts b/data_structures/binary_search_tree_node.ts similarity index 80% rename from data_structures/bst_node.ts rename to data_structures/binary_search_tree_node.ts index e214b33b9294..bb4b216923d3 100644 --- a/data_structures/bst_node.ts +++ b/data_structures/binary_search_tree_node.ts @@ -2,14 +2,21 @@ // This module is browser compatible. /** - * A generic Binary Search Tree (BST) node class. + * A generic Binary Search Tree (BST) node interface. + * It is implemented by the internal node classes of the binary search tree and + * red black tree. * - * @example Creating a new BSTNode. + * @example Getting a reference to a BinarySearchTreeNode. * ```ts - * import { BSTNode } from "@std/data-structures"; + * import { BinarySearchTree } from "@std/data-structures"; * import { assertEquals } from "@std/assert"; * - * const node = new BSTNode(null, 42); + * const tree = new BinarySearchTree(); + * + * assertEquals(tree.insert(42), true); + * + * const root = tree.getRoot(); + * * assertEquals(node.value, 42); * assertEquals(node.left, null); * assertEquals(node.right, null); @@ -18,7 +25,7 @@ * * @typeparam T The type of the values stored in the binary tree. */ -export class BSTNode { +export interface BinarySearchTreeNode { /** * The left child node, or null if there is no left child. * @@ -38,7 +45,7 @@ export class BSTNode { * assertEquals(leftChild?.value, 21); * ``` */ - left: BSTNode | null; + left: BinarySearchTreeNode | null; /** * The right child node, or null if there is no right child. @@ -59,7 +66,7 @@ export class BSTNode { * assertEquals(leftChild?.value, 42); * ``` */ - right: BSTNode | null; + right: BinarySearchTreeNode | null; /** * The parent of this node, or null if there is no parent. @@ -80,7 +87,7 @@ export class BSTNode { * assertEquals(leftChild?.parent?.value, 42); * ``` */ - parent: BSTNode | null; + parent: BinarySearchTreeNode | null; /** * The value stored at this node. @@ -98,16 +105,4 @@ export class BSTNode { * ``` */ 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 | null, value: T) { - this.left = null; - this.right = null; - this.parent = parent; - this.value = value; - } } diff --git a/data_structures/binary_search_tree_test.ts b/data_structures/binary_search_tree_test.ts index c9322d195371..58490c6bc6c3 100644 --- a/data_structures/binary_search_tree_test.ts +++ b/data_structures/binary_search_tree_test.ts @@ -5,7 +5,7 @@ import { assertStrictEquals, assertThrows, } from "@std/assert"; -import type { BSTNode } from "./bst_node.ts"; +import type { BinarySearchTreeNode } from "./binary_search_tree_node.ts"; import { BinarySearchTree } from "./binary_search_tree.ts"; import { ascend, descend } from "./comparators.ts"; @@ -21,7 +21,7 @@ interface Container { stSize: number; } -function callback(n: BSTNode) { +function callback(n: BinarySearchTreeNode) { let totalSize = 1; totalSize += n.left?.value.stSize || 0; totalSize += n.right?.value.stSize || 0; diff --git a/data_structures/bst_node_test.ts b/data_structures/bst_node_test.ts deleted file mode 100644 index 25eb2a2e5552..000000000000 --- a/data_structures/bst_node_test.ts +++ /dev/null @@ -1,26 +0,0 @@ -// 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; -let child: BSTNode; -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); -}); diff --git a/data_structures/mod.ts b/data_structures/mod.ts index 1af1ddcdbdd4..86e85b798934 100644 --- a/data_structures/mod.ts +++ b/data_structures/mod.ts @@ -27,6 +27,6 @@ export * from "./binary_heap.ts"; export * from "./binary_search_tree.ts"; -export * from "./bst_node.ts"; +export * from "./binary_search_tree_node.ts"; export * from "./comparators.ts"; export * from "./red_black_tree.ts"; diff --git a/data_structures/red_black_tree.ts b/data_structures/red_black_tree.ts index 7d3b83392e76..4126e66fb28b 100644 --- a/data_structures/red_black_tree.ts +++ b/data_structures/red_black_tree.ts @@ -3,7 +3,7 @@ import { ascend } from "./comparators.ts"; import { BinarySearchTree } from "./binary_search_tree.ts"; -import type { BSTNode } from "./bst_node.ts"; +import type { BinarySearchTreeNode } from "./binary_search_tree_node.ts"; import { type Direction, RedBlackNode } from "./_red_black_node.ts"; import { internals } from "./_binary_search_tree_internals.ts"; @@ -111,7 +111,7 @@ export class RedBlackTree extends BinarySearchTree { */ constructor( compare: (a: T, b: T) => number = ascend, - callback?: (node: BSTNode) => void, + callback?: (node: BinarySearchTreeNode) => void, ) { if (typeof compare !== "function") { throw new TypeError( diff --git a/data_structures/red_black_tree_test.ts b/data_structures/red_black_tree_test.ts index 00dfa9723ebe..13e46abad814 100644 --- a/data_structures/red_black_tree_test.ts +++ b/data_structures/red_black_tree_test.ts @@ -1,6 +1,6 @@ // Copyright 2018-2025 the Deno authors. MIT license. import { assertEquals, assertStrictEquals, assertThrows } from "@std/assert"; -import type { BSTNode } from "./bst_node.ts"; +import type { BinarySearchTreeNode } from "./binary_search_tree_node.ts"; import { RedBlackTree } from "./red_black_tree.ts"; import { ascend, descend } from "./comparators.ts"; import { MyMath } from "./_test_utils.ts"; @@ -259,7 +259,7 @@ Deno.test("RedBlackTree works as exepcted with descend comparator", () => { } }); -function callback(n: BSTNode) { +function callback(n: BinarySearchTreeNode) { let totalSize = 1; totalSize += n.left?.value.stSize || 0; totalSize += n.right?.value.stSize || 0;