@hviana/sema 0.1.5 → 0.1.6
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/AGENTS.md +6 -5
- package/CITATION.cff +49 -0
- package/HOW_IT_WORKS.md +11 -12
- package/README.md +7 -5
- package/dist/example/demo.js +25 -25
- package/dist/example/train_base.d.ts +19 -21
- package/dist/example/train_base.js +1687 -1952
- package/dist/src/alphabet.d.ts +3 -3
- package/dist/src/alphabet.js +24 -27
- package/dist/src/alu/src/alu.d.ts +169 -194
- package/dist/src/alu/src/alu.js +374 -414
- package/dist/src/alu/src/expr.d.ts +36 -43
- package/dist/src/alu/src/expr.js +252 -278
- package/dist/src/alu/src/index.d.ts +9 -98
- package/dist/src/alu/src/index.js +10 -64
- package/dist/src/alu/src/kernel-arith.d.ts +1 -5
- package/dist/src/alu/src/kernel-arith.js +195 -285
- package/dist/src/alu/src/kernel-bits.d.ts +1 -5
- package/dist/src/alu/src/kernel-bits.js +81 -115
- package/dist/src/alu/src/kernel-logic.js +33 -63
- package/dist/src/alu/src/kernel-nd.js +155 -195
- package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
- package/dist/src/alu/src/kernel-numeric.js +259 -331
- package/dist/src/alu/src/operation.d.ts +112 -132
- package/dist/src/alu/src/operation.js +149 -163
- package/dist/src/alu/src/parser.d.ts +168 -175
- package/dist/src/alu/src/parser.js +382 -421
- package/dist/src/alu/src/resonance.d.ts +16 -25
- package/dist/src/alu/src/resonance.js +49 -56
- package/dist/src/alu/src/text.d.ts +3 -7
- package/dist/src/alu/src/text.js +31 -37
- package/dist/src/alu/src/value.d.ts +14 -14
- package/dist/src/alu/src/value.js +150 -160
- package/dist/src/alu/test/alu.test.js +543 -656
- package/dist/src/bytes.d.ts +1 -5
- package/dist/src/bytes.js +33 -40
- package/dist/src/config.d.ts +99 -102
- package/dist/src/config.js +78 -83
- package/dist/src/derive/src/deduction.d.ts +60 -62
- package/dist/src/derive/src/deduction.js +103 -109
- package/dist/src/derive/src/index.d.ts +1 -7
- package/dist/src/derive/src/priority-queue.d.ts +8 -8
- package/dist/src/derive/src/priority-queue.js +57 -61
- package/dist/src/derive/src/rewrite.d.ts +15 -18
- package/dist/src/derive/src/rewrite.js +59 -67
- package/dist/src/derive/src/trie.d.ts +56 -56
- package/dist/src/derive/src/trie.js +175 -179
- package/dist/src/derive/test/derive.test.js +96 -100
- package/dist/src/extension.d.ts +13 -16
- package/dist/src/geometry.d.ts +17 -42
- package/dist/src/geometry.js +235 -266
- package/dist/src/index.d.ts +2 -17
- package/dist/src/index.js +2 -12
- package/dist/src/ingest-cache.d.ts +25 -30
- package/dist/src/ingest-cache.js +108 -127
- package/dist/src/mind/articulation.d.ts +1 -5
- package/dist/src/mind/articulation.js +77 -112
- package/dist/src/mind/attention.d.ts +40 -108
- package/dist/src/mind/attention.js +749 -871
- package/dist/src/mind/canonical.d.ts +4 -19
- package/dist/src/mind/canonical.js +27 -31
- package/dist/src/mind/graph-search.d.ts +201 -225
- package/dist/src/mind/graph-search.js +742 -821
- package/dist/src/mind/index.d.ts +2 -10
- package/dist/src/mind/junction.d.ts +31 -58
- package/dist/src/mind/junction.js +172 -237
- package/dist/src/mind/learning.d.ts +16 -52
- package/dist/src/mind/learning.js +143 -165
- package/dist/src/mind/match.d.ts +20 -69
- package/dist/src/mind/match.js +259 -318
- package/dist/src/mind/mechanisms/alu.js +16 -16
- package/dist/src/mind/mechanisms/cast.d.ts +9 -13
- package/dist/src/mind/mechanisms/cast.js +363 -456
- package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
- package/dist/src/mind/mechanisms/confluence.js +152 -183
- package/dist/src/mind/mechanisms/cover.d.ts +2 -8
- package/dist/src/mind/mechanisms/cover.js +148 -210
- package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
- package/dist/src/mind/mechanisms/extraction.js +234 -288
- package/dist/src/mind/mechanisms/recall.d.ts +6 -10
- package/dist/src/mind/mechanisms/recall.js +126 -185
- package/dist/src/mind/mind.d.ts +129 -154
- package/dist/src/mind/mind.js +258 -288
- package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
- package/dist/src/mind/pipeline-mechanism.js +161 -172
- package/dist/src/mind/pipeline.d.ts +4 -14
- package/dist/src/mind/pipeline.js +125 -189
- package/dist/src/mind/primitives.d.ts +8 -32
- package/dist/src/mind/primitives.js +99 -117
- package/dist/src/mind/rationale.d.ts +86 -98
- package/dist/src/mind/rationale.js +113 -121
- package/dist/src/mind/reasoning.d.ts +2 -13
- package/dist/src/mind/reasoning.js +129 -166
- package/dist/src/mind/recognition.d.ts +1 -4
- package/dist/src/mind/recognition.js +183 -208
- package/dist/src/mind/resonance.d.ts +5 -22
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +6 -25
- package/dist/src/mind/trace.js +50 -58
- package/dist/src/mind/traverse.d.ts +16 -58
- package/dist/src/mind/traverse.js +304 -357
- package/dist/src/mind/types.d.ts +120 -127
- package/dist/src/mind/types.js +60 -69
- package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
- package/dist/src/rabitq-ivf/src/database.js +201 -0
- package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
- package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
- package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
- package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
- package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
- package/dist/src/rabitq-ivf/src/prng.js +36 -0
- package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
- package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
- package/dist/src/sema.d.ts +9 -13
- package/dist/src/sema.js +26 -40
- package/dist/src/store-sqlite.d.ts +169 -166
- package/dist/src/store-sqlite.js +762 -662
- package/dist/src/store.d.ts +586 -630
- package/dist/src/store.js +1423 -1581
- package/dist/src/vec.d.ts +5 -9
- package/dist/src/vec.js +61 -73
- package/example/train_base.ts +13 -10
- package/package.json +1 -1
- package/src/alu/README.md +1 -1
- package/src/alu/src/index.ts +1 -1
- package/src/config.ts +19 -27
- package/src/index.ts +6 -11
- package/src/rabitq-ivf/README.md +56 -0
- package/src/rabitq-ivf/src/database.ts +276 -0
- package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
- package/src/rabitq-ivf/src/ivf.ts +1330 -0
- package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
- package/src/store-sqlite.ts +196 -9
- package/src/store.ts +8 -32
- package/test/08-storage.test.mjs +3 -3
- package/test/14-scaling.test.mjs +2 -2
- package/test/35-ivf.test.mjs +263 -0
- package/test/36-bloom.test.mjs +123 -0
- package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
- package/dist/src/rabitq-hnsw/src/database.js +0 -405
- package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
- package/dist/src/rabitq-hnsw/src/heap.js +0 -94
- package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
- package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
- package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
- package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
- package/dist/src/rabitq-hnsw/src/prng.js +0 -38
- package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
- package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
- package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
- package/dist/src/rabitq-hnsw/src/store.js +0 -916
- package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
- package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
- package/src/rabitq-hnsw/README.md +0 -303
- package/src/rabitq-hnsw/src/database.ts +0 -492
- package/src/rabitq-hnsw/src/heap.ts +0 -90
- package/src/rabitq-hnsw/src/hnsw.ts +0 -514
- package/src/rabitq-hnsw/src/store.ts +0 -994
- package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
- /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
|
@@ -36,73 +36,65 @@ import { lightestDerivation } from "./deduction.js";
|
|
|
36
36
|
* start there. No quadratic span structure.
|
|
37
37
|
*/
|
|
38
38
|
export function coverSequence(length, candidates) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
if (length <= 0)
|
|
40
|
+
return { spans: [], covered: 0, uncovered: 0 };
|
|
41
|
+
// Candidates indexed by where they start, and by their exact (start,end) edge
|
|
42
|
+
// for reconstruction. Both are O(|candidates|) to build and to read.
|
|
43
|
+
const byStart = Array.from({ length }, () => []);
|
|
44
|
+
const byEdge = new Map();
|
|
45
|
+
let maxWeight = 1;
|
|
46
|
+
for (const c of candidates) {
|
|
47
|
+
if (c.end <= c.start || c.start < 0 || c.end > length)
|
|
48
|
+
continue;
|
|
49
|
+
const w = c.weight ?? 1;
|
|
50
|
+
if (w > maxWeight)
|
|
51
|
+
maxWeight = w;
|
|
52
|
+
byStart[c.start].push(c);
|
|
53
|
+
const edge = c.start * (length + 1) + c.end;
|
|
54
|
+
const prev = byEdge.get(edge);
|
|
55
|
+
if (!prev || w < (prev.weight ?? 1))
|
|
56
|
+
byEdge.set(edge, c);
|
|
50
57
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
// One uncovered symbol must outweigh any sum of span weights, so coverage is
|
|
59
|
+
// strictly the primary objective and weight only breaks ties.
|
|
60
|
+
const skipCost = maxWeight * length + 1;
|
|
61
|
+
const system = {
|
|
62
|
+
key: (p) => "" + p,
|
|
63
|
+
axioms: () => [{ item: 0, cost: 0 }],
|
|
64
|
+
isGoal: (p) => p === length,
|
|
65
|
+
// No nonzero admissible bound is available here: a single candidate can
|
|
66
|
+
// cover the whole remainder for unit cost, so any per-symbol estimate would
|
|
67
|
+
// overestimate. With h = 0 this is exact Knuth/Dijkstra over the frontier —
|
|
68
|
+
// optimal, and linear in the positions. (The A* outside bound is for systems
|
|
69
|
+
// whose remaining cost can be genuinely lower-bounded; see the engine.)
|
|
70
|
+
*rules(p) {
|
|
71
|
+
if (p >= length)
|
|
72
|
+
return;
|
|
73
|
+
yield { premises: [p], conclusion: p + 1, cost: skipCost }; // leave uncovered
|
|
74
|
+
for (const c of byStart[p]) {
|
|
75
|
+
yield { premises: [p], conclusion: c.end, cost: c.weight ?? 1 };
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
const best = lightestDerivation(system);
|
|
80
|
+
if (!best)
|
|
81
|
+
return { spans: [], covered: 0, uncovered: length };
|
|
82
|
+
// Walk the frontier chain back to the axiom; an edge p→q is a chosen span iff
|
|
83
|
+
// a candidate spans exactly [p, q) (a covering edge is always cheaper than the
|
|
84
|
+
// skips it replaces, so it appears on the optimal path wherever it is used).
|
|
85
|
+
const spans = [];
|
|
86
|
+
let node = best;
|
|
87
|
+
while (node && node.rule) {
|
|
88
|
+
const to = node.item;
|
|
89
|
+
const from = node.premises[0].item;
|
|
90
|
+
const span = byEdge.get(from * (length + 1) + to);
|
|
91
|
+
if (span)
|
|
92
|
+
spans.push(span);
|
|
93
|
+
node = node.premises[0];
|
|
54
94
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
// One uncovered symbol must outweigh any sum of span weights, so coverage is
|
|
63
|
-
// strictly the primary objective and weight only breaks ties.
|
|
64
|
-
const skipCost = maxWeight * length + 1;
|
|
65
|
-
const system = {
|
|
66
|
-
key: (p) => "" + p,
|
|
67
|
-
axioms: () => [{ item: 0, cost: 0 }],
|
|
68
|
-
isGoal: (p) => p === length,
|
|
69
|
-
// No nonzero admissible bound is available here: a single candidate can
|
|
70
|
-
// cover the whole remainder for unit cost, so any per-symbol estimate would
|
|
71
|
-
// overestimate. With h = 0 this is exact Knuth/Dijkstra over the frontier —
|
|
72
|
-
// optimal, and linear in the positions. (The A* outside bound is for systems
|
|
73
|
-
// whose remaining cost can be genuinely lower-bounded; see the engine.)
|
|
74
|
-
*rules(p) {
|
|
75
|
-
if (p >= length) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
yield { premises: [p], conclusion: p + 1, cost: skipCost }; // leave uncovered
|
|
79
|
-
for (const c of byStart[p]) {
|
|
80
|
-
yield { premises: [p], conclusion: c.end, cost: c.weight ?? 1 };
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
const best = lightestDerivation(system);
|
|
85
|
-
if (!best) {
|
|
86
|
-
return { spans: [], covered: 0, uncovered: length };
|
|
87
|
-
}
|
|
88
|
-
// Walk the frontier chain back to the axiom; an edge p→q is a chosen span iff
|
|
89
|
-
// a candidate spans exactly [p, q) (a covering edge is always cheaper than the
|
|
90
|
-
// skips it replaces, so it appears on the optimal path wherever it is used).
|
|
91
|
-
const spans = [];
|
|
92
|
-
let node = best;
|
|
93
|
-
while (node && node.rule) {
|
|
94
|
-
const to = node.item;
|
|
95
|
-
const from = node.premises[0].item;
|
|
96
|
-
const span = byEdge.get(from * (length + 1) + to);
|
|
97
|
-
if (span) {
|
|
98
|
-
spans.push(span);
|
|
99
|
-
}
|
|
100
|
-
node = node.premises[0];
|
|
101
|
-
}
|
|
102
|
-
spans.reverse();
|
|
103
|
-
let covered = 0;
|
|
104
|
-
for (const s of spans) {
|
|
105
|
-
covered += s.end - s.start;
|
|
106
|
-
}
|
|
107
|
-
return { spans, covered, uncovered: length - covered };
|
|
95
|
+
spans.reverse();
|
|
96
|
+
let covered = 0;
|
|
97
|
+
for (const s of spans)
|
|
98
|
+
covered += s.end - s.start;
|
|
99
|
+
return { spans, covered, uncovered: length - covered };
|
|
108
100
|
}
|
|
@@ -29,62 +29,62 @@
|
|
|
29
29
|
*/
|
|
30
30
|
/** One pattern occurrence found by the trie. */
|
|
31
31
|
export interface Match<P> {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
id: number;
|
|
40
|
-
/** Payload registered with the pattern. */
|
|
41
|
-
payload: P;
|
|
42
|
-
}
|
|
43
|
-
export declare class Trie<P = undefined> {
|
|
44
|
-
private _nxt;
|
|
45
|
-
private _sym;
|
|
46
|
-
private _multi;
|
|
47
|
-
private _end;
|
|
48
|
-
private readonly _lens;
|
|
49
|
-
private readonly _vals;
|
|
50
|
-
private _len;
|
|
51
|
-
constructor();
|
|
52
|
-
/** The root state, for cursor walks. */
|
|
53
|
-
get root(): number;
|
|
54
|
-
/** Number of distinct patterns stored. */
|
|
55
|
-
get size(): number;
|
|
56
|
-
/** Allocate a fresh state, growing the typed arrays by a fixed increment
|
|
57
|
-
* when full. No power-of-2 doubling — the transient double-memory spike
|
|
58
|
-
* during growth is bounded to the increment. */
|
|
59
|
-
private _state;
|
|
60
|
-
/** Follow symbol `c` from state `s`. Returns the next state, or -1. */
|
|
61
|
-
private _follow;
|
|
62
|
-
/** Follow one symbol from `state`; returns the next state or -1 if none. */
|
|
63
|
-
step(state: number, symbol: number): number;
|
|
64
|
-
/** The pattern ending exactly at `state`, or null. */
|
|
65
|
-
terminal(state: number): {
|
|
32
|
+
/** Start offset in the searched sequence (inclusive). */
|
|
33
|
+
start: number;
|
|
34
|
+
/** End offset (exclusive); `end - start === length`. */
|
|
35
|
+
end: number;
|
|
36
|
+
/** Length of the matched pattern. */
|
|
37
|
+
length: number;
|
|
38
|
+
/** Pattern id, assigned in insertion order (0-based). */
|
|
66
39
|
id: number;
|
|
40
|
+
/** Payload registered with the pattern. */
|
|
67
41
|
payload: P;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
42
|
+
}
|
|
43
|
+
export declare class Trie<P = undefined> {
|
|
44
|
+
private _nxt;
|
|
45
|
+
private _sym;
|
|
46
|
+
private _multi;
|
|
47
|
+
private _end;
|
|
48
|
+
private readonly _lens;
|
|
49
|
+
private readonly _vals;
|
|
50
|
+
private _len;
|
|
51
|
+
constructor();
|
|
52
|
+
/** The root state, for cursor walks. */
|
|
53
|
+
get root(): number;
|
|
54
|
+
/** Number of distinct patterns stored. */
|
|
55
|
+
get size(): number;
|
|
56
|
+
/** Allocate a fresh state, growing the typed arrays by a fixed increment
|
|
57
|
+
* when full. No power-of-2 doubling — the transient double-memory spike
|
|
58
|
+
* during growth is bounded to the increment. */
|
|
59
|
+
private _state;
|
|
60
|
+
/** Follow symbol `c` from state `s`. Returns the next state, or -1. */
|
|
61
|
+
private _follow;
|
|
62
|
+
/** Follow one symbol from `state`; returns the next state or -1 if none. */
|
|
63
|
+
step(state: number, symbol: number): number;
|
|
64
|
+
/** The pattern ending exactly at `state`, or null. */
|
|
65
|
+
terminal(state: number): {
|
|
66
|
+
id: number;
|
|
67
|
+
payload: P;
|
|
68
|
+
} | null;
|
|
69
|
+
/** Length of the pattern with this id. */
|
|
70
|
+
lengthOf(id: number): number;
|
|
71
|
+
/**
|
|
72
|
+
* Insert a pattern, returning its id. Inserting the same symbol-sequence
|
|
73
|
+
* twice returns the first id and keeps the first payload (patterns are keyed
|
|
74
|
+
* by content). Empty patterns are ignored and return -1.
|
|
75
|
+
*/
|
|
76
|
+
insert(pattern: ArrayLike<number>, payload: P): number;
|
|
77
|
+
/**
|
|
78
|
+
* Every stored pattern that begins exactly at `pos` in `seq`, shortest first.
|
|
79
|
+
* Walks forward from the root in O(longest match); reports nothing about any
|
|
80
|
+
* other position. This is the on-demand probe the search uses.
|
|
81
|
+
*/
|
|
82
|
+
matchesAt(seq: ArrayLike<number>, pos: number): Array<Match<P>>;
|
|
83
|
+
/**
|
|
84
|
+
* Every occurrence of every pattern anywhere in `seq` (eager form, the union
|
|
85
|
+
* of {@link matchesAt} over all start positions). O(seq · longest pattern),
|
|
86
|
+
* independent of how many patterns are stored. Use {@link matchesAt} when the
|
|
87
|
+
* search only needs the sites at a particular position.
|
|
88
|
+
*/
|
|
89
|
+
scan(seq: ArrayLike<number>): Array<Match<P>>;
|
|
90
90
|
}
|
|
@@ -29,193 +29,189 @@
|
|
|
29
29
|
*/
|
|
30
30
|
const ROOT = 0;
|
|
31
31
|
export class Trie {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
/** The root state, for cursor walks. */
|
|
54
|
-
get root() {
|
|
55
|
-
return ROOT;
|
|
56
|
-
}
|
|
57
|
-
/** Number of distinct patterns stored. */
|
|
58
|
-
get size() {
|
|
59
|
-
return this._lens.length;
|
|
60
|
-
}
|
|
61
|
-
// ── internal helpers ────────────────────────────────────────────────────
|
|
62
|
-
/** Allocate a fresh state, growing the typed arrays by a fixed increment
|
|
63
|
-
* when full. No power-of-2 doubling — the transient double-memory spike
|
|
64
|
-
* during growth is bounded to the increment. */
|
|
65
|
-
_state() {
|
|
66
|
-
const s = this._len++;
|
|
67
|
-
if (s >= this._nxt.length) {
|
|
68
|
-
const c = s + 4096;
|
|
69
|
-
const nn = new Int32Array(c);
|
|
70
|
-
nn.set(this._nxt);
|
|
71
|
-
this._nxt = nn;
|
|
72
|
-
const ns = new Uint8Array(c);
|
|
73
|
-
ns.set(this._sym);
|
|
74
|
-
this._sym = ns;
|
|
75
|
-
const ne = new Int32Array(c);
|
|
76
|
-
ne.set(this._end);
|
|
77
|
-
this._end = ne;
|
|
32
|
+
// ── compact transition storage ──────────────────────────────────────────
|
|
33
|
+
// _nxt[s] — -1 terminal (no outgoing transition)
|
|
34
|
+
// -2 multi-transition → _multi.get(s)
|
|
35
|
+
// >=0 singleton target; the symbol is _sym[s]
|
|
36
|
+
_nxt;
|
|
37
|
+
_sym;
|
|
38
|
+
_multi = null;
|
|
39
|
+
// _end[s] is the pattern id ending exactly at s, or -1.
|
|
40
|
+
_end;
|
|
41
|
+
// Per-pattern data, indexed by pattern id.
|
|
42
|
+
_lens = [];
|
|
43
|
+
_vals = [];
|
|
44
|
+
_len = 1; // next free state (state 0 = root)
|
|
45
|
+
constructor() {
|
|
46
|
+
const c = 256;
|
|
47
|
+
this._nxt = new Int32Array(c);
|
|
48
|
+
this._sym = new Uint8Array(c);
|
|
49
|
+
this._end = new Int32Array(c);
|
|
50
|
+
this._nxt[ROOT] = -1;
|
|
51
|
+
this._end[ROOT] = -1;
|
|
78
52
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
/** Follow symbol `c` from state `s`. Returns the next state, or -1. */
|
|
84
|
-
_follow(s, c) {
|
|
85
|
-
const n = this._nxt[s];
|
|
86
|
-
if (n >= 0) {
|
|
87
|
-
return this._sym[s] === c ? n : -1;
|
|
53
|
+
/** The root state, for cursor walks. */
|
|
54
|
+
get root() {
|
|
55
|
+
return ROOT;
|
|
88
56
|
}
|
|
89
|
-
|
|
90
|
-
|
|
57
|
+
/** Number of distinct patterns stored. */
|
|
58
|
+
get size() {
|
|
59
|
+
return this._lens.length;
|
|
91
60
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Insert a pattern, returning its id. Inserting the same symbol-sequence
|
|
111
|
-
* twice returns the first id and keeps the first payload (patterns are keyed
|
|
112
|
-
* by content). Empty patterns are ignored and return -1.
|
|
113
|
-
*/
|
|
114
|
-
insert(pattern, payload) {
|
|
115
|
-
const n = pattern.length;
|
|
116
|
-
if (n === 0) {
|
|
117
|
-
return -1;
|
|
118
|
-
}
|
|
119
|
-
let s = ROOT;
|
|
120
|
-
for (let i = 0; i < n; i++) {
|
|
121
|
-
const c = pattern[i];
|
|
122
|
-
const t = this._nxt[s];
|
|
123
|
-
if (t === -1) {
|
|
124
|
-
// Terminal — place first transition as a singleton.
|
|
125
|
-
const ns = this._state();
|
|
126
|
-
this._nxt[s] = ns;
|
|
127
|
-
this._sym[s] = c;
|
|
128
|
-
s = ns;
|
|
129
|
-
} else if (t >= 0) {
|
|
130
|
-
// Singleton — either advance on match, or expand to multi.
|
|
131
|
-
if (this._sym[s] === c) {
|
|
132
|
-
s = t;
|
|
133
|
-
} else {
|
|
134
|
-
const map = new Map();
|
|
135
|
-
map.set(this._sym[s], t);
|
|
136
|
-
const ns = this._state();
|
|
137
|
-
map.set(c, ns);
|
|
138
|
-
if (!this._multi) {
|
|
139
|
-
this._multi = new Map();
|
|
140
|
-
}
|
|
141
|
-
this._multi.set(s, map);
|
|
142
|
-
this._nxt[s] = -2;
|
|
143
|
-
s = ns;
|
|
144
|
-
}
|
|
145
|
-
} else {
|
|
146
|
-
// Multi (t === -2) — extend the existing Map.
|
|
147
|
-
const map = this._multi.get(s);
|
|
148
|
-
let ns = map.get(c);
|
|
149
|
-
if (ns === undefined) {
|
|
150
|
-
ns = this._state();
|
|
151
|
-
map.set(c, ns);
|
|
61
|
+
// ── internal helpers ────────────────────────────────────────────────────
|
|
62
|
+
/** Allocate a fresh state, growing the typed arrays by a fixed increment
|
|
63
|
+
* when full. No power-of-2 doubling — the transient double-memory spike
|
|
64
|
+
* during growth is bounded to the increment. */
|
|
65
|
+
_state() {
|
|
66
|
+
const s = this._len++;
|
|
67
|
+
if (s >= this._nxt.length) {
|
|
68
|
+
const c = s + 4096;
|
|
69
|
+
const nn = new Int32Array(c);
|
|
70
|
+
nn.set(this._nxt);
|
|
71
|
+
this._nxt = nn;
|
|
72
|
+
const ns = new Uint8Array(c);
|
|
73
|
+
ns.set(this._sym);
|
|
74
|
+
this._sym = ns;
|
|
75
|
+
const ne = new Int32Array(c);
|
|
76
|
+
ne.set(this._end);
|
|
77
|
+
this._end = ne;
|
|
152
78
|
}
|
|
153
|
-
s =
|
|
154
|
-
|
|
79
|
+
this._nxt[s] = -1;
|
|
80
|
+
this._end[s] = -1;
|
|
81
|
+
return s;
|
|
82
|
+
}
|
|
83
|
+
/** Follow symbol `c` from state `s`. Returns the next state, or -1. */
|
|
84
|
+
_follow(s, c) {
|
|
85
|
+
const n = this._nxt[s];
|
|
86
|
+
if (n >= 0)
|
|
87
|
+
return this._sym[s] === c ? n : -1;
|
|
88
|
+
if (n === -2)
|
|
89
|
+
return this._multi.get(s).get(c) ?? -1;
|
|
90
|
+
return -1; // n === -1
|
|
91
|
+
}
|
|
92
|
+
// ── cursor API (allocation-free) ─────────────────────────────────────────
|
|
93
|
+
/** Follow one symbol from `state`; returns the next state or -1 if none. */
|
|
94
|
+
step(state, symbol) {
|
|
95
|
+
return this._follow(state, symbol);
|
|
155
96
|
}
|
|
156
|
-
|
|
157
|
-
|
|
97
|
+
/** The pattern ending exactly at `state`, or null. */
|
|
98
|
+
terminal(state) {
|
|
99
|
+
const id = this._end[state];
|
|
100
|
+
return id === -1 ? null : { id, payload: this._vals[id] };
|
|
158
101
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
this._end[s] = id;
|
|
163
|
-
return id;
|
|
164
|
-
}
|
|
165
|
-
// ── matching ───────────────────────────────────────────────────────────
|
|
166
|
-
/**
|
|
167
|
-
* Every stored pattern that begins exactly at `pos` in `seq`, shortest first.
|
|
168
|
-
* Walks forward from the root in O(longest match); reports nothing about any
|
|
169
|
-
* other position. This is the on-demand probe the search uses.
|
|
170
|
-
*/
|
|
171
|
-
matchesAt(seq, pos) {
|
|
172
|
-
const out = [];
|
|
173
|
-
let s = ROOT;
|
|
174
|
-
for (let i = pos, n = seq.length; i < n; i++) {
|
|
175
|
-
s = this._follow(s, seq[i]);
|
|
176
|
-
if (s === -1) {
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
const id = this._end[s];
|
|
180
|
-
if (id !== -1) {
|
|
181
|
-
out.push({
|
|
182
|
-
start: pos,
|
|
183
|
-
end: i + 1,
|
|
184
|
-
length: this._lens[id],
|
|
185
|
-
id,
|
|
186
|
-
payload: this._vals[id],
|
|
187
|
-
});
|
|
188
|
-
}
|
|
102
|
+
/** Length of the pattern with this id. */
|
|
103
|
+
lengthOf(id) {
|
|
104
|
+
return this._lens[id];
|
|
189
105
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
106
|
+
// ── build ───────────────────────────────────────────────────────────────
|
|
107
|
+
/**
|
|
108
|
+
* Insert a pattern, returning its id. Inserting the same symbol-sequence
|
|
109
|
+
* twice returns the first id and keeps the first payload (patterns are keyed
|
|
110
|
+
* by content). Empty patterns are ignored and return -1.
|
|
111
|
+
*/
|
|
112
|
+
insert(pattern, payload) {
|
|
113
|
+
const n = pattern.length;
|
|
114
|
+
if (n === 0)
|
|
115
|
+
return -1;
|
|
116
|
+
let s = ROOT;
|
|
117
|
+
for (let i = 0; i < n; i++) {
|
|
118
|
+
const c = pattern[i];
|
|
119
|
+
const t = this._nxt[s];
|
|
120
|
+
if (t === -1) {
|
|
121
|
+
// Terminal — place first transition as a singleton.
|
|
122
|
+
const ns = this._state();
|
|
123
|
+
this._nxt[s] = ns;
|
|
124
|
+
this._sym[s] = c;
|
|
125
|
+
s = ns;
|
|
126
|
+
}
|
|
127
|
+
else if (t >= 0) {
|
|
128
|
+
// Singleton — either advance on match, or expand to multi.
|
|
129
|
+
if (this._sym[s] === c) {
|
|
130
|
+
s = t;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const map = new Map();
|
|
134
|
+
map.set(this._sym[s], t);
|
|
135
|
+
const ns = this._state();
|
|
136
|
+
map.set(c, ns);
|
|
137
|
+
if (!this._multi)
|
|
138
|
+
this._multi = new Map();
|
|
139
|
+
this._multi.set(s, map);
|
|
140
|
+
this._nxt[s] = -2;
|
|
141
|
+
s = ns;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// Multi (t === -2) — extend the existing Map.
|
|
146
|
+
const map = this._multi.get(s);
|
|
147
|
+
let ns = map.get(c);
|
|
148
|
+
if (ns === undefined) {
|
|
149
|
+
ns = this._state();
|
|
150
|
+
map.set(c, ns);
|
|
151
|
+
}
|
|
152
|
+
s = ns;
|
|
153
|
+
}
|
|
206
154
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
155
|
+
if (this._end[s] !== -1)
|
|
156
|
+
return this._end[s]; // already present
|
|
157
|
+
const id = this._lens.length;
|
|
158
|
+
this._lens.push(n);
|
|
159
|
+
this._vals.push(payload);
|
|
160
|
+
this._end[s] = id;
|
|
161
|
+
return id;
|
|
162
|
+
}
|
|
163
|
+
// ── matching ───────────────────────────────────────────────────────────
|
|
164
|
+
/**
|
|
165
|
+
* Every stored pattern that begins exactly at `pos` in `seq`, shortest first.
|
|
166
|
+
* Walks forward from the root in O(longest match); reports nothing about any
|
|
167
|
+
* other position. This is the on-demand probe the search uses.
|
|
168
|
+
*/
|
|
169
|
+
matchesAt(seq, pos) {
|
|
170
|
+
const out = [];
|
|
171
|
+
let s = ROOT;
|
|
172
|
+
for (let i = pos, n = seq.length; i < n; i++) {
|
|
173
|
+
s = this._follow(s, seq[i]);
|
|
174
|
+
if (s === -1)
|
|
175
|
+
break;
|
|
176
|
+
const id = this._end[s];
|
|
177
|
+
if (id !== -1) {
|
|
178
|
+
out.push({
|
|
179
|
+
start: pos,
|
|
180
|
+
end: i + 1,
|
|
181
|
+
length: this._lens[id],
|
|
182
|
+
id,
|
|
183
|
+
payload: this._vals[id],
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Every occurrence of every pattern anywhere in `seq` (eager form, the union
|
|
191
|
+
* of {@link matchesAt} over all start positions). O(seq · longest pattern),
|
|
192
|
+
* independent of how many patterns are stored. Use {@link matchesAt} when the
|
|
193
|
+
* search only needs the sites at a particular position.
|
|
194
|
+
*/
|
|
195
|
+
scan(seq) {
|
|
196
|
+
const out = [];
|
|
197
|
+
for (let pos = 0, n = seq.length; pos < n; pos++) {
|
|
198
|
+
let s = ROOT;
|
|
199
|
+
for (let i = pos; i < n; i++) {
|
|
200
|
+
s = this._follow(s, seq[i]);
|
|
201
|
+
if (s === -1)
|
|
202
|
+
break;
|
|
203
|
+
const id = this._end[s];
|
|
204
|
+
if (id !== -1) {
|
|
205
|
+
out.push({
|
|
206
|
+
start: pos,
|
|
207
|
+
end: i + 1,
|
|
208
|
+
length: this._lens[id],
|
|
209
|
+
id,
|
|
210
|
+
payload: this._vals[id],
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
216
214
|
}
|
|
217
|
-
|
|
215
|
+
return out;
|
|
218
216
|
}
|
|
219
|
-
return out;
|
|
220
|
-
}
|
|
221
217
|
}
|