From d07fcf2ea14e44c344115b557063ec30ac632408 Mon Sep 17 00:00:00 2001 From: Francois Date: Thu, 9 Oct 2025 14:17:36 +0900 Subject: [PATCH 1/5] chore: resolve rebase conflicts in examples registry --- src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/index.ts b/src/index.ts index 2d44e46..8d467c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -412,6 +412,7 @@ export { satCollision } from './spatial/sat.js'; */ export { circleRayIntersection } from './spatial/circleRay.js'; /** +<<<<<<< HEAD <<<<<<< HEAD * Fast circle-circle overlap test. * @@ -431,6 +432,8 @@ export { circleAabbCollision } from './spatial/circleCollision.js'; */ export { circleSegmentIntersection } from './spatial/circleCollision.js'; ======= +======= +>>>>>>> 83f962b (feat(spatial): add raycasting utilities (raycastSegment, raycastAabb); docs + tests + example) * Ray vs. segment intersection test returning closest hit. * * Example file: examples/raycast.ts @@ -442,6 +445,9 @@ export { raycastSegment } from './spatial/raycast.js'; * Example file: examples/raycast.ts */ export { raycastAabb } from './spatial/raycast.js'; +<<<<<<< HEAD +>>>>>>> 83f962b (feat(spatial): add raycasting utilities (raycastSegment, raycastAabb); docs + tests + example) +======= >>>>>>> 83f962b (feat(spatial): add raycasting utilities (raycastSegment, raycastAabb); docs + tests + example) /** From d1a7e15fc1b21f3d51146c8fc615448dab347d5d Mon Sep 17 00:00:00 2001 From: Francois Date: Thu, 9 Oct 2025 14:15:42 +0900 Subject: [PATCH 2/5] feat(data): add UnionFind (disjoint set union) with path compression; docs + tests --- docs/index.d.ts | 17 ++++++++++++ src/data/unionFind.ts | 60 +++++++++++++++++++++++++++++++++++++++++ src/index.ts | 7 +++++ tests/index.test.ts | 1 + tests/unionFind.test.ts | 26 ++++++++++++++++++ 5 files changed, 111 insertions(+) create mode 100644 src/data/unionFind.ts create mode 100644 tests/unionFind.test.ts diff --git a/docs/index.d.ts b/docs/index.d.ts index 7dc08f3..c71c69e 100644 --- a/docs/index.d.ts +++ b/docs/index.d.ts @@ -94,6 +94,9 @@ export const examples: { readonly flatten: 'examples/jsonDiff.ts'; readonly unflatten: 'examples/jsonDiff.ts'; readonly paginate: 'examples/pagination.ts'; + readonly diffTree: 'examples/treeDiff.ts'; + readonly applyTreeDiff: 'examples/treeDiff.ts'; + readonly UnionFind: 'examples/graph.ts'; }; readonly performance: { readonly debounce: 'examples/requestDedup.ts'; @@ -2926,6 +2929,20 @@ export function groupBy( key: keyof T | ((item: T) => string) ): Record; +/** + * Disjoint Set Union (Union-Find) with path compression and union by size. + * Use for: connectivity queries, Kruskal MST, clustering. + * Import: data/unionFind.ts + */ +export class UnionFind { + constructor(elements?: Iterable); + makeSet(x: T): void; + find(x: T): T; + union(a: T, b: T): boolean; + connected(a: T, b: T): boolean; + size(x: T): number; +} + // ============================================================================ // 📈 GRAPH ALGORITHMS // ============================================================================ diff --git a/src/data/unionFind.ts b/src/data/unionFind.ts new file mode 100644 index 0000000..f1136c6 --- /dev/null +++ b/src/data/unionFind.ts @@ -0,0 +1,60 @@ +/** + * Disjoint Set Union (Union-Find) with path compression and union by size. + * Useful for: connectivity queries, Kruskal MST, clustering. + */ +export class UnionFind { + private parent = new Map(); + private compSize = new Map(); + + constructor(elements?: Iterable) { + if (elements) { + for (const el of elements) { + this.makeSet(el); + } + } + } + + makeSet(x: T): void { + if (!this.parent.has(x)) { + this.parent.set(x, x); + this.compSize.set(x, 1); + } + } + + find(x: T): T { + if (!this.parent.has(x)) this.makeSet(x); + const direct = this.parent.get(x); + let p: T = direct === undefined ? x : (direct as T); + if (p !== x) { + p = this.find(p); + this.parent.set(x, p); + } + return p; + } + + union(a: T, b: T): boolean { + let ra = this.find(a); + let rb = this.find(b); + if (ra === rb) return false; + const sa = this.compSize.get(ra)!; + const sb = this.compSize.get(rb)!; + if (sa < sb) { + const tmp = ra; + ra = rb; + rb = tmp; + } + // attach rb under ra + this.parent.set(rb, ra); + this.compSize.set(ra, sa + sb); + this.compSize.delete(rb); + return true; + } + + connected(a: T, b: T): boolean { + return this.find(a) === this.find(b); + } + + size(x: T): number { + return this.compSize.get(this.find(x)) ?? 1; + } +} diff --git a/src/index.ts b/src/index.ts index 8d467c2..2924bf9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -94,6 +94,7 @@ export const examples = { paginate: 'examples/pagination.ts', diffTree: 'examples/treeDiff.ts', applyTreeDiff: 'examples/treeDiff.ts', + UnionFind: 'examples/graph.ts', }, performance: { debounce: 'examples/requestDedup.ts', @@ -1027,6 +1028,12 @@ export type { * Tree diff helpers for hierarchical data. */ export { diffTree, applyTreeDiff } from './data/treeDiff.js'; +/** + * Disjoint Set Union (Union-Find) with path compression and union by size. + * + * Example file: examples/graph.ts + */ +export { UnionFind } from './data/unionFind.js'; export type { TreeNode, diff --git a/tests/index.test.ts b/tests/index.test.ts index dcf120e..cb04ce9 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -123,6 +123,7 @@ describe('package entry point', () => { | 'paginate' | 'diffTree' | 'applyTreeDiff' + | 'UnionFind' >(); expectTypeOf>().toEqualTypeOf< diff --git a/tests/unionFind.test.ts b/tests/unionFind.test.ts new file mode 100644 index 0000000..73953c5 --- /dev/null +++ b/tests/unionFind.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import { UnionFind } from '../src/index.js'; + +describe('UnionFind', () => { + it('unions and finds components for numbers', () => { + const uf = new UnionFind([0, 1, 2, 3, 4]); + uf.union(0, 1); + uf.union(2, 3); + expect(uf.connected(0, 1)).toBe(true); + expect(uf.connected(0, 2)).toBe(false); + uf.union(1, 2); + expect(uf.connected(0, 3)).toBe(true); + expect(uf.size(0)).toBe(4); + expect(uf.size(4)).toBe(1); + }); + + it('supports string keys', () => { + const uf = new UnionFind(['a', 'b', 'c']); + uf.union('a', 'b'); + expect(uf.connected('a', 'b')).toBe(true); + expect(uf.connected('a', 'c')).toBe(false); + uf.makeSet('d'); + expect(uf.size('d')).toBe(1); + }); +}); + From faf0bddd5c2f2364c2cc243a80b64d1d9e685fd0 Mon Sep 17 00:00:00 2001 From: Francois Date: Thu, 9 Oct 2025 14:18:04 +0900 Subject: [PATCH 3/5] docs(roadmap): mark circle collision, raycasting, union-find, and maxflow as completed --- ROADMAP.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 5fea36e..eb127b3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -42,7 +42,7 @@ - [x] Diamond-square terrain height map generator - [x] L-system generator for foliage and organic structures - [x] Dungeon generation suite (BSP subdivision, rooms & corridors variants) - - [ ] Maze algorithms pack (Recursive backtracking ✅, Prim's ✅, Kruskal's ✅, Wilson's ✅, Aldous–Broder ✅, Recursive Division ✅) + - [x] Maze algorithms pack (Recursive backtracking ✅, Prim's ✅, Kruskal's ✅, Wilson's ✅, Aldous–Broder ✅, Recursive Division ✅) - Gameplay systems & utilities: - [x] Fixed-timestep game loop utility with interpolation helpers - [x] Delta-time manager for frame-independent timing @@ -95,15 +95,15 @@ **Graph algorithms** - [x] Minimum spanning tree (Kruskal) - [x] Strongly connected components (Tarjan/Kosaraju) - - [ ] Maximum flow (Dinic preferred; Edmonds–Karp fallback) + - [x] Maximum flow (Dinic preferred; Edmonds–Karp fallback) **Spatial & collision expansion** - [ ] Octree partitioning for 3D space - - [ ] Circle collision helpers - - [ ] Raycasting utilities + - [x] Circle collision helpers + - [x] Raycasting utilities - [ ] Bounding volume hierarchy (BVH) builder **Data structures** - [ ] Binary heap priority queue - - [ ] Disjoint set union (union-find) + - [x] Disjoint set union (union-find) - [ ] Bloom filter probabilistic membership - [ ] Skip list sorted structure - [ ] Segment tree range query helper From 70491fc60860491cef8b4c79fa832adcf82425c9 Mon Sep 17 00:00:00 2001 From: Francois Date: Thu, 9 Oct 2025 14:19:26 +0900 Subject: [PATCH 4/5] ci: retrigger for clean base From 855a7984ae09e7e8972af7092e680ee43341b3b8 Mon Sep 17 00:00:00 2001 From: Francois Date: Thu, 9 Oct 2025 14:21:27 +0900 Subject: [PATCH 5/5] fix: resolve stray conflict markers in spatial exports --- src/index.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2924bf9..ab88224 100644 --- a/src/index.ts +++ b/src/index.ts @@ -413,8 +413,6 @@ export { satCollision } from './spatial/sat.js'; */ export { circleRayIntersection } from './spatial/circleRay.js'; /** -<<<<<<< HEAD -<<<<<<< HEAD * Fast circle-circle overlap test. * * Example file: examples/circle.ts @@ -432,9 +430,7 @@ export { circleAabbCollision } from './spatial/circleCollision.js'; * Example file: examples/circle.ts */ export { circleSegmentIntersection } from './spatial/circleCollision.js'; -======= -======= ->>>>>>> 83f962b (feat(spatial): add raycasting utilities (raycastSegment, raycastAabb); docs + tests + example) +/** * Ray vs. segment intersection test returning closest hit. * * Example file: examples/raycast.ts @@ -446,10 +442,6 @@ export { raycastSegment } from './spatial/raycast.js'; * Example file: examples/raycast.ts */ export { raycastAabb } from './spatial/raycast.js'; -<<<<<<< HEAD ->>>>>>> 83f962b (feat(spatial): add raycasting utilities (raycastSegment, raycastAabb); docs + tests + example) -======= ->>>>>>> 83f962b (feat(spatial): add raycasting utilities (raycastSegment, raycastAabb); docs + tests + example) /** * Continuous swept AABB collision detection for moving boxes.