ts-sparse-set/README.md

144 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

### TS SparseSet
[![NPM version][npm-image]][npm-url]
[![Downloads][downloads-image]][downloads-url]
**TS SparseSet** is a high-performance TypeScript data structure implementing a *sparse set*, providing constant-time operations and cache-friendly dense storage. Ideal for ECS architectures, game engines, simulations, and any ID-indexed data.
---
## Features
* **O(1) Operations:** `add`, `remove`, `has`, `get`, `ensure`, and index lookups.
* **Dense Storage:** Compact arrays for optimal data locality and iteration speed.
* **Swap-Remove Semantics:** Maintains dense compactness after deletions without gaps.
* **Full Iteration Support:** Native iterators, `values()`, `ids()`, `entries()`, `forEach()`.
* **Zero-GC Hot Path:** No intermediate allocations in iteration modes.
* **TypeScript Native:** Fully typed, generic, and safe.
---
## Installation
```bash
npm install ts-sparse-set
```
---
## Usage Example
```ts
import { SparseSet } from 'ts-sparse-set';
const set = new SparseSet<number>();
set.add(10, 42);
set.add(20, 100);
console.log(set.has(10)); // true
console.log(set.get(20)); // 100
set.add(10, 77);
console.log(set.get(10)); // 77
set.remove(20);
console.log(set.has(20)); // false
// Iteration (dense order)
for (const entry of set) {
console.log(entry.id, entry.value);
}
// Using ensure(...)
const v = set.ensure(50, () => 123);
console.log(v); // 123
// Utility iteration helpers
console.log([...set.ids()]); // [10, 50]
console.log([...set.values()]); // [77, 123]
console.log(set.size()); // 2
```
---
## API Documentation
### `class SparseSet<V>`
#### Core Methods
| Method | Description |
| ---------------------------------- | --------------------------------------------------- |
| **`add(id: number, value: V): V`** | Inserts a new value or replaces an existing one. |
| **`get(id: number): V \| null`** | Retrieves a value or returns `null` if not present. |
| **`has(id: number): boolean`** | Checks if an element exists. |
| **`remove(id: number): void`** | Removes the element using O(1) swap-remove logic. |
| **`size(): number`** | Returns the number of elements stored. |
| **`clear(): void`** | Removes all elements. |
#### Iteration Helpers
| Method | Description |
| --------------------------------------- | ------------------------------------------------------- |
| **`[Symbol.iterator]()`** | Iterates over `{ id, value }` entries in dense order. |
| **`ids(): Generator<number>`** | Iterates over all ids. |
| **`values(): Generator<V>`** | Iterates over all values. |
| **`entries(): Generator<[number, V]>`** | Iterates over `[id, value]` tuples. |
| **`forEach(fn)`** | Efficient callback-based iteration without allocations. |
#### Utility Methods
| Method | Description |
| --------------------------------------------- | --------------------------------------------------- |
| **`ensure(id: number, factory: () => V): V`** | Returns existing value or inserts a new one lazily. |
| **`tryGetIndex(id: number): number`** | Returns dense index or `-1` if id does not exist. |
---
## Benchmarks
**Tested on:**
- CPU: AMD Ryzen 7 7700 (8-core, up to 5.4 GHz)
- RAM: 32 GB DDR5-5600
- OS: Windows 11 Pro
- Runtime: Node.js v24.11.1 (tsx)
- Set size: **1,000,000** entities
| Operation | Throughput (ops/sec) | Per-operation latency |
|------------------------------|----------------------------|-----------------------|
| `add()` | **30.0 31.0 million** | ~3234 ns |
| `has()` | **9.7 9.9 million** | ~101103 ns |
| `get()` | **7.6 7.7 million** | ~130132 ns |
| `remove()` (swap-remove) | **13.0 13.9 million** | ~7277 ns |
| `ensure(id, factory)` | **4.0 million** | ~250 ns |
| `size()` | **>526 million** | **~1.9 ns** |
| `forEach()` (1M elements) | **191193 passes/sec** | **~5.2 ms** |
| `ids()` / `values()` | **6465 passes/sec** | ~15.415.6 ms |
| Generator iteration | **5455 passes/sec** | ~1819 ms |
## When to Use SparseSet
SparseSet excels when:
* IDs are numeric and bounded.
* You need fast add/remove with compact dense data.
* You rely heavily on iteration speed (ECS, physics, grids, pools).
* You want predictable memory layout and minimal GC pressure.
It is **not** a general-purpose map or associative container — it is optimized for performance-critical numeric ID use cases.
---
## Contributing
Issues, PRs, and suggestions are welcome.
Project repository: **[https://github.com/yazmeyaa/ts-sparse-set](https://github.com/yazmeyaa/ts-sparse-set)**
---
[npm-image]: https://img.shields.io/npm/v/ts-sparse-set.svg?style=flat-square
[npm-url]: https://npmjs.org/package/ts-sparse-set
[downloads-image]: https://img.shields.io/npm/dm/ts-sparse-set.svg?style=flat-square
[downloads-url]: https://npmjs.org/package/ts-sparse-set