Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 5 additions & 5 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions docs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -2926,6 +2929,20 @@ export function groupBy<T>(
key: keyof T | ((item: T) => string)
): Record<string, T[]>;

/**
* 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<T = number> {
constructor(elements?: Iterable<T>);
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
// ============================================================================
Expand Down
60 changes: 60 additions & 0 deletions src/data/unionFind.ts
Original file line number Diff line number Diff line change
@@ -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<T = number> {
private parent = new Map<T, T>();
private compSize = new Map<T, number>();

constructor(elements?: Iterable<T>) {
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;
}
}
11 changes: 8 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -412,7 +413,6 @@ export { satCollision } from './spatial/sat.js';
*/
export { circleRayIntersection } from './spatial/circleRay.js';
/**
<<<<<<< HEAD
* Fast circle-circle overlap test.
*
* Example file: examples/circle.ts
Expand All @@ -430,7 +430,7 @@ export { circleAabbCollision } from './spatial/circleCollision.js';
* Example file: examples/circle.ts
*/
export { circleSegmentIntersection } from './spatial/circleCollision.js';
=======
/**
* Ray vs. segment intersection test returning closest hit.
*
* Example file: examples/raycast.ts
Expand All @@ -442,7 +442,6 @@ export { raycastSegment } from './spatial/raycast.js';
* Example file: examples/raycast.ts
*/
export { raycastAabb } from './spatial/raycast.js';
>>>>>>> 83f962b (feat(spatial): add raycasting utilities (raycastSegment, raycastAabb); docs + tests + example)

/**
* Continuous swept AABB collision detection for moving boxes.
Expand Down Expand Up @@ -1021,6 +1020,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,
Expand Down
1 change: 1 addition & 0 deletions tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ describe('package entry point', () => {
| 'paginate'
| 'diffTree'
| 'applyTreeDiff'
| 'UnionFind'
>();

expectTypeOf<ExampleName<'search'>>().toEqualTypeOf<
Expand Down
26 changes: 26 additions & 0 deletions tests/unionFind.test.ts
Original file line number Diff line number Diff line change
@@ -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<string>(['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);
});
});