Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions data_structures/_test_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export class MyMath {
export interface Container {
id: number;
values: number[];
st_size: number;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you use camelCase?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done.

}
35 changes: 34 additions & 1 deletion data_structures/binary_search_tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ type Direction = "left" | "right";
export class BinarySearchTree<T> implements Iterable<T> {
#root: BinarySearchNode<T> | null = null;
#size = 0;
#callback: ((node: BinarySearchNode<T>) => void) | null = null;
#compare: (a: T, b: T) => number;

/**
Expand All @@ -104,12 +105,18 @@ export class BinarySearchTree<T> implements Iterable<T> {
* @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<T>) => void) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Need to document callback param in jsdoc.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added the param.

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;
}

Expand Down Expand Up @@ -353,6 +360,10 @@ export class BinarySearchTree<T> implements Iterable<T> {
}
replacement[direction] = node;
node.parent = replacement;
if (this.#callback) {
this.#callback(node);
this.#callback(replacement);
}
}

#insertNode(
Expand All @@ -374,6 +385,14 @@ export class BinarySearchTree<T> implements Iterable<T> {
} 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 +429,23 @@ export class BinarySearchTree<T> implements Iterable<T> {
}

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<T> | null {
return this.#root;
}

/**
* Add a value to the binary search tree if it does not already exist in the
* tree.
Expand Down
40 changes: 26 additions & 14 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 { BinarySearchNode } from "./_binary_search_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[];
st_size: number;
}

function callback(n: BinarySearchNode<Container>) {
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", () => {
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: [], 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);
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: [], 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);
Expand Down
10 changes: 8 additions & 2 deletions data_structures/red_black_tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -107,13 +108,18 @@ export class RedBlackTree<T> extends BinarySearchTree<T> {
*
* @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<T>) => 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);
}

/**
Expand Down
39 changes: 25 additions & 14 deletions data_structures/red_black_tree_test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -252,32 +254,40 @@ Deno.test("RedBlackTree works as exepcted with descend comparator", () => {
}
});

function callback(n: BinarySearchNode<Container>) {
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<Container> = 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);
Expand All @@ -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);
Expand Down
Loading