Skip to content

Commit 064f7ef

Browse files
committed
feat(util): 🎸 add AvlSet implementation
1 parent 0659d07 commit 064f7ef

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

src/util/trees/avl/AvlSet.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import {insert, insertLeft, remove, insertRight, print} from './util';
2+
import {printTree} from '../../print/printTree';
3+
import {findOrNextLower, first, next} from '../util';
4+
import type {Printable} from '../../print/types';
5+
import type {Comparator, HeadlessNode} from '../types';
6+
import type {AvlNodeReference, IAvlTreeNode} from './types';
7+
8+
export class AvlSetNode<V> implements IAvlTreeNode<V, void> {
9+
public p: AvlSetNode<V> | undefined = undefined;
10+
public l: AvlSetNode<V> | undefined = undefined;
11+
public r: AvlSetNode<V> | undefined = undefined;
12+
public bf: number = 0;
13+
public v: undefined = undefined;
14+
constructor(public readonly k: V) {}
15+
}
16+
17+
const defaultComparator = (a: unknown, b: unknown) => (a === b ? 0 : (a as any) < (b as any) ? -1 : 1);
18+
19+
export class AvlSet<V> implements Printable {
20+
public root: AvlSetNode<V> | undefined = undefined;
21+
public readonly comparator: Comparator<V>;
22+
23+
constructor(comparator?: Comparator<V>) {
24+
this.comparator = comparator || defaultComparator;
25+
}
26+
27+
private insert(value: V): AvlNodeReference<AvlSetNode<V>> {
28+
const item = new AvlSetNode<V>(value);
29+
this.root = insert(this.root, item, this.comparator);
30+
return item;
31+
}
32+
33+
public add(value: V): AvlNodeReference<AvlSetNode<V>> {
34+
const root = this.root;
35+
if (!root) return this.insert(value);
36+
const comparator = this.comparator;
37+
let next: AvlSetNode<V> | undefined = root,
38+
curr: AvlSetNode<V> | undefined = next;
39+
let cmp: number = 0;
40+
do {
41+
curr = next;
42+
cmp = comparator(value, curr.k);
43+
if (cmp === 0) return curr;
44+
} while ((next = cmp < 0 ? (curr.l as AvlSetNode<V>) : (curr.r as AvlSetNode<V>)));
45+
const node = new AvlSetNode<V>(value);
46+
this.root =
47+
cmp < 0 ? (insertLeft(root, node, curr) as AvlSetNode<V>) : (insertRight(root, node, curr) as AvlSetNode<V>);
48+
return node;
49+
}
50+
51+
private find(k: V): AvlNodeReference<AvlSetNode<V>> | undefined {
52+
const comparator = this.comparator;
53+
let curr: AvlSetNode<V> | undefined = this.root;
54+
while (curr) {
55+
const cmp = comparator(k, curr.k);
56+
if (cmp === 0) return curr;
57+
curr = cmp < 0 ? (curr.l as AvlSetNode<V>) : (curr.r as AvlSetNode<V>);
58+
}
59+
return undefined;
60+
}
61+
62+
public del(k: V): void {
63+
const node = this.find(k);
64+
if (!node) return;
65+
this.root = remove(this.root, node as AvlSetNode<V>);
66+
}
67+
68+
public clear(): void {
69+
this.root = undefined;
70+
}
71+
72+
public has(k: V): boolean {
73+
return !!this.find(k);
74+
}
75+
76+
public size(): number {
77+
const root = this.root;
78+
if (!root) return 0;
79+
let curr = first(root);
80+
let size = 1;
81+
while ((curr = next(curr as HeadlessNode) as AvlSetNode<V> | undefined)) size++;
82+
return size;
83+
}
84+
85+
public isEmpty(): boolean {
86+
return !this.root;
87+
}
88+
89+
public getOrNextLower(k: V): AvlSetNode<V> | undefined {
90+
return (findOrNextLower(this.root, k, this.comparator) as AvlSetNode<V>) || undefined;
91+
}
92+
93+
public forEach(fn: (node: AvlSetNode<V>) => void): void {
94+
const root = this.root;
95+
if (!root) return;
96+
let curr = first(root);
97+
do fn(curr!);
98+
while ((curr = next(curr as HeadlessNode) as AvlSetNode<V> | undefined));
99+
}
100+
101+
public toString(tab: string): string {
102+
return this.constructor.name + printTree(tab, [(tab) => print(this.root, tab)]);
103+
}
104+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {AvlSet} from '../AvlSet';
2+
3+
test('can add numbers to set', () => {
4+
const set = new AvlSet<number>();
5+
expect(set.size()).toBe(0);
6+
expect(set.has(1)).toBe(false);
7+
set.add(1);
8+
expect(set.size()).toBe(1);
9+
expect(set.has(1)).toBe(true);
10+
set.add(24);
11+
set.add(42);
12+
set.add(42);
13+
expect(set.size()).toBe(3);
14+
expect(set.has(24)).toBe(true);
15+
expect(set.has(42)).toBe(true);
16+
expect(set.has(25)).toBe(false);
17+
});
18+
19+
test('can remove numbers from set', () => {
20+
const set = new AvlSet<number>();
21+
set.add(1);
22+
set.add(24);
23+
set.add(42);
24+
expect(set.has(1)).toBe(true);
25+
expect(set.has(24)).toBe(true);
26+
expect(set.has(42)).toBe(true);
27+
set.del(24);
28+
expect(set.has(1)).toBe(true);
29+
expect(set.has(24)).toBe(false);
30+
expect(set.has(42)).toBe(true);
31+
set.del(1);
32+
expect(set.has(1)).toBe(false);
33+
expect(set.has(24)).toBe(false);
34+
expect(set.has(42)).toBe(true);
35+
expect(set.size()).toBe(1);
36+
set.del(42);
37+
expect(set.has(1)).toBe(false);
38+
expect(set.has(24)).toBe(false);
39+
expect(set.has(42)).toBe(false);
40+
expect(set.size()).toBe(0);
41+
});
42+
43+
test('can store structs', () => {
44+
class Struct { constructor (public x: number, public y: number) {} }
45+
const set = new AvlSet<Struct>((a, b) => {
46+
const dx = a.x - b.x;
47+
return dx === 0 ? a.y - b.y : dx;
48+
});
49+
set.add(new Struct(0, 0));
50+
set.add(new Struct(0, 1));
51+
expect(set.size()).toBe(2);
52+
set.del(new Struct(0, 0));
53+
expect(set.size()).toBe(1);
54+
expect(set.has(new Struct(0, 0))).toBe(false);
55+
expect(set.has(new Struct(0, 1))).toBe(true);
56+
set.add(new Struct(2, 3));
57+
set.add(new Struct(3, 3));
58+
expect(set.size()).toBe(3);
59+
expect(set.has(new Struct(3, 3))).toBe(true);
60+
expect(set.has(new Struct(2, 3))).toBe(true);
61+
});

0 commit comments

Comments
 (0)