AhoCorasick
AhoCorasick module: multi-pattern string search (pure March implementation).
Finds every occurrence of ANY of a set of fixed string patterns inside a haystack in a single pass, in O(haystack_len + total_pattern_len) time — independent of how many patterns there are. The naive alternative, N calls to String.index_of-style scanning (one per pattern), costs O(N * haystack_len). This is the classic Aho-Corasick automaton: a trie over the patterns, BFS-computed failure links (the "what state do I fall back to on a mismatch" edges), and output sets merged along the failure chain so a match of a shorter suffix pattern is reported even when the trie path actually walked belongs to a longer pattern (e.g. patterns "he" and "she" both match inside "ushers" — "she" is the trie path walked, but "he" is a suffix of it and must also be reported).
Two-phase use, matching the "compile once, scan many times" shape that makes the automaton worth building in the first place:
let ac = AhoCorasick.build(["he", "she", "his", "hers"])
AhoCorasick.find_all(ac, "ushers")
-- [{start: 1, stop: 4, pattern_index: 1, pattern: "she"},
-- {start: 2, stop: 4, pattern_index: 0, pattern: "he"}]Matches are returned in the order their END position is reached scanning left to right; matches sharing an end position are ordered longest-pattern- first (the pattern whose trie path was actually walked, then its suffixes via the failure chain, shortest last).
Byte-oriented throughout (like Regex and Json): string_byte_at / string_byte_length, no per-byte allocation. Since string_chars and friends operate on raw bytes rather than decoded Unicode codepoints, this is safe for UTF-8 input too — multibyte continuation bytes are always >= 0x80 and match only themselves.
An empty pattern (zero bytes) is skipped during build: it would match at every position with zero width, which is not useful for a "find these literal substrings" search and would need special-casing throughout. It is simply excluded from the automaton — no error, no match ever produced for it.
Types
Functions
Build an automaton matching any of patterns in a single linear pass over a haystack. Building costs O(total pattern length); scanning with the result costs O(haystack length), independent of the number of patterns — build once, scan as many haystacks as you like with find_all / matches / find_first.
Patterns are referenced by their position in `patterns` (`pattern_index`
on a `Match`). An empty pattern string is skipped (see module doc).
let ac = AhoCorasick.build(["he", "she", "his", "hers"])
AhoCorasick.matches(ac, "ushers") -- trueFind every match of any pattern in haystack, in one linear pass. Matches can overlap, and a shorter pattern that is a suffix of a longer matched pattern is reported separately (see module doc's "he"/"she" example).
let ac = AhoCorasick.build(["he", "she"])
AhoCorasick.find_all(ac, "ushers")
-- [{start: 1, stop: 4, pattern_index: 1, pattern: "she"},
-- {start: 2, stop: 4, pattern_index: 0, pattern: "he"}]Find the first match (by end position, longest pattern first) of any pattern in haystack, without building the full match list.
let ac = AhoCorasick.build(["he", "she"])
AhoCorasick.find_first(ac, "ushers") -- Some({start: 1, stop: 4, ...})
AhoCorasick.find_first(ac, "xyz") -- NoneWhether any pattern matches anywhere in haystack.
let ac = AhoCorasick.build(["needle"])
AhoCorasick.matches(ac, "a needle in a haystack") -- true
AhoCorasick.matches(ac, "nothing here") -- falseThe pattern text at index (its position in the list passed to build), or None if out of range.
let ac = AhoCorasick.build(["he", "she"])
AhoCorasick.pattern_at(ac, 1) -- Some("she")
AhoCorasick.pattern_at(ac, 99) -- None