typescript-dsa-stl 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +594 -407
- package/dist/algorithms/graph.d.ts +49 -0
- package/dist/algorithms/graph.d.ts.map +1 -1
- package/dist/algorithms/graph.js +95 -0
- package/dist/algorithms/graph.js.map +1 -1
- package/dist/algorithms/index.d.ts +2 -2
- package/dist/algorithms/index.d.ts.map +1 -1
- package/dist/algorithms/index.js +1 -1
- package/dist/algorithms/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,17 +1,60 @@
|
|
|
1
1
|
# TypeScript_DSA
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Repository** for the npm package **[typescript-dsa-stl](https://www.npmjs.com/package/typescript-dsa-stl)** · [GitHub](https://github.com/SajidAbdullah729/TypeScript_DSA)
|
|
4
4
|
|
|
5
|
-
STL-style data structures and algorithms for TypeScript: **Vector**, **Stack**, **Queue**, **Deque**, **List**, **PriorityQueue**, **
|
|
5
|
+
STL-style data structures and algorithms for TypeScript: **Vector**, **Stack**, **Queue**, **Deque**, **List**, **PriorityQueue**, ordered/unordered **Map** and **Set**, **OrderedMultiMap** / **OrderedMultiSet**, **segment trees**, **graph** helpers (BFS, DFS, topological sort, Dijkstra, Kruskal, DSU), and **string** algorithms (KMP, Rabin–Karp, rolling hash). Install from npm for your app; this repo is the source.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Table of contents
|
|
10
|
+
|
|
11
|
+
| Section | What you’ll find |
|
|
12
|
+
|--------|-------------------|
|
|
13
|
+
| [Installation](#installation) | npm install |
|
|
14
|
+
| [Package layout & imports](#package-layout--imports) | Barrel vs subpaths (tree-shaking) |
|
|
15
|
+
| [Quick start](#quick-start) | One file showing main APIs |
|
|
16
|
+
| [API reference](#api-reference) | Export tables |
|
|
17
|
+
| [Complexity](#complexity) | Big-O for collections |
|
|
18
|
+
| [Collections](#collections) | Deque, nested vectors, multi-map / multi-set |
|
|
19
|
+
| [Segment trees](#segment-trees) | Overview, variants, and examples (one section) |
|
|
20
|
+
| [Graph algorithms](#graph-algorithms) | Adjacency lists, BFS/DFS, topological sort, components, MST, shortest paths |
|
|
21
|
+
| [String algorithms](#string-algorithms) | KMP, Rabin–Karp, rolling hash |
|
|
22
|
+
| [For maintainers](#for-maintainers) | Build and publish |
|
|
23
|
+
| [License](#license) | MIT |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
10
28
|
|
|
11
29
|
```bash
|
|
12
30
|
npm install typescript-dsa-stl
|
|
13
31
|
```
|
|
14
32
|
|
|
33
|
+
**Runtime:** Node **18+** (see `package.json` `engines`).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Package layout & imports
|
|
38
|
+
|
|
39
|
+
Import everything from the root, or use subpaths for smaller bundles:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { Vector, Stack, Queue, Deque } from 'typescript-dsa-stl/collections';
|
|
43
|
+
import {
|
|
44
|
+
sort,
|
|
45
|
+
binarySearch,
|
|
46
|
+
breadthFirstSearch,
|
|
47
|
+
depthFirstSearch,
|
|
48
|
+
topologicalSortStack,
|
|
49
|
+
topologicalSortIndegree,
|
|
50
|
+
KnuthMorrisPratt,
|
|
51
|
+
RabinKarp,
|
|
52
|
+
StringRollingHash,
|
|
53
|
+
} from 'typescript-dsa-stl/algorithms';
|
|
54
|
+
import { clamp, range } from 'typescript-dsa-stl/utils';
|
|
55
|
+
import type { Comparator } from 'typescript-dsa-stl/types';
|
|
56
|
+
```
|
|
57
|
+
|
|
15
58
|
---
|
|
16
59
|
|
|
17
60
|
## Quick start
|
|
@@ -33,6 +76,8 @@ import {
|
|
|
33
76
|
sort,
|
|
34
77
|
find,
|
|
35
78
|
binarySearch,
|
|
79
|
+
lowerBound,
|
|
80
|
+
upperBound,
|
|
36
81
|
min,
|
|
37
82
|
max,
|
|
38
83
|
clamp,
|
|
@@ -112,6 +157,58 @@ clamp(42, 0, 10); // 10
|
|
|
112
157
|
range(0, 5); // [0, 1, 2, 3, 4]
|
|
113
158
|
```
|
|
114
159
|
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## API reference
|
|
163
|
+
|
|
164
|
+
### Main export map
|
|
165
|
+
|
|
166
|
+
| Area | Exports |
|
|
167
|
+
|------|---------|
|
|
168
|
+
| **Collections** | `Vector`, `Stack`, `Queue`, `Deque`, `List`, `ListNode`, `PriorityQueue`, `OrderedMap`, `UnorderedMap`, `OrderedSet`, `UnorderedSet`, `OrderedMultiMap`, `OrderedMultiSet`, `GeneralSegmentTree`, `SegmentTree`, `SegmentTreeSum`, `SegmentTreeMin`, `SegmentTreeMax`, `LazySegmentTreeSum`, `WeightedEdge`, `AdjacencyList`, `WeightedAdjacencyList`, `createAdjacencyList`, `createWeightedAdjacencyList`, `addEdge`, `deleteEdge` |
|
|
169
|
+
| **Algorithms** | `sort`, `find`, `findIndex`, `transform`, `filter`, `reduce`, `reverse`, `unique`, `binarySearch`, `lowerBound`, `upperBound`, `min`, `max`, `partition`, `DisjointSetUnion`, `KnuthMorrisPratt`, `RabinKarp`, `RABIN_KARP_DEFAULT_MODS`, `StringRollingHash`, `breadthFirstSearch`, `depthFirstSearch`, `topologicalSortStack`, `topologicalSortIndegree`, `connectedComponents`, `kruskalMST`, `dijkstra`, `reconstructPath` |
|
|
170
|
+
| **Utils** | `clamp`, `range`, `noop`, `identity`, `swap` |
|
|
171
|
+
| **Types** | `Comparator`, `Predicate`, `UnaryFn`, `Reducer`, `IterableLike`, `toArray`, `RabinKarpTripleMods`, `WeightedUndirectedEdge`, `TopologicalSortResult`, `GeneralSegmentTreeConfig`, `SegmentCombine`, `SegmentMerge`, `SegmentLeafBuild` |
|
|
172
|
+
|
|
173
|
+
### Subpath imports (tree-shaking)
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { Vector, Stack, Queue, Deque } from 'typescript-dsa-stl/collections';
|
|
177
|
+
import { sort, binarySearch, breadthFirstSearch, depthFirstSearch, topologicalSortStack, topologicalSortIndegree, KnuthMorrisPratt, RabinKarp, StringRollingHash } from 'typescript-dsa-stl/algorithms';
|
|
178
|
+
import { clamp, range } from 'typescript-dsa-stl/utils';
|
|
179
|
+
import type { Comparator } from 'typescript-dsa-stl/types';
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Complexity
|
|
185
|
+
|
|
186
|
+
### Linear and associative structures
|
|
187
|
+
|
|
188
|
+
| Structure | Access | Insert end | Insert middle | Remove end | Remove middle |
|
|
189
|
+
|-----------|--------|------------|---------------|------------|---------------|
|
|
190
|
+
| **Vector** | O(1) | O(1)* | O(n) | O(1) | O(n) |
|
|
191
|
+
| **Stack** | — | O(1) | — | O(1) | — |
|
|
192
|
+
| **Queue** | — | O(1)* | — | O(1)* | — |
|
|
193
|
+
| **Deque** | O(1) | O(1)* (front/back) | — | O(1)* (front/back) | — |
|
|
194
|
+
| **List** | O(n) | O(1) | O(1)** | O(1) | O(1)** |
|
|
195
|
+
| **PriorityQueue** | — | O(log n) | — | O(log n) | — |
|
|
196
|
+
| **OrderedMap** (Map) | O(log n) get | O(log n) set | — | O(log n) delete | — |
|
|
197
|
+
| **UnorderedMap** | O(1)* get/set | O(1)* | — | O(1)* delete | — |
|
|
198
|
+
| **OrderedSet** (Set) | O(log n) has | O(log n) add | — | O(log n) delete | — |
|
|
199
|
+
| **UnorderedSet** | O(1)* has/add | O(1)* | — | O(1)* delete | — |
|
|
200
|
+
| **OrderedMultiMap** | O(log n) get | O(log n) set | — | O(log n) delete | — |
|
|
201
|
+
| **OrderedMultiSet** | O(log n) has/count | O(log n) add | — | O(log n) delete | — |
|
|
202
|
+
|
|
203
|
+
\* Amortized (hash).
|
|
204
|
+
\** At a known node.
|
|
205
|
+
|
|
206
|
+
Segment-tree time and space behaviour is documented in [Segment trees](#segment-trees) (overview and table at the start of that section).
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Collections
|
|
211
|
+
|
|
115
212
|
### Deque (like C++ `std::deque`)
|
|
116
213
|
|
|
117
214
|
A **double-ended queue**: amortized **O(1)** `pushFront` / `pushBack` / `popFront` / `popBack`, and **O(1)** random access via `at` / `set`. Implemented as a growable circular buffer (same asymptotics as a typical `std::deque` for these operations).
|
|
@@ -178,181 +275,524 @@ cube.push(layer0);
|
|
|
178
275
|
cube.at(0).at(1).at(0); // 3 (layer 0, row 1, col 0)
|
|
179
276
|
```
|
|
180
277
|
|
|
181
|
-
###
|
|
278
|
+
### OrderedMultiMap and OrderedMultiSet — use cases
|
|
182
279
|
|
|
183
|
-
|
|
280
|
+
**OrderedMultiSet** is a sorted collection that allows duplicate elements (like C++ `std::multiset`). Use it when you need ordering and multiple copies of the same value.
|
|
184
281
|
|
|
185
|
-
|
|
282
|
+
| Use case | Example |
|
|
283
|
+
|----------|---------|
|
|
284
|
+
| **Sorted runs / leaderboard with ties** | Store scores; multiple users can have the same score. Iterate in sorted order, use `count(score)` for ties. |
|
|
285
|
+
| **Event timeline with repeated timestamps** | Add events by time; several events can share the same time. `add(timestamp)`, iterate in order. |
|
|
286
|
+
| **K-th smallest in a multiset** | Keep elements sorted; k-th element is at index `k - 1` in iteration. |
|
|
287
|
+
| **Range counts** | Combined with binary search ideas: count elements in `[low, high]` using `count` and iteration. |
|
|
186
288
|
|
|
187
|
-
C
|
|
289
|
+
**OrderedMultiMap** maps one key to multiple values while keeping keys sorted (like C++ `std::multimap`). Use it when a key can have several associated values and you need key order.
|
|
188
290
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
291
|
+
| Use case | Example |
|
|
292
|
+
|----------|---------|
|
|
293
|
+
| **Inverted index** | Key = term, values = document IDs containing that term. `set(term, docId)` for each occurrence; `getAll(term)` returns all doc IDs. |
|
|
294
|
+
| **Grouping by key** | Key = category, values = items. `set(category, item)`; iterate keys in order, use `getAll(key)` per group. |
|
|
295
|
+
| **One-to-many relations** | Key = user ID, values = session IDs. `set(userId, sessionId)`; `getAll(userId)` lists all sessions. |
|
|
296
|
+
| **Time-series by bucket** | Key = time bucket, values = events. Sorted keys give chronological buckets; `getAll(bucket)` gets events in that bucket. |
|
|
194
297
|
|
|
195
|
-
|
|
298
|
+
**OrderedMultiSet example:**
|
|
196
299
|
|
|
197
300
|
```ts
|
|
198
|
-
import {
|
|
199
|
-
|
|
200
|
-
const n = 5;
|
|
201
|
-
const graph = createAdjacencyList(n); // empty graph with n vertices
|
|
202
|
-
|
|
203
|
-
// C++: graph[u].push_back(v);
|
|
204
|
-
graph[u].push(v);
|
|
301
|
+
import { OrderedMultiSet } from 'typescript-dsa-stl';
|
|
205
302
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
303
|
+
const scores = new OrderedMultiSet<number>();
|
|
304
|
+
scores.add(85); scores.add(92); scores.add(85); scores.add(78);
|
|
305
|
+
console.log(scores.toArray()); // [78, 85, 85, 92]
|
|
306
|
+
console.log(scores.count(85)); // 2
|
|
307
|
+
scores.delete(85); // remove one 85
|
|
308
|
+
console.log(scores.count(85)); // 1
|
|
309
|
+
scores.deleteAll(85); // remove all 85s
|
|
210
310
|
```
|
|
211
311
|
|
|
212
|
-
|
|
312
|
+
**OrderedMultiMap example:**
|
|
213
313
|
|
|
214
314
|
```ts
|
|
215
|
-
import {
|
|
216
|
-
|
|
217
|
-
const graph = createAdjacencyList(5);
|
|
218
|
-
|
|
219
|
-
addEdge(graph, u, v); // add u -> v
|
|
220
|
-
deleteEdge(graph, u, v); // remove all edges u -> v
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
#### Weighted adjacency list
|
|
224
|
-
|
|
225
|
-
In C++ you might write:
|
|
315
|
+
import { OrderedMultiMap } from 'typescript-dsa-stl';
|
|
226
316
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
317
|
+
const index = new OrderedMultiMap<string, number>(); // term -> doc IDs
|
|
318
|
+
index.set('typescript', 1); index.set('typescript', 3); index.set('stl', 2);
|
|
319
|
+
console.log(index.getAll('typescript')); // [1, 3]
|
|
320
|
+
console.log(index.get('stl')); // 2
|
|
321
|
+
for (const [key, value] of index) {
|
|
322
|
+
console.log(key, value); // keys in sorted order
|
|
323
|
+
}
|
|
231
324
|
```
|
|
232
325
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
```ts
|
|
236
|
-
import { createWeightedAdjacencyList } from 'typescript-dsa-stl/collections';
|
|
326
|
+
---
|
|
237
327
|
|
|
238
|
-
|
|
239
|
-
const graph = createWeightedAdjacencyList(n); // empty weighted graph with n vertices
|
|
328
|
+
## Segment trees
|
|
240
329
|
|
|
241
|
-
|
|
242
|
-
graph[u].push({ to: v, weight: w });
|
|
330
|
+
### Segment tree overview and complexity
|
|
243
331
|
|
|
244
|
-
|
|
245
|
-
for (const { to, weight } of graph[u]) {
|
|
246
|
-
// edge u -> to with cost = weight
|
|
247
|
-
}
|
|
248
|
-
```
|
|
332
|
+
A segment tree is an indexable array backed by a tree so **range questions** (sum, min, max, or your own combine) and **updates** cost **O(log n)** instead of scanning the whole slice.
|
|
249
333
|
|
|
250
|
-
|
|
334
|
+
Segment trees support **range queries** and **point updates** in **O(log n)**. Range endpoints are **inclusive**: `query(l, r)` covers indices `l` through `r`.
|
|
251
335
|
|
|
252
|
-
|
|
253
|
-
import { createWeightedAdjacencyList, addEdge, deleteEdge } from 'typescript-dsa-stl/collections';
|
|
336
|
+
**What each type does:**
|
|
254
337
|
|
|
255
|
-
|
|
338
|
+
| Type | Does |
|
|
339
|
+
|------|------|
|
|
340
|
+
| **SegmentTreeSum** / **Min** / **Max** | Fixed numeric range **sum**, **min**, or **max** with **one index updated at a time**. |
|
|
341
|
+
| **SegmentTree** (generic) | Your own **associative** combine over ranges; same type for array entries and node values. |
|
|
342
|
+
| **GeneralSegmentTree** | Array stores raw **V**, nodes hold a summary **T** built with **merge** and **buildLeaf**. |
|
|
343
|
+
| **LazySegmentTreeSum** | **Add the same delta to a whole range**, optional **single-cell set**, and **range sum** (lazy tags). |
|
|
256
344
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
345
|
+
| Structure | Build | Point update | Range query | Extra |
|
|
346
|
+
|-----------|-------|--------------|-------------|--------|
|
|
347
|
+
| **GeneralSegmentTree**, **SegmentTree**, **SegmentTreeSum** / **Min** / **Max** | O(n) | O(log n) | O(log n) | Inclusive `[l, r]`; **GeneralSegmentTree** keeps raw `V` and uses `merge` + `buildLeaf` |
|
|
348
|
+
| **LazySegmentTreeSum** | O(n) | `set`: O(log n) | `rangeSum`: O(log n) | `rangeAdd` on a range: O(log n) |
|
|
260
349
|
|
|
261
|
-
|
|
350
|
+
### Segment tree: Sum, Min, Max and example
|
|
262
351
|
|
|
263
|
-
|
|
352
|
+
- **`SegmentTreeSum`** — answers “what is the **sum** from `l` to `r`?” after you **`update(i, value)`** on one index.
|
|
353
|
+
- **`SegmentTreeMin`** — answers “what is the **minimum** in `[l, r]`?” after single-index updates.
|
|
354
|
+
- **`SegmentTreeMax`** — answers “what is the **maximum** in `[l, r]`?” after single-index updates.
|
|
264
355
|
|
|
265
|
-
|
|
266
|
-
|----------|-------------|
|
|
267
|
-
| **BFS / DFS, connectivity** | Unweighted: shortest path in terms of hop count, connected components, cycle detection. |
|
|
268
|
-
| **Shortest path (Dijkstra), MST** | Weighted: edge weights as distances or costs; run Dijkstra, Prim, or Kruskal on the list. |
|
|
269
|
-
| **Social / dependency graphs** | Unweighted or weighted: followers, dependencies (e.g. build order), recommendation graphs. |
|
|
270
|
-
| **Grid / game graphs** | Unweighted: 4- or 8-neighbor grids; weighted if movement costs differ per cell. |
|
|
271
|
-
| **Network / flow** | Weighted: capacities or latencies on edges for max-flow or routing. |
|
|
356
|
+
Together they are fixed numeric implementations: build from initial values, **`update(i, value)`** for one index, **`query(l, r)`** for an inclusive range.
|
|
272
357
|
|
|
273
|
-
|
|
358
|
+
```ts
|
|
359
|
+
import {
|
|
360
|
+
SegmentTreeSum,
|
|
361
|
+
SegmentTreeMin,
|
|
362
|
+
SegmentTreeMax,
|
|
363
|
+
} from 'typescript-dsa-stl';
|
|
274
364
|
|
|
275
|
-
|
|
365
|
+
const sum = new SegmentTreeSum([1, 2, 3, 4]);
|
|
366
|
+
console.log(sum.query(0, 3)); // 10
|
|
367
|
+
sum.update(1, 10);
|
|
368
|
+
console.log(sum.query(0, 3)); // 1 + 10 + 3 + 4 = 18
|
|
276
369
|
|
|
277
|
-
|
|
370
|
+
const mn = new SegmentTreeMin([5, 2, 8, 1]);
|
|
371
|
+
console.log(mn.query(0, 3)); // 1
|
|
278
372
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
/ \
|
|
282
|
-
1 2
|
|
283
|
-
\ /
|
|
284
|
-
3
|
|
373
|
+
const mx = new SegmentTreeMax([5, 2, 8, 1]);
|
|
374
|
+
console.log(mx.query(0, 3)); // 8
|
|
285
375
|
```
|
|
286
376
|
|
|
287
|
-
|
|
377
|
+
**Example — analytics / reporting (fixed buckets, range totals, single-day corrections):** each index is a **fixed bucket** (hour, day, version slot, …). You ask for the **sum** from bucket `a` through `b` and sometimes **fix one bucket** after late data or reconciliation — same API as above, wrapped for clarity.
|
|
288
378
|
|
|
289
379
|
```ts
|
|
290
|
-
import {
|
|
291
|
-
createAdjacencyList,
|
|
292
|
-
addEdge,
|
|
293
|
-
breadthFirstSearch,
|
|
294
|
-
depthFirstSearch,
|
|
295
|
-
} from 'typescript-dsa-stl';
|
|
296
|
-
|
|
297
|
-
const n = 4;
|
|
298
|
-
const graph = createAdjacencyList(n);
|
|
299
|
-
|
|
300
|
-
// Undirected diamond: add both directions for each edge
|
|
301
|
-
addEdge(graph, 0, 1);
|
|
302
|
-
addEdge(graph, 1, 0);
|
|
303
|
-
addEdge(graph, 0, 2);
|
|
304
|
-
addEdge(graph, 2, 0);
|
|
305
|
-
addEdge(graph, 1, 3);
|
|
306
|
-
addEdge(graph, 3, 1);
|
|
307
|
-
addEdge(graph, 2, 3);
|
|
308
|
-
addEdge(graph, 3, 2);
|
|
380
|
+
import { SegmentTreeSum } from 'typescript-dsa-stl';
|
|
309
381
|
|
|
310
|
-
|
|
382
|
+
/** Revenue (or events, page views, API calls) per calendar day; index 0 = first day of period. */
|
|
383
|
+
class PeriodMetrics {
|
|
384
|
+
private readonly tree: SegmentTreeSum;
|
|
311
385
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
386
|
+
constructor(dailyValues: readonly number[]) {
|
|
387
|
+
this.tree = new SegmentTreeSum(dailyValues);
|
|
388
|
+
}
|
|
315
389
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
390
|
+
/** Total for an inclusive day range — e.g. chart drill-down or export row. */
|
|
391
|
+
totalBetweenDay(firstDayIndex: number, lastDayIndex: number): number {
|
|
392
|
+
return this.tree.query(firstDayIndex, lastDayIndex);
|
|
393
|
+
}
|
|
319
394
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
395
|
+
/** Backfill or correct one day without rebuilding the whole series. */
|
|
396
|
+
setDay(dayIndex: number, amount: number): void {
|
|
397
|
+
this.tree.update(dayIndex, amount);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
323
400
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
console.log(breadthFirstSearch(5, withIsolated, 0)); // [0, 1] — not [0,1,2,3,4]
|
|
401
|
+
const january = new PeriodMetrics([1200, 980, 1100, 1050, 1300]);
|
|
402
|
+
console.log(january.totalBetweenDay(0, 4)); // full period
|
|
403
|
+
january.setDay(2, 1150); // corrected day 2
|
|
404
|
+
console.log(january.totalBetweenDay(1, 3)); // sum over days 1..3
|
|
329
405
|
```
|
|
330
406
|
|
|
331
|
-
**
|
|
332
|
-
|
|
333
|
-
- **Directed graphs:** only list outgoing edges in `adj[u]`; traversal follows arcs from `start`.
|
|
334
|
-
- **Disconnected graphs:** run again from another unvisited `start`, or use `connectedComponents` to enumerate components first.
|
|
335
|
-
- **Weighted graphs:** for traversal ignoring weights, use the same vertex lists as the unweighted graph (weights are ignored by these two functions).
|
|
407
|
+
In production you would usually **persist** the underlying series in a database and **rebuild** the tree when the period reloads; the tree stays useful in memory for dashboards, simulations, or request handlers that see heavy read/update traffic on the same window.
|
|
336
408
|
|
|
337
|
-
|
|
409
|
+
### Generic SegmentTree
|
|
338
410
|
|
|
339
|
-
|
|
411
|
+
**`SegmentTree<T>`** supports range queries for **any associative operation** (gcd, concatenation, bitwise OR, …) on a fixed-length array, with **point updates**, when element type and aggregate type are the same — pass an **associative** `combine` and a **neutral** value for query ranges that miss a segment (e.g. `0` for sum, `Infinity` for min).
|
|
340
412
|
|
|
341
413
|
```ts
|
|
342
|
-
import {
|
|
414
|
+
import { SegmentTree } from 'typescript-dsa-stl';
|
|
343
415
|
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
416
|
+
const gcdTree = new SegmentTree<number>(
|
|
417
|
+
[12, 18, 24],
|
|
418
|
+
(a, b) => {
|
|
419
|
+
let x = a;
|
|
420
|
+
let y = b;
|
|
421
|
+
while (y !== 0) {
|
|
422
|
+
const t = y;
|
|
423
|
+
y = x % y;
|
|
424
|
+
x = t;
|
|
425
|
+
}
|
|
426
|
+
return x;
|
|
427
|
+
},
|
|
428
|
+
0
|
|
429
|
+
);
|
|
430
|
+
console.log(gcdTree.query(0, 2)); // gcd(12, 18, 24) === 6
|
|
431
|
+
|
|
432
|
+
// Non-numeric example: concatenate strings
|
|
433
|
+
const strTree = new SegmentTree<string>(
|
|
434
|
+
['a', 'b', 'c'],
|
|
435
|
+
(a, b) => a + b,
|
|
436
|
+
''
|
|
437
|
+
);
|
|
438
|
+
console.log(strTree.query(0, 2)); // 'abc'
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### GeneralSegmentTree
|
|
442
|
+
|
|
443
|
+
**`GeneralSegmentTree<T, V>`** keeps **raw** values of type **V** in the array while each segment stores a **different** summary type **T** (e.g. raw numbers in the array, but nodes keep sums of squares or custom stats).
|
|
444
|
+
|
|
445
|
+
You supply:
|
|
446
|
+
|
|
447
|
+
- **`merge(left, right)`** — combine two child aggregates (internal nodes).
|
|
448
|
+
- **`neutral`** — identity for `merge` when a query does not overlap a segment.
|
|
449
|
+
- **`buildLeaf(value, index)`** — build the leaf from the raw array on initial construction and on every `update`.
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
import { GeneralSegmentTree } from 'typescript-dsa-stl';
|
|
453
|
+
|
|
454
|
+
// Store sum of squares; raw array is plain numbers
|
|
455
|
+
const st = new GeneralSegmentTree<number, number>([1, 2, 3], {
|
|
456
|
+
merge: (a, b) => a + b,
|
|
457
|
+
neutral: 0,
|
|
458
|
+
buildLeaf: (v, i) => v * v + i,
|
|
459
|
+
});
|
|
460
|
+
console.log(st.query(0, 2)); // (1+0) + (4+1) + (9+2) = 17
|
|
461
|
+
st.update(1, 4);
|
|
462
|
+
console.log(st.rawAt(1)); // 4 — current raw value at index 1
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### LazySegmentTreeSum and example
|
|
466
|
+
|
|
467
|
+
**`LazySegmentTreeSum`** maintains a numeric array where you can **add a constant to every element in a range**, **overwrite one cell**, and query **range sums** — all in **O(log n)** via lazy propagation (unlike the trees above, which only support point updates).
|
|
468
|
+
|
|
469
|
+
**`rangeAdd(l, r, delta)`** adds `delta` to every element in the inclusive range. **`rangeSum(l, r)`** returns the sum. **`set(i, value)`** assigns one position (lazy tags are applied along the path). All are **O(log n)** — see the complexity table in the overview above.
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
import { LazySegmentTreeSum } from 'typescript-dsa-stl';
|
|
473
|
+
|
|
474
|
+
const lazy = new LazySegmentTreeSum([0, 0, 0, 0]);
|
|
475
|
+
lazy.rangeAdd(1, 2, 5); // indices 1 and 2 get +5
|
|
476
|
+
console.log(lazy.rangeSum(0, 3)); // 10
|
|
477
|
+
lazy.set(0, 100);
|
|
478
|
+
console.log(lazy.rangeSum(0, 3)); // 100 + 5 + 5 + 0
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**Example — bulk adjustment on a slice, then aggregate:** apply the **same delta** to **every** element in an index range (bonuses, prorated credits, simulation shocks), then query **range sums** without updating each cell one by one.
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
import { LazySegmentTreeSum } from 'typescript-dsa-stl';
|
|
485
|
+
|
|
486
|
+
/** Example: per-seat or per-row amounts; apply a flat bonus to ranks 10–50 (0-based 9..49), then sum a sub-range for a sub-team. */
|
|
487
|
+
function simulateBulkBonusAndSubtotal(seatCount: number): void {
|
|
488
|
+
// Initial per-seat values (e.g. base commission), built once
|
|
489
|
+
const base = Array.from({ length: seatCount }, (_, i) => 100 + i);
|
|
490
|
+
const amounts = new LazySegmentTreeSum(base);
|
|
491
|
+
|
|
492
|
+
// HR: +250 to everyone in seats 10–50 inclusive (indices 9..49)
|
|
493
|
+
amounts.rangeAdd(9, 49, 250);
|
|
494
|
+
|
|
495
|
+
// Finance: subtotal for seats 20–30 only
|
|
496
|
+
console.log(amounts.rangeSum(19, 29));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
simulateBulkBonusAndSubtotal(100);
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
The same idea applies to **inventory deltas** across bin ranges, **loyalty points** batch credits by user-ID band (when IDs map to contiguous indices), or **game/simulation** state where many cells gain the same buff and you query partial totals.
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## Graph algorithms
|
|
507
|
+
|
|
508
|
+
Graph helpers live on the main package and under `typescript-dsa-stl/collections` for adjacency types and factories.
|
|
509
|
+
|
|
510
|
+
### Adjacency list (like C++ `vector<vector<type>> graph(n)`)
|
|
511
|
+
|
|
512
|
+
You can model C++-style adjacency lists using the graph types and helpers exported from `typescript-dsa-stl/collections` (or the main package).
|
|
513
|
+
|
|
514
|
+
#### Unweighted adjacency list
|
|
515
|
+
|
|
516
|
+
C++:
|
|
517
|
+
|
|
518
|
+
```cpp
|
|
519
|
+
int n = 5;
|
|
520
|
+
vector<vector<int>> graph(n);
|
|
521
|
+
graph[u].push_back(v); // or graph[u].pb(v);
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
TypeScript (easy declaration with `createAdjacencyList`):
|
|
525
|
+
|
|
526
|
+
```ts
|
|
527
|
+
import { createAdjacencyList } from 'typescript-dsa-stl/collections';
|
|
528
|
+
|
|
529
|
+
const n = 5;
|
|
530
|
+
const graph = createAdjacencyList(n); // empty graph with n vertices
|
|
531
|
+
|
|
532
|
+
// C++: graph[u].push_back(v);
|
|
533
|
+
graph[u].push(v);
|
|
534
|
+
|
|
535
|
+
// Iteration is the same idea as in C++
|
|
536
|
+
for (const v of graph[u]) {
|
|
537
|
+
// neighbor v
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
Or with helpers `addEdge` / `deleteEdge`:
|
|
542
|
+
|
|
543
|
+
```ts
|
|
544
|
+
import { createAdjacencyList, addEdge, deleteEdge } from 'typescript-dsa-stl/collections';
|
|
545
|
+
|
|
546
|
+
const graph = createAdjacencyList(5);
|
|
547
|
+
|
|
548
|
+
addEdge(graph, u, v); // add u -> v
|
|
549
|
+
deleteEdge(graph, u, v); // remove all edges u -> v
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
#### Weighted adjacency list
|
|
553
|
+
|
|
554
|
+
In C++ you might write:
|
|
555
|
+
|
|
556
|
+
```cpp
|
|
557
|
+
int n = 5;
|
|
558
|
+
vector<vector<pair<int,int>>> graph(n);
|
|
559
|
+
graph[u].push_back({v, w}); // edge u -> v with weight w
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
In TypeScript, use `createWeightedAdjacencyList` for easy declaration:
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
import { createWeightedAdjacencyList } from 'typescript-dsa-stl/collections';
|
|
566
|
+
|
|
567
|
+
const n = 5;
|
|
568
|
+
const graph = createWeightedAdjacencyList(n); // empty weighted graph with n vertices
|
|
569
|
+
|
|
570
|
+
// C++: graph[u].push_back({v, w});
|
|
571
|
+
graph[u].push({ to: v, weight: w });
|
|
572
|
+
|
|
573
|
+
// When iterating, you get both neighbor and weight
|
|
574
|
+
for (const { to, weight } of graph[u]) {
|
|
575
|
+
// edge u -> to with cost = weight
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Or with the helper functions `addEdge` / `deleteEdge`:
|
|
580
|
+
|
|
581
|
+
```ts
|
|
582
|
+
import { createWeightedAdjacencyList, addEdge, deleteEdge } from 'typescript-dsa-stl/collections';
|
|
583
|
+
|
|
584
|
+
const graph = createWeightedAdjacencyList(5);
|
|
585
|
+
|
|
586
|
+
addEdge(graph, u, v, w); // add u -> v with weight w
|
|
587
|
+
deleteEdge(graph, u, v, w); // delete all edges u -> v with weight w
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
#### Graph adjacency list — use cases
|
|
591
|
+
|
|
592
|
+
Use an **unweighted** graph (adjacency list) when you only care about connectivity; use a **weighted** graph when edges have costs (distance, time, capacity).
|
|
593
|
+
|
|
594
|
+
| Use case | When to use |
|
|
595
|
+
|----------|-------------|
|
|
596
|
+
| **BFS / DFS, connectivity** | Unweighted: shortest path in terms of hop count, connected components, cycle detection. |
|
|
597
|
+
| **Shortest path (Dijkstra), MST** | Weighted: edge weights as distances or costs; run Dijkstra, Prim, or Kruskal on the list. |
|
|
598
|
+
| **Social / dependency graphs** | Unweighted or weighted: followers, dependencies (e.g. build order), recommendation graphs. |
|
|
599
|
+
| **Grid / game graphs** | Unweighted: 4- or 8-neighbor grids; weighted if movement costs differ per cell. |
|
|
600
|
+
| **Network / flow** | Weighted: capacities or latencies on edges for max-flow or routing. |
|
|
601
|
+
|
|
602
|
+
### Breadth-first search (BFS) and depth-first search (DFS)
|
|
603
|
+
|
|
604
|
+
`breadthFirstSearch` and `depthFirstSearch` take the number of vertices `n`, an unweighted `AdjacencyList`, and a `start` vertex. They return the **visit order** for all vertices **reachable** from `start` (vertices outside that component are not included). For an undirected graph, add each edge in **both** directions (see `addEdge` below).
|
|
605
|
+
|
|
606
|
+
**Example graph (diamond):** edges `0—1`, `0—2`, `1—3`, `2—3`.
|
|
607
|
+
|
|
608
|
+
```text
|
|
609
|
+
0
|
|
610
|
+
/ \
|
|
611
|
+
1 2
|
|
612
|
+
\ /
|
|
613
|
+
3
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
With neighbors listed in ascending vertex id (`0: [1,2]`, `1: [0,3]`, …), **BFS** from `0` visits by increasing distance from `0`: first `0`, then `1` and `2`, then `3` → order `[0, 1, 2, 3]`. **DFS** (preorder, first neighbor in each list first) goes `0 → 1 → 3` then `2` → order `[0, 1, 3, 2]`. The exact DFS order depends on how you order each adjacency list.
|
|
617
|
+
|
|
618
|
+
```ts
|
|
619
|
+
import {
|
|
620
|
+
createAdjacencyList,
|
|
621
|
+
addEdge,
|
|
622
|
+
breadthFirstSearch,
|
|
623
|
+
depthFirstSearch,
|
|
624
|
+
} from 'typescript-dsa-stl';
|
|
625
|
+
|
|
626
|
+
const n = 4;
|
|
627
|
+
const graph = createAdjacencyList(n);
|
|
628
|
+
|
|
629
|
+
// Undirected diamond: add both directions for each edge
|
|
630
|
+
addEdge(graph, 0, 1);
|
|
631
|
+
addEdge(graph, 1, 0);
|
|
632
|
+
addEdge(graph, 0, 2);
|
|
633
|
+
addEdge(graph, 2, 0);
|
|
634
|
+
addEdge(graph, 1, 3);
|
|
635
|
+
addEdge(graph, 3, 1);
|
|
636
|
+
addEdge(graph, 2, 3);
|
|
637
|
+
addEdge(graph, 3, 2);
|
|
638
|
+
|
|
639
|
+
const start = 0;
|
|
640
|
+
|
|
641
|
+
// BFS: level-by-level from start (hop count); output: [0, 1, 2, 3]
|
|
642
|
+
console.log(breadthFirstSearch(n, graph, start));
|
|
643
|
+
// Expected console output: [ 0, 1, 2, 3 ]
|
|
644
|
+
|
|
645
|
+
// DFS: preorder with explicit stack; output: [0, 1, 3, 2] for this adjacency layout
|
|
646
|
+
console.log(depthFirstSearch(n, graph, start));
|
|
647
|
+
// Expected console output: [ 0, 1, 3, 2 ]
|
|
648
|
+
|
|
649
|
+
// Invalid start → empty traversal
|
|
650
|
+
console.log(breadthFirstSearch(n, graph, -1)); // []
|
|
651
|
+
console.log(depthFirstSearch(n, graph, n)); // []
|
|
652
|
+
|
|
653
|
+
// Vertex 4 isolated: BFS/DFS from 0 never visits 4
|
|
654
|
+
const withIsolated = createAdjacencyList(5);
|
|
655
|
+
addEdge(withIsolated, 0, 1);
|
|
656
|
+
addEdge(withIsolated, 1, 0);
|
|
657
|
+
console.log(breadthFirstSearch(5, withIsolated, 0)); // [0, 1] — not [0,1,2,3,4]
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
**Notes**
|
|
661
|
+
|
|
662
|
+
- **Directed graphs:** only list outgoing edges in `adj[u]`; traversal follows arcs from `start`.
|
|
663
|
+
- **Disconnected graphs:** run again from another unvisited `start`, or use `connectedComponents` to enumerate components first.
|
|
664
|
+
- **Weighted graphs:** for traversal ignoring weights, use the same vertex lists as the unweighted graph (weights are ignored by these two functions).
|
|
665
|
+
|
|
666
|
+
### Topological sort
|
|
667
|
+
|
|
668
|
+
`topologicalSortStack` (iterative DFS / finish order) and `topologicalSortIndegree` (Kahn’s algorithm, zero-indegree queue) both take `n` and a **directed** unweighted `AdjacencyList`. They return `{ order, ok }`: a permutation of `0..n-1` when `ok` is true, or failure when a directed cycle exists.
|
|
669
|
+
|
|
670
|
+
**When to use**
|
|
671
|
+
|
|
672
|
+
- **Task / build / dependency ordering:** items must happen only after their prerequisites (package install order, compile steps, course prerequisites).
|
|
673
|
+
- **Scheduling under precedence constraints:** jobs with “A before B” rules and no cycles.
|
|
674
|
+
- **Detecting cycles in a directed model:** if `ok` is false, the graph (on valid vertices `0..n-1`) is not a DAG.
|
|
675
|
+
- **Pick either algorithm:** both answer the same yes/no; choose **stack** if you want DFS-style behavior and an explicit stack; choose **indegree** (Kahn) if you prefer peeling sources level-by-level (often closer to “ready queue” mental models).
|
|
676
|
+
|
|
677
|
+
**When topological order is not possible**
|
|
678
|
+
|
|
679
|
+
- Any **directed cycle** (including a **self-loop**): `ok` is false.
|
|
680
|
+
- **Undirected** graphs modeled with **both** `u → v` and `v → u`: that is a 2-cycle, so **not** a DAG unless you only use directed edges that reflect real precedence.
|
|
681
|
+
|
|
682
|
+
**Example (how to call it and use the result)**
|
|
683
|
+
|
|
684
|
+
Both functions return the same shape: **`TopologicalSortResult`** — `{ order: number[]; ok: boolean }`.
|
|
685
|
+
|
|
686
|
+
- **`ok === true`:** `order` is a **permutation of `0..n-1`**; every edge `u → v` appears with `u` before `v` in `order`.
|
|
687
|
+
- **`ok === false`:** **no** full topological order exists (directed cycle). For `topologicalSortStack`, `order` is `[]`. For `topologicalSortIndegree`, `order` may list only some vertices; **do not** treat it as a complete sort.
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
import {
|
|
691
|
+
createAdjacencyList,
|
|
692
|
+
addEdge,
|
|
693
|
+
topologicalSortStack,
|
|
694
|
+
topologicalSortIndegree,
|
|
695
|
+
type TopologicalSortResult,
|
|
696
|
+
} from 'typescript-dsa-stl';
|
|
697
|
+
|
|
698
|
+
const n = 4;
|
|
699
|
+
const g = createAdjacencyList(n);
|
|
700
|
+
addEdge(g, 0, 1);
|
|
701
|
+
addEdge(g, 0, 2);
|
|
702
|
+
addEdge(g, 1, 3);
|
|
703
|
+
addEdge(g, 2, 3);
|
|
704
|
+
|
|
705
|
+
// Whole result (typed)
|
|
706
|
+
const result: TopologicalSortResult = topologicalSortStack(n, g);
|
|
707
|
+
|
|
708
|
+
// Usually destructure
|
|
709
|
+
const { order, ok } = topologicalSortIndegree(n, g);
|
|
710
|
+
|
|
711
|
+
if (ok) {
|
|
712
|
+
// `order` here is from `topologicalSortIndegree` above → [0, 1, 2, 3] for this graph.
|
|
713
|
+
|
|
714
|
+
// 1) See the whole sequence at once
|
|
715
|
+
console.log(order);
|
|
716
|
+
// → [ 0, 1, 2, 3 ] (Node.js / browser consoles may add line breaks or “Array(4)” styling)
|
|
717
|
+
|
|
718
|
+
// 2) How many steps (same as n when ok is true)
|
|
719
|
+
console.log(order.length);
|
|
720
|
+
// → 4
|
|
721
|
+
|
|
722
|
+
// 3) Pick by position: first task, second task, …
|
|
723
|
+
const first = order[0];
|
|
724
|
+
const second = order[1];
|
|
725
|
+
console.log('do vertex', first, 'before', second);
|
|
726
|
+
// → do vertex 0 before 1
|
|
727
|
+
|
|
728
|
+
// 4) Simple loop with indices
|
|
729
|
+
for (let i = 0; i < order.length; i++) {
|
|
730
|
+
console.log('step', i + 1, '→ vertex', order[i]);
|
|
731
|
+
}
|
|
732
|
+
// → step 1 → vertex 0
|
|
733
|
+
// → step 2 → vertex 1
|
|
734
|
+
// → step 3 → vertex 2
|
|
735
|
+
// → step 4 → vertex 3
|
|
736
|
+
|
|
737
|
+
// 5) Same loop, shorter (when you only need the vertex id)
|
|
738
|
+
for (const vertex of order) {
|
|
739
|
+
console.log('run job for vertex', vertex);
|
|
740
|
+
}
|
|
741
|
+
// → run job for vertex 0
|
|
742
|
+
// → run job for vertex 1
|
|
743
|
+
// → run job for vertex 2
|
|
744
|
+
// → run job for vertex 3
|
|
745
|
+
|
|
746
|
+
// 6) Optional: each number is an index into your own list of names
|
|
747
|
+
const jobNames = ['bootstrap', 'compileA', 'compileB', 'link'];
|
|
748
|
+
const readable = order.map((vertex) => jobNames[vertex]);
|
|
749
|
+
console.log(readable.join(' → '));
|
|
750
|
+
// → bootstrap → compileA → compileB → link
|
|
751
|
+
} else {
|
|
752
|
+
// No valid order exists (cycle). Use a flag, return early, or show an error.
|
|
753
|
+
console.error('Graph has a cycle; cannot topologically sort.');
|
|
754
|
+
// → Graph has a cycle; cannot topologically sort.
|
|
755
|
+
// (often printed on stderr; some runtimes prepend “Error” styling)
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Compare algorithms (same `ok` on a given graph; `order` may differ)
|
|
759
|
+
const a = topologicalSortStack(n, g);
|
|
760
|
+
const b = topologicalSortIndegree(n, g);
|
|
761
|
+
console.log(a.ok, b.ok);
|
|
762
|
+
// → true true
|
|
763
|
+
// (here `a.order` is [0, 2, 1, 3] and `b.order` is [0, 1, 2, 3] — both valid)
|
|
764
|
+
|
|
765
|
+
// Cycle: 0 → 1 → 2 → 0
|
|
766
|
+
const cyclic = createAdjacencyList(3);
|
|
767
|
+
addEdge(cyclic, 0, 1);
|
|
768
|
+
addEdge(cyclic, 1, 2);
|
|
769
|
+
addEdge(cyclic, 2, 0);
|
|
770
|
+
const bad = topologicalSortStack(3, cyclic);
|
|
771
|
+
console.log(bad.ok);
|
|
772
|
+
// → false
|
|
773
|
+
console.log(bad.order);
|
|
774
|
+
// → []
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### Disjoint Set Union (Union-Find)
|
|
778
|
+
|
|
779
|
+
Use Union-Find (DSU) to compute connected components efficiently. It merges endpoints of every edge in the adjacency list, so for directed graphs it returns weak connectivity components.
|
|
780
|
+
|
|
781
|
+
```ts
|
|
782
|
+
import { createAdjacencyList, connectedComponents } from 'typescript-dsa-stl';
|
|
783
|
+
|
|
784
|
+
const n = 5;
|
|
785
|
+
const graph = createAdjacencyList(n);
|
|
786
|
+
graph[0].push(1);
|
|
787
|
+
graph[1].push(0);
|
|
788
|
+
graph[3].push(4);
|
|
349
789
|
graph[4].push(3);
|
|
350
790
|
|
|
351
791
|
const comps = connectedComponents(n, graph);
|
|
352
792
|
// e.g. [[0, 1], [2], [3, 4]]
|
|
353
793
|
```
|
|
354
794
|
|
|
355
|
-
|
|
795
|
+
#### Traverse the result
|
|
356
796
|
|
|
357
797
|
`connectedComponents(n, adj)` returns `number[][]` where each inner array is a component (list of vertices).
|
|
358
798
|
|
|
@@ -369,7 +809,7 @@ for (const comp of comps) {
|
|
|
369
809
|
const sizes = comps.map(comp => comp.length);
|
|
370
810
|
```
|
|
371
811
|
|
|
372
|
-
|
|
812
|
+
### Kruskal MST (uses DSU)
|
|
373
813
|
|
|
374
814
|
For a weighted graph, `kruskalMST` builds a Minimum Spanning Tree (MST) using DSU.
|
|
375
815
|
|
|
@@ -393,7 +833,28 @@ const { edges, totalWeight } = kruskalMST(n, wGraph, { undirected: true });
|
|
|
393
833
|
// edges: MST edges (chosen by weight), totalWeight: sum of weights
|
|
394
834
|
```
|
|
395
835
|
|
|
396
|
-
####
|
|
836
|
+
#### Traverse the MST
|
|
837
|
+
|
|
838
|
+
`kruskalMST(...)` returns `{ edges, totalWeight }`. To traverse the MST like a graph, convert `edges` into an adjacency list:
|
|
839
|
+
|
|
840
|
+
```ts
|
|
841
|
+
import { createWeightedAdjacencyList } from 'typescript-dsa-stl/collections';
|
|
842
|
+
|
|
843
|
+
const mstAdj = createWeightedAdjacencyList(n);
|
|
844
|
+
|
|
845
|
+
for (const { u, v, weight } of edges) {
|
|
846
|
+
// MST is undirected (we used { undirected: true })
|
|
847
|
+
mstAdj[u].push({ to: v, weight });
|
|
848
|
+
mstAdj[v].push({ to: u, weight });
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Example: iterate neighbors of vertex 0 in the MST
|
|
852
|
+
for (const { to, weight } of mstAdj[0]) {
|
|
853
|
+
// visit edge 0 -> to (weight)
|
|
854
|
+
}
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
### Dijkstra shortest paths
|
|
397
858
|
|
|
398
859
|
`dijkstra` computes single-source shortest paths on a **weighted** graph with **non-negative** edge weights.
|
|
399
860
|
It returns:
|
|
@@ -425,32 +886,15 @@ const path = reconstructPath(prev, 0, target); // [0, ..., target] or [] if unre
|
|
|
425
886
|
console.log(path); // [0, 1, 2, 4]
|
|
426
887
|
```
|
|
427
888
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
`kruskalMST(...)` returns `{ edges, totalWeight }`. To traverse the MST like a graph, convert `edges` into an adjacency list:
|
|
431
|
-
|
|
432
|
-
```ts
|
|
433
|
-
import { createWeightedAdjacencyList } from 'typescript-dsa-stl/collections';
|
|
434
|
-
|
|
435
|
-
const mstAdj = createWeightedAdjacencyList(n);
|
|
436
|
-
|
|
437
|
-
for (const { u, v, weight } of edges) {
|
|
438
|
-
// MST is undirected (we used { undirected: true })
|
|
439
|
-
mstAdj[u].push({ to: v, weight });
|
|
440
|
-
mstAdj[v].push({ to: u, weight });
|
|
441
|
-
}
|
|
889
|
+
---
|
|
442
890
|
|
|
443
|
-
|
|
444
|
-
for (const { to, weight } of mstAdj[0]) {
|
|
445
|
-
// visit edge 0 -> to (weight)
|
|
446
|
-
}
|
|
447
|
-
```
|
|
891
|
+
## String algorithms
|
|
448
892
|
|
|
449
|
-
|
|
893
|
+
### Knuth–Morris–Pratt (KMP), Rabin–Karp, and string rolling hash
|
|
450
894
|
|
|
451
895
|
All three work on **UTF-16 code units** (same as `String` indexing). They solve **different jobs**: KMP and Rabin–Karp are **pattern matchers** (list all start indices of a pattern in a text). `StringRollingHash` is a **substring-hash tool** on a **fixed** string—you combine it with your own logic (equality checks, binary search, etc.).
|
|
452
896
|
|
|
453
|
-
|
|
897
|
+
#### When to use which
|
|
454
898
|
|
|
455
899
|
| Goal | Prefer | Why |
|
|
456
900
|
|------|--------|-----|
|
|
@@ -555,263 +999,6 @@ console.log(a.substringHash(2, 2) === b.fullHash()); // true — both are "na"
|
|
|
555
999
|
|
|
556
1000
|
---
|
|
557
1001
|
|
|
558
|
-
## Segment trees
|
|
559
|
-
|
|
560
|
-
Segment trees support **range queries** and **point updates** in **O(log n)**. Range endpoints are **inclusive**: `query(l, r)` covers indices `l` through `r`.
|
|
561
|
-
|
|
562
|
-
### Ready-made variants (`SegmentTreeSum`, `SegmentTreeMin`, `SegmentTreeMax`)
|
|
563
|
-
|
|
564
|
-
```ts
|
|
565
|
-
import {
|
|
566
|
-
SegmentTreeSum,
|
|
567
|
-
SegmentTreeMin,
|
|
568
|
-
SegmentTreeMax,
|
|
569
|
-
} from 'typescript-dsa-stl';
|
|
570
|
-
|
|
571
|
-
const sum = new SegmentTreeSum([1, 2, 3, 4]);
|
|
572
|
-
console.log(sum.query(0, 3)); // 10
|
|
573
|
-
sum.update(1, 10);
|
|
574
|
-
console.log(sum.query(0, 3)); // 1 + 10 + 3 + 4 = 18
|
|
575
|
-
|
|
576
|
-
const mn = new SegmentTreeMin([5, 2, 8, 1]);
|
|
577
|
-
console.log(mn.query(0, 3)); // 1
|
|
578
|
-
|
|
579
|
-
const mx = new SegmentTreeMax([5, 2, 8, 1]);
|
|
580
|
-
console.log(mx.query(0, 3)); // 8
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
### Generic `SegmentTree<T>` (custom combine + neutral)
|
|
584
|
-
|
|
585
|
-
Use the same type for array elements and aggregates. Pass an **associative** `combine` and a **neutral** value for query ranges that miss a segment (e.g. `0` for sum, `Infinity` for min).
|
|
586
|
-
|
|
587
|
-
```ts
|
|
588
|
-
import { SegmentTree } from 'typescript-dsa-stl';
|
|
589
|
-
|
|
590
|
-
const gcdTree = new SegmentTree<number>(
|
|
591
|
-
[12, 18, 24],
|
|
592
|
-
(a, b) => {
|
|
593
|
-
let x = a;
|
|
594
|
-
let y = b;
|
|
595
|
-
while (y !== 0) {
|
|
596
|
-
const t = y;
|
|
597
|
-
y = x % y;
|
|
598
|
-
x = t;
|
|
599
|
-
}
|
|
600
|
-
return x;
|
|
601
|
-
},
|
|
602
|
-
0
|
|
603
|
-
);
|
|
604
|
-
console.log(gcdTree.query(0, 2)); // gcd(12, 18, 24) === 6
|
|
605
|
-
|
|
606
|
-
// Non-numeric example: concatenate strings
|
|
607
|
-
const strTree = new SegmentTree<string>(
|
|
608
|
-
['a', 'b', 'c'],
|
|
609
|
-
(a, b) => a + b,
|
|
610
|
-
''
|
|
611
|
-
);
|
|
612
|
-
console.log(strTree.query(0, 2)); // 'abc'
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
### `GeneralSegmentTree<T, V>` (custom merge + buildLeaf)
|
|
616
|
-
|
|
617
|
-
Use when **raw** values `V` differ from the **aggregate** type `T`:
|
|
618
|
-
|
|
619
|
-
- **`merge(left, right)`** — combine two child aggregates (internal nodes).
|
|
620
|
-
- **`neutral`** — identity for `merge` when a query does not overlap a segment.
|
|
621
|
-
- **`buildLeaf(value, index)`** — build the leaf from the raw array on initial construction and on every `update`.
|
|
622
|
-
|
|
623
|
-
```ts
|
|
624
|
-
import { GeneralSegmentTree } from 'typescript-dsa-stl';
|
|
625
|
-
|
|
626
|
-
// Store sum of squares; raw array is plain numbers
|
|
627
|
-
const st = new GeneralSegmentTree<number, number>([1, 2, 3], {
|
|
628
|
-
merge: (a, b) => a + b,
|
|
629
|
-
neutral: 0,
|
|
630
|
-
buildLeaf: (v, i) => v * v + i,
|
|
631
|
-
});
|
|
632
|
-
console.log(st.query(0, 2)); // (1+0) + (4+1) + (9+2) = 17
|
|
633
|
-
st.update(1, 4);
|
|
634
|
-
console.log(st.rawAt(1)); // 4 — current raw value at index 1
|
|
635
|
-
```
|
|
636
|
-
|
|
637
|
-
### `LazySegmentTreeSum` (range add + range sum)
|
|
638
|
-
|
|
639
|
-
**`rangeAdd(l, r, delta)`** adds `delta` to every element in the inclusive range. **`rangeSum(l, r)`** returns the sum. **`set(i, value)`** assigns one position (lazy tags are applied along the path). All are **O(log n)**.
|
|
640
|
-
|
|
641
|
-
```ts
|
|
642
|
-
import { LazySegmentTreeSum } from 'typescript-dsa-stl';
|
|
643
|
-
|
|
644
|
-
const lazy = new LazySegmentTreeSum([0, 0, 0, 0]);
|
|
645
|
-
lazy.rangeAdd(1, 2, 5); // indices 1 and 2 get +5
|
|
646
|
-
console.log(lazy.rangeSum(0, 3)); // 10
|
|
647
|
-
lazy.set(0, 100);
|
|
648
|
-
console.log(lazy.rangeSum(0, 3)); // 100 + 5 + 5 + 0
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
### Real-world use cases
|
|
652
|
-
|
|
653
|
-
These patterns show up in backends and internal tools when you need **many** range queries and updates on a fixed sequence (length known up front), without scanning the whole array each time.
|
|
654
|
-
|
|
655
|
-
#### 1. Analytics or reporting: totals over a time window (with corrections)
|
|
656
|
-
|
|
657
|
-
Each index is a **fixed bucket** (hour of day, day of month, version slot, etc.). You repeatedly ask “what is the **sum** from bucket `a` through `b`?” and sometimes **fix one bucket** after late data or a reconciliation.
|
|
658
|
-
|
|
659
|
-
```ts
|
|
660
|
-
import { SegmentTreeSum } from 'typescript-dsa-stl';
|
|
661
|
-
|
|
662
|
-
/** Revenue (or events, page views, API calls) per calendar day; index 0 = first day of period. */
|
|
663
|
-
class PeriodMetrics {
|
|
664
|
-
private readonly tree: SegmentTreeSum;
|
|
665
|
-
|
|
666
|
-
constructor(dailyValues: readonly number[]) {
|
|
667
|
-
this.tree = new SegmentTreeSum(dailyValues);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/** Total for an inclusive day range — e.g. chart drill-down or export row. */
|
|
671
|
-
totalBetweenDay(firstDayIndex: number, lastDayIndex: number): number {
|
|
672
|
-
return this.tree.query(firstDayIndex, lastDayIndex);
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
/** Backfill or correct one day without rebuilding the whole series. */
|
|
676
|
-
setDay(dayIndex: number, amount: number): void {
|
|
677
|
-
this.tree.update(dayIndex, amount);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
const january = new PeriodMetrics([1200, 980, 1100, 1050, 1300]);
|
|
682
|
-
console.log(january.totalBetweenDay(0, 4)); // full period
|
|
683
|
-
january.setDay(2, 1150); // corrected day 2
|
|
684
|
-
console.log(january.totalBetweenDay(1, 3)); // sum over days 1..3
|
|
685
|
-
```
|
|
686
|
-
|
|
687
|
-
In production you would usually **persist** the underlying series in a database and **rebuild** the tree when the period reloads; the tree stays useful in memory for dashboards, simulations, or request handlers that see heavy read/update traffic on the same window.
|
|
688
|
-
|
|
689
|
-
#### 2. Operations or finance: bulk adjustment on a slice, then aggregate
|
|
690
|
-
|
|
691
|
-
You apply the **same delta** to **every** element in an index range (tiered bonuses, prorated credits, simulation shocks), then need **range sums** for reporting. A lazy sum tree avoids touching each cell one by one.
|
|
692
|
-
|
|
693
|
-
```ts
|
|
694
|
-
import { LazySegmentTreeSum } from 'typescript-dsa-stl';
|
|
695
|
-
|
|
696
|
-
/** Example: per-seat or per-row amounts; apply a flat bonus to ranks 10–50 (0-based 9..49), then sum a sub-range for a sub-team. */
|
|
697
|
-
function simulateBulkBonusAndSubtotal(seatCount: number): void {
|
|
698
|
-
// Initial per-seat values (e.g. base commission), built once
|
|
699
|
-
const base = Array.from({ length: seatCount }, (_, i) => 100 + i);
|
|
700
|
-
const amounts = new LazySegmentTreeSum(base);
|
|
701
|
-
|
|
702
|
-
// HR: +250 to everyone in seats 10–50 inclusive (indices 9..49)
|
|
703
|
-
amounts.rangeAdd(9, 49, 250);
|
|
704
|
-
|
|
705
|
-
// Finance: subtotal for seats 20–30 only
|
|
706
|
-
console.log(amounts.rangeSum(19, 29));
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
simulateBulkBonusAndSubtotal(100);
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
The same idea applies to **inventory deltas** across bin ranges, **loyalty points** batch credits by user-ID band (when IDs map to contiguous indices), or **game/simulation** state where many cells gain the same buff and you query partial totals.
|
|
713
|
-
|
|
714
|
-
---
|
|
715
|
-
|
|
716
|
-
## API overview
|
|
717
|
-
|
|
718
|
-
| Module | Exports |
|
|
719
|
-
|--------|--------|
|
|
720
|
-
| **Collections** | `Vector`, `Stack`, `Queue`, `Deque`, `List`, `ListNode`, `PriorityQueue`, `OrderedMap`, `UnorderedMap`, `OrderedSet`, `UnorderedSet`, `OrderedMultiMap`, `OrderedMultiSet`, `GeneralSegmentTree`, `SegmentTree`, `SegmentTreeSum`, `SegmentTreeMin`, `SegmentTreeMax`, `LazySegmentTreeSum`, `WeightedEdge`, `AdjacencyList`, `WeightedAdjacencyList`, `createAdjacencyList`, `createWeightedAdjacencyList`, `addEdge`, `deleteEdge` |
|
|
721
|
-
| **Algorithms** | `sort`, `find`, `findIndex`, `transform`, `filter`, `reduce`, `reverse`, `unique`, `binarySearch`, `lowerBound`, `upperBound`, `min`, `max`, `partition`, `DisjointSetUnion`, `KnuthMorrisPratt`, `RabinKarp`, `RABIN_KARP_DEFAULT_MODS`, `StringRollingHash`, `breadthFirstSearch`, `depthFirstSearch`, `connectedComponents`, `kruskalMST` |
|
|
722
|
-
| **Utils** | `clamp`, `range`, `noop`, `identity`, `swap` |
|
|
723
|
-
| **Types** | `Comparator`, `Predicate`, `UnaryFn`, `Reducer`, `IterableLike`, `toArray`, `RabinKarpTripleMods`, `GeneralSegmentTreeConfig`, `SegmentCombine`, `SegmentMerge`, `SegmentLeafBuild` |
|
|
724
|
-
|
|
725
|
-
### Subpath imports (tree-shaking)
|
|
726
|
-
|
|
727
|
-
```ts
|
|
728
|
-
import { Vector, Stack, Queue, Deque } from 'typescript-dsa-stl/collections';
|
|
729
|
-
import { sort, binarySearch, breadthFirstSearch, depthFirstSearch, KnuthMorrisPratt, RabinKarp, StringRollingHash } from 'typescript-dsa-stl/algorithms';
|
|
730
|
-
import { clamp, range } from 'typescript-dsa-stl/utils';
|
|
731
|
-
import type { Comparator } from 'typescript-dsa-stl/types';
|
|
732
|
-
```
|
|
733
|
-
|
|
734
|
-
---
|
|
735
|
-
|
|
736
|
-
## Data structures
|
|
737
|
-
|
|
738
|
-
| Structure | Access | Insert end | Insert middle | Remove end | Remove middle |
|
|
739
|
-
|-----------|--------|------------|---------------|------------|---------------|
|
|
740
|
-
| **Vector** | O(1) | O(1)* | O(n) | O(1) | O(n) |
|
|
741
|
-
| **Stack** | — | O(1) | — | O(1) | — |
|
|
742
|
-
| **Queue** | — | O(1)* | — | O(1)* | — |
|
|
743
|
-
| **Deque** | O(1) | O(1)* (front/back) | — | O(1)* (front/back) | — |
|
|
744
|
-
| **List** | O(n) | O(1) | O(1)** | O(1) | O(1)** |
|
|
745
|
-
| **PriorityQueue** | — | O(log n) | — | O(log n) | — |
|
|
746
|
-
| **OrderedMap** (Map) | O(log n) get | O(log n) set | — | O(log n) delete | — |
|
|
747
|
-
| **UnorderedMap** | O(1)* get/set | O(1)* | — | O(1)* delete | — |
|
|
748
|
-
| **OrderedSet** (Set) | O(log n) has | O(log n) add | — | O(log n) delete | — |
|
|
749
|
-
| **UnorderedSet** | O(1)* has/add | O(1)* | — | O(1)* delete | — |
|
|
750
|
-
| **OrderedMultiMap** | O(log n) get | O(log n) set | — | O(log n) delete | — |
|
|
751
|
-
| **OrderedMultiSet** | O(log n) has/count | O(log n) add | — | O(log n) delete | — |
|
|
752
|
-
|
|
753
|
-
\* Amortized (hash).
|
|
754
|
-
\** At a known node.
|
|
755
|
-
|
|
756
|
-
### Segment trees (range queries)
|
|
757
|
-
|
|
758
|
-
| Structure | Build | Point update | Range query | Extra |
|
|
759
|
-
|-----------|-------|--------------|-------------|--------|
|
|
760
|
-
| **GeneralSegmentTree**, **SegmentTree**, **SegmentTreeSum** / **Min** / **Max** | O(n) | O(log n) | O(log n) | Inclusive `[l, r]`; **GeneralSegmentTree** keeps raw `V` and uses `merge` + `buildLeaf` |
|
|
761
|
-
| **LazySegmentTreeSum** | O(n) | `set`: O(log n) | `rangeSum`: O(log n) | `rangeAdd` on a range: O(log n) |
|
|
762
|
-
|
|
763
|
-
---
|
|
764
|
-
|
|
765
|
-
## OrderedMultiMap and OrderedMultiSet — use cases
|
|
766
|
-
|
|
767
|
-
**OrderedMultiSet** is a sorted collection that allows duplicate elements (like C++ `std::multiset`). Use it when you need ordering and multiple copies of the same value.
|
|
768
|
-
|
|
769
|
-
| Use case | Example |
|
|
770
|
-
|----------|---------|
|
|
771
|
-
| **Sorted runs / leaderboard with ties** | Store scores; multiple users can have the same score. Iterate in sorted order, use `count(score)` for ties. |
|
|
772
|
-
| **Event timeline with repeated timestamps** | Add events by time; several events can share the same time. `add(timestamp)`, iterate in order. |
|
|
773
|
-
| **K-th smallest in a multiset** | Keep elements sorted; k-th element is at index `k - 1` in iteration. |
|
|
774
|
-
| **Range counts** | Combined with binary search ideas: count elements in `[low, high]` using `count` and iteration. |
|
|
775
|
-
|
|
776
|
-
**OrderedMultiMap** maps one key to multiple values while keeping keys sorted (like C++ `std::multimap`). Use it when a key can have several associated values and you need key order.
|
|
777
|
-
|
|
778
|
-
| Use case | Example |
|
|
779
|
-
|----------|---------|
|
|
780
|
-
| **Inverted index** | Key = term, values = document IDs containing that term. `set(term, docId)` for each occurrence; `getAll(term)` returns all doc IDs. |
|
|
781
|
-
| **Grouping by key** | Key = category, values = items. `set(category, item)`; iterate keys in order, use `getAll(key)` per group. |
|
|
782
|
-
| **One-to-many relations** | Key = user ID, values = session IDs. `set(userId, sessionId)`; `getAll(userId)` lists all sessions. |
|
|
783
|
-
| **Time-series by bucket** | Key = time bucket, values = events. Sorted keys give chronological buckets; `getAll(bucket)` gets events in that bucket. |
|
|
784
|
-
|
|
785
|
-
### OrderedMultiSet example
|
|
786
|
-
|
|
787
|
-
```ts
|
|
788
|
-
import { OrderedMultiSet } from 'typescript-dsa-stl';
|
|
789
|
-
|
|
790
|
-
const scores = new OrderedMultiSet<number>();
|
|
791
|
-
scores.add(85); scores.add(92); scores.add(85); scores.add(78);
|
|
792
|
-
console.log(scores.toArray()); // [78, 85, 85, 92]
|
|
793
|
-
console.log(scores.count(85)); // 2
|
|
794
|
-
scores.delete(85); // remove one 85
|
|
795
|
-
console.log(scores.count(85)); // 1
|
|
796
|
-
scores.deleteAll(85); // remove all 85s
|
|
797
|
-
```
|
|
798
|
-
|
|
799
|
-
### OrderedMultiMap example
|
|
800
|
-
|
|
801
|
-
```ts
|
|
802
|
-
import { OrderedMultiMap } from 'typescript-dsa-stl';
|
|
803
|
-
|
|
804
|
-
const index = new OrderedMultiMap<string, number>(); // term -> doc IDs
|
|
805
|
-
index.set('typescript', 1); index.set('typescript', 3); index.set('stl', 2);
|
|
806
|
-
console.log(index.getAll('typescript')); // [1, 3]
|
|
807
|
-
console.log(index.get('stl')); // 2
|
|
808
|
-
for (const [key, value] of index) {
|
|
809
|
-
console.log(key, value); // keys in sorted order
|
|
810
|
-
}
|
|
811
|
-
```
|
|
812
|
-
|
|
813
|
-
---
|
|
814
|
-
|
|
815
1002
|
## For maintainers
|
|
816
1003
|
|
|
817
1004
|
- **Build:** `npm run build` (also runs before `npm publish` via `prepublishOnly`)
|