Skip to content

Commit

Permalink
Merge pull request #553 from streamich/avl-improvements
Browse files Browse the repository at this point in the history
AVL map and set iterator improvements
  • Loading branch information
streamich authored Mar 17, 2024
2 parents d34c4ae + 8d80187 commit be415a2
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 6 deletions.
8 changes: 8 additions & 0 deletions src/util/buffers/cmpUint8Array2.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/**
* Compares two `Uint8Arrays` byte-by-byte. Returns a negative number if `a` is
* less than `b`, a positive number if `a` is greater than `b`, or 0 if `a` is
* equal to `b`.
*
* @returns A negative number if a is less than b, a positive number if a is
* greater than b, or 0 if a is equal to b.
*/
export const cmpUint8Array2 = (a: Uint8Array, b: Uint8Array): number => {
const len1 = a.length;
const len2 = b.length;
Expand Down
19 changes: 19 additions & 0 deletions src/util/buffers/cmpUint8Array3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Compares two `Uint8Arrays`, first by length, then by each byte. Returns a
* negative number if `a` is less than `b`, a positive number if `a` is greater
* than `b`, or 0 if `a` is equal to `b`.
*
* @returns A negative number if a is less than b, a positive number if a is
* greater than b, or 0 if a is equal to b.
*/
export const cmpUint8Array3 = (a: Uint8Array, b: Uint8Array): number => {
const len1 = a.length;
const len2 = b.length;
const diff = len1 - len2;
if (diff !== 0) return diff;
for (let i = 0; i < len1; i++) {
const diff = a[i] - b[i];
if (diff !== 0) return diff;
}
return 0;
};
37 changes: 34 additions & 3 deletions src/util/trees/avl/AvlMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,44 @@ export class AvlMap<K, V> implements Printable {
}

public forEach(fn: (node: AvlNode<K, V>) => void): void {
const root = this.root;
if (!root) return;
let curr = first(root);
let curr = this.first();
if (!curr) return;
do fn(curr!);
while ((curr = next(curr as HeadlessNode) as AvlNode<K, V> | undefined));
}

public first(): AvlNode<K, V> | undefined {
const root = this.root;
return root ? first(root) : undefined;
}

public readonly next = next;

public iterator0(): () => undefined | AvlNode<K, V> {
let curr = this.first();
return () => {
if (!curr) return;
const value = curr;
curr = next(curr as HeadlessNode) as AvlNode<K, V> | undefined;
return value;
};
}

public iterator(): Iterator<AvlNode<K, V>> {
const iterator = this.iterator0();
return {
next: () => {
const value = iterator();
const res = <IteratorResult<AvlNode<K, V>>>{value, done: !value};
return res;
},
};
}

public entries(): IterableIterator<AvlNode<K, V>> {
return <any>{[Symbol.iterator]: () => this.iterator()};
}

public toString(tab: string): string {
return this.constructor.name + printTree(tab, [(tab) => print(this.root, tab)]);
}
Expand Down
37 changes: 34 additions & 3 deletions src/util/trees/avl/AvlSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,44 @@ export class AvlSet<V> implements Printable {
}

public forEach(fn: (node: AvlSetNode<V>) => void): void {
const root = this.root;
if (!root) return;
let curr = first(root);
let curr = this.first();
if (!curr) return;
do fn(curr!);
while ((curr = next(curr as HeadlessNode) as AvlSetNode<V> | undefined));
}

public first(): AvlSetNode<V> | undefined {
const root = this.root;
return root ? first(root) : undefined;
}

public readonly next = next;

public iterator0(): () => undefined | AvlSetNode<V> {
let curr = this.first();
return () => {
if (!curr) return undefined;
const value = curr;
curr = next(curr as HeadlessNode) as AvlSetNode<V> | undefined;
return value;
};
}

public iterator(): Iterator<AvlSetNode<V>> {
const iterator = this.iterator0();
return {
next: () => {
const value = iterator();
const res = <IteratorResult<AvlSetNode<V>>>{value, done: !value};
return res;
},
};
}

public entries(): IterableIterator<AvlSetNode<V>> {
return <any>{[Symbol.iterator]: () => this.iterator()};
}

public toString(tab: string): string {
return this.constructor.name + printTree(tab, [(tab) => print(this.root, tab)]);
}
Expand Down
94 changes: 94 additions & 0 deletions src/util/trees/avl/__tests__/AvlMap.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,97 @@ test('smoke test', () => {
tree.forEach((node) => keys.push(node.k));
expect(keys).toEqual([1, 3, 4, 4.1, 44]);
});

describe('.first()/next() iteration', () => {
test('for empty map, returns finished iterator', () => {
const tree = new AvlMap<string, number>();
const entry = tree.first();
expect(entry).toEqual(undefined);
});

test('can iterate through map entries', () => {
const tree = new AvlMap<string, number>();
tree.set('a', 1);
tree.set('b', 2);
tree.set('c', 3);
const list: [string, number][] = [];
for (let entry = tree.first(); entry; entry = tree.next(entry)) {
list.push([entry.k, entry.v]);
}
expect(list).toEqual([
['a', 1],
['b', 2],
['c', 3],
]);
});
});

describe('.iterator0()', () => {
test('for empty map, returns finished iterator', () => {
const tree = new AvlMap<string, number>();
const iterator = tree.iterator0();
const entry = iterator();
expect(entry).toEqual(undefined);
});

test('can iterate through map entries', () => {
const tree = new AvlMap<string, number>();
tree.set('a', 1);
tree.set('b', 2);
tree.set('c', 3);
const list: [string, number][] = [];
const iterator = tree.iterator0();
for (let entry = iterator(); entry; entry = iterator()) {
list.push([entry.k, entry.v]);
}
expect(list).toEqual([
['a', 1],
['b', 2],
['c', 3],
]);
});
});

describe('.iterator()', () => {
test('for empty map, returns finished iterator', () => {
const tree = new AvlMap<string, number>();
const iterator = tree.iterator();
const entry = iterator.next();
expect(entry).toEqual({done: true, value: undefined});
});

test('can iterate through map entries', () => {
const tree = new AvlMap<string, number>();
tree.set('a', 1);
tree.set('b', 2);
tree.set('c', 3);
const iterator = tree.iterator();
const list: [string, number][] = [];
for (let entry = iterator.next(); !entry.done; entry = iterator.next()) {
list.push([entry.value!.k, entry.value!.v]);
}
expect(list).toEqual([
['a', 1],
['b', 2],
['c', 3],
]);
});
});

describe('for...of iteration', () => {
test('can iterate through map entries', () => {
const tree = new AvlMap<string, number>();
tree.set('a', 1);
tree.set('b', 2);
tree.set('c', 3);
const list: [string, number][] = [];
for (const entry of tree.entries()) {
list.push([entry.k, entry.v]);
}
expect(list).toEqual([
['a', 1],
['b', 2],
['c', 3],
]);
});
});
78 changes: 78 additions & 0 deletions src/util/trees/avl/__tests__/AvlSet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,81 @@ test('can store structs', () => {
expect(set.has(new Struct(3, 3))).toBe(true);
expect(set.has(new Struct(2, 3))).toBe(true);
});

describe('.first()/next() iteration', () => {
test('for empty map, returns finished iterator', () => {
const tree = new AvlSet<string>();
const entry = tree.first();
expect(entry).toEqual(undefined);
});

test('can iterate through map entries', () => {
const tree = new AvlSet<number>();
tree.add(1);
tree.add(2);
tree.add(3);
const list: number[] = [];
for (let entry = tree.first(); entry; entry = tree.next(entry)) {
list.push(entry.k);
}
expect(list).toEqual([1, 2, 3]);
});
});

describe('.iterator0()', () => {
test('for empty map, returns finished iterator', () => {
const tree = new AvlSet<number>();
const iterator = tree.iterator0();
const entry = iterator();
expect(entry).toEqual(undefined);
});

test('can iterate through map entries', () => {
const tree = new AvlSet<number>();
tree.add(1);
tree.add(2);
tree.add(3);
const list: number[] = [];
const iterator = tree.iterator0();
for (let entry = iterator(); entry; entry = iterator()) {
list.push(entry.k);
}
expect(list).toEqual([1, 2, 3]);
});
});

describe('.iterator()', () => {
test('for empty map, returns finished iterator', () => {
const tree = new AvlSet<number>();
const iterator = tree.iterator();
const entry = iterator.next();
expect(entry).toEqual({done: true, value: undefined});
});

test('can iterate through map entries', () => {
const tree = new AvlSet<number>();
tree.add(1);
tree.add(2);
tree.add(3);
const list: number[] = [];
const iterator = tree.iterator();
for (let entry = iterator.next(); !entry.done; entry = iterator.next()) {
list.push(entry.value!.k);
}
expect(list).toEqual([1, 2, 3]);
});
});

describe('for...of iteration', () => {
test('can iterate through map entries', () => {
const tree = new AvlSet<number>();
tree.add(1);
tree.add(2);
tree.add(3);
const list: number[] = [];
for (const entry of tree.entries()) {
list.push(entry.k);
}
expect(list).toEqual([1, 2, 3]);
});
});

0 comments on commit be415a2

Please sign in to comment.