From 65af848169f8c15fff6d5ee3634dd72521c65258 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 17 Mar 2024 10:27:21 +0100 Subject: [PATCH 1/5] =?UTF-8?q?feat(util):=20=F0=9F=8E=B8=20add=20ability?= =?UTF-8?q?=20to=20async=20iterate=20over=20AvlMap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/trees/avl/AvlMap.ts | 14 +++++++++++++ src/util/trees/avl/__tests__/AvlMap.spec.ts | 23 +++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/util/trees/avl/AvlMap.ts b/src/util/trees/avl/AvlMap.ts index 931221e25b..19a3945c2d 100644 --- a/src/util/trees/avl/AvlMap.ts +++ b/src/util/trees/avl/AvlMap.ts @@ -105,6 +105,20 @@ export class AvlMap implements Printable { while ((curr = next(curr as HeadlessNode) as AvlNode | undefined)); } + public iterator(): Iterator> { + const root = this.root; + if (!root) return {next: () => ({done: true, value: undefined})}; + let curr = first(root); + return { + next: () => { + if (!curr) return {done: true, value: undefined}; + const value = curr; + curr = next(curr as HeadlessNode) as AvlNode | undefined; + return {done: false, value}; + }, + }; + } + public toString(tab: string): string { return this.constructor.name + printTree(tab, [(tab) => print(this.root, tab)]); } diff --git a/src/util/trees/avl/__tests__/AvlMap.spec.ts b/src/util/trees/avl/__tests__/AvlMap.spec.ts index a74d9c23d2..e012c3fd1e 100644 --- a/src/util/trees/avl/__tests__/AvlMap.spec.ts +++ b/src/util/trees/avl/__tests__/AvlMap.spec.ts @@ -14,3 +14,26 @@ test('smoke test', () => { tree.forEach((node) => keys.push(node.k)); expect(keys).toEqual([1, 3, 4, 4.1, 44]); }); + + +describe('.iterator()', () => { + test('for empty map, returns finished iterator', () => { + const tree = new AvlMap(); + 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(); + 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]]); + }); +}); From ce43424f301aaecde84fc52e11cc56b3b6c17f6a Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 17 Mar 2024 10:51:44 +0100 Subject: [PATCH 2/5] =?UTF-8?q?feat(util):=20=F0=9F=8E=B8=20improve=20AvlM?= =?UTF-8?q?ap=20iteration=20strategies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/trees/avl/AvlMap.ts | 32 +++++++++--- src/util/trees/avl/__tests__/AvlMap.spec.ts | 55 +++++++++++++++++++++ 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/util/trees/avl/AvlMap.ts b/src/util/trees/avl/AvlMap.ts index 19a3945c2d..39f23e1bee 100644 --- a/src/util/trees/avl/AvlMap.ts +++ b/src/util/trees/avl/AvlMap.ts @@ -105,20 +105,38 @@ export class AvlMap implements Printable { while ((curr = next(curr as HeadlessNode) as AvlNode | undefined)); } - public iterator(): Iterator> { + public first(): AvlNode | undefined { const root = this.root; - if (!root) return {next: () => ({done: true, value: undefined})}; - let curr = first(root); + return root ? first(root) : undefined; + } + + public readonly next = next; + + public iterator0(): (() => undefined | AvlNode) { + let curr = this.first(); + return () => { + if (!curr) return undefined; + const value = curr; + curr = next(curr as HeadlessNode) as AvlNode | undefined; + return value; + }; + } + + public iterator(): Iterator> { + const iterator = this.iterator0(); return { next: () => { - if (!curr) return {done: true, value: undefined}; - const value = curr; - curr = next(curr as HeadlessNode) as AvlNode | undefined; - return {done: false, value}; + const value = iterator(); + const res = >>{value, done: !value}; + return res; }, }; } + public entries(): IterableIterator> { + return {[Symbol.iterator]: () => this.iterator()}; + } + public toString(tab: string): string { return this.constructor.name + printTree(tab, [(tab) => print(this.root, tab)]); } diff --git a/src/util/trees/avl/__tests__/AvlMap.spec.ts b/src/util/trees/avl/__tests__/AvlMap.spec.ts index e012c3fd1e..1bedeab399 100644 --- a/src/util/trees/avl/__tests__/AvlMap.spec.ts +++ b/src/util/trees/avl/__tests__/AvlMap.spec.ts @@ -15,6 +15,47 @@ test('smoke test', () => { expect(keys).toEqual([1, 3, 4, 4.1, 44]); }); +describe('.first()/next() iteration', () => { + test('for empty map, returns finished iterator', () => { + const tree = new AvlMap(); + const entry = tree.first(); + expect(entry).toEqual(undefined); + }); + + test('can iterate through map entries', () => { + const tree = new AvlMap(); + 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(); + const iterator = tree.iterator0(); + const entry = iterator(); + expect(entry).toEqual(undefined); + }); + + test('can iterate through map entries', () => { + const tree = new AvlMap(); + 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', () => { @@ -37,3 +78,17 @@ describe('.iterator()', () => { expect(list).toEqual([['a', 1], ['b', 2], ['c', 3]]); }); }); + +describe('for...of iteration', () => { + test('can iterate through map entries', () => { + const tree = new AvlMap(); + 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]]); + }); +}); From 69d891968f7f357629bbe6b3819cd475484a3baa Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 17 Mar 2024 10:56:46 +0100 Subject: [PATCH 3/5] =?UTF-8?q?feat(util):=20=F0=9F=8E=B8=20improve=20AvlS?= =?UTF-8?q?et=20iteration=20strategies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/trees/avl/AvlMap.ts | 7 +- src/util/trees/avl/AvlSet.ts | 37 +++++++++- src/util/trees/avl/__tests__/AvlSet.spec.ts | 78 +++++++++++++++++++++ 3 files changed, 115 insertions(+), 7 deletions(-) diff --git a/src/util/trees/avl/AvlMap.ts b/src/util/trees/avl/AvlMap.ts index 39f23e1bee..020cfaea22 100644 --- a/src/util/trees/avl/AvlMap.ts +++ b/src/util/trees/avl/AvlMap.ts @@ -98,9 +98,8 @@ export class AvlMap implements Printable { } public forEach(fn: (node: AvlNode) => 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 | undefined)); } @@ -115,7 +114,7 @@ export class AvlMap implements Printable { public iterator0(): (() => undefined | AvlNode) { let curr = this.first(); return () => { - if (!curr) return undefined; + if (!curr) return; const value = curr; curr = next(curr as HeadlessNode) as AvlNode | undefined; return value; diff --git a/src/util/trees/avl/AvlSet.ts b/src/util/trees/avl/AvlSet.ts index e3b66394dc..57cf432b65 100644 --- a/src/util/trees/avl/AvlSet.ts +++ b/src/util/trees/avl/AvlSet.ts @@ -91,13 +91,44 @@ export class AvlSet implements Printable { } public forEach(fn: (node: AvlSetNode) => 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 | undefined)); } + public first(): AvlSetNode | undefined { + const root = this.root; + return root ? first(root) : undefined; + } + + public readonly next = next; + + public iterator0(): (() => undefined | AvlSetNode) { + let curr = this.first(); + return () => { + if (!curr) return undefined; + const value = curr; + curr = next(curr as HeadlessNode) as AvlSetNode | undefined; + return value; + }; + } + + public iterator(): Iterator> { + const iterator = this.iterator0(); + return { + next: () => { + const value = iterator(); + const res = >>{value, done: !value}; + return res; + }, + }; + } + + public entries(): IterableIterator> { + return {[Symbol.iterator]: () => this.iterator()}; + } + public toString(tab: string): string { return this.constructor.name + printTree(tab, [(tab) => print(this.root, tab)]); } diff --git a/src/util/trees/avl/__tests__/AvlSet.spec.ts b/src/util/trees/avl/__tests__/AvlSet.spec.ts index ddf7dddb6a..1c19485179 100644 --- a/src/util/trees/avl/__tests__/AvlSet.spec.ts +++ b/src/util/trees/avl/__tests__/AvlSet.spec.ts @@ -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(); + const entry = tree.first(); + expect(entry).toEqual(undefined); + }); + + test('can iterate through map entries', () => { + const tree = new AvlSet(); + 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(); + const iterator = tree.iterator0(); + const entry = iterator(); + expect(entry).toEqual(undefined); + }); + + test('can iterate through map entries', () => { + const tree = new AvlSet(); + 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(); + 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(); + 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(); + 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]); + }); +}); From 04ec019b3ae2008740168877444069ce61b82711 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 17 Mar 2024 10:59:10 +0100 Subject: [PATCH 4/5] =?UTF-8?q?style(util):=20=F0=9F=92=84=20run=20Prettie?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/trees/avl/AvlMap.ts | 2 +- src/util/trees/avl/AvlSet.ts | 2 +- src/util/trees/avl/__tests__/AvlMap.spec.ts | 24 +++++++++++++++++---- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/util/trees/avl/AvlMap.ts b/src/util/trees/avl/AvlMap.ts index 020cfaea22..e7df5adc9b 100644 --- a/src/util/trees/avl/AvlMap.ts +++ b/src/util/trees/avl/AvlMap.ts @@ -111,7 +111,7 @@ export class AvlMap implements Printable { public readonly next = next; - public iterator0(): (() => undefined | AvlNode) { + public iterator0(): () => undefined | AvlNode { let curr = this.first(); return () => { if (!curr) return; diff --git a/src/util/trees/avl/AvlSet.ts b/src/util/trees/avl/AvlSet.ts index 57cf432b65..a122439c59 100644 --- a/src/util/trees/avl/AvlSet.ts +++ b/src/util/trees/avl/AvlSet.ts @@ -104,7 +104,7 @@ export class AvlSet implements Printable { public readonly next = next; - public iterator0(): (() => undefined | AvlSetNode) { + public iterator0(): () => undefined | AvlSetNode { let curr = this.first(); return () => { if (!curr) return undefined; diff --git a/src/util/trees/avl/__tests__/AvlMap.spec.ts b/src/util/trees/avl/__tests__/AvlMap.spec.ts index 1bedeab399..9058803539 100644 --- a/src/util/trees/avl/__tests__/AvlMap.spec.ts +++ b/src/util/trees/avl/__tests__/AvlMap.spec.ts @@ -31,7 +31,11 @@ describe('.first()/next() iteration', () => { 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]]); + expect(list).toEqual([ + ['a', 1], + ['b', 2], + ['c', 3], + ]); }); }); @@ -53,7 +57,11 @@ describe('.iterator0()', () => { for (let entry = iterator(); entry; entry = iterator()) { list.push([entry.k, entry.v]); } - expect(list).toEqual([['a', 1], ['b', 2], ['c', 3]]); + expect(list).toEqual([ + ['a', 1], + ['b', 2], + ['c', 3], + ]); }); }); @@ -75,7 +83,11 @@ describe('.iterator()', () => { 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]]); + expect(list).toEqual([ + ['a', 1], + ['b', 2], + ['c', 3], + ]); }); }); @@ -89,6 +101,10 @@ describe('for...of iteration', () => { for (const entry of tree.entries()) { list.push([entry.k, entry.v]); } - expect(list).toEqual([['a', 1], ['b', 2], ['c', 3]]); + expect(list).toEqual([ + ['a', 1], + ['b', 2], + ['c', 3], + ]); }); }); From 8d80187e591a3a30e724cfb64c435d0bc3f29f62 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 17 Mar 2024 10:59:46 +0100 Subject: [PATCH 5/5] =?UTF-8?q?feat(util):=20=F0=9F=8E=B8=20add=20another?= =?UTF-8?q?=20Uint8Array=20comparison=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/buffers/cmpUint8Array2.ts | 8 ++++++++ src/util/buffers/cmpUint8Array3.ts | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/util/buffers/cmpUint8Array3.ts diff --git a/src/util/buffers/cmpUint8Array2.ts b/src/util/buffers/cmpUint8Array2.ts index 6bfcbcc38d..344ec1e6f2 100644 --- a/src/util/buffers/cmpUint8Array2.ts +++ b/src/util/buffers/cmpUint8Array2.ts @@ -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; diff --git a/src/util/buffers/cmpUint8Array3.ts b/src/util/buffers/cmpUint8Array3.ts new file mode 100644 index 0000000000..950c358f7e --- /dev/null +++ b/src/util/buffers/cmpUint8Array3.ts @@ -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; +};