Aho-Corasick

Aho-Corasick is an algorithm for matching many patterns at once.

It inserts all patterns into a trie and adds failure links.
Then a text can be scanned once to find occurrences of all registered patterns.

Use cases

Typical use cases include:

Idea

First, insert all patterns into a trie.
A trie node represents the current longest pattern prefix matched by the text suffix.

When there is no trie edge for the next character, we move to the longest suffix that is also a pattern prefix.
This fallback pointer is called a failure link.

With failure links, each text character advances the automaton state once.
If the state has pattern outputs, those patterns occur ending at the current position.

Import

import atcoder/extra/string/aho_corasick

Types

type AhoCorasick
type AhoCorasickNode
type AhoCorasickMatch

AhoCorasickMatch has these fields:

l: int
r: int
patternId: int

The match interval is half-open [l, r).

Constructors

proc initAhoCorasick(): AhoCorasick
proc initAhoCorasick(patterns: openArray[string]): AhoCorasick

You can either build an empty automaton and add patterns, or build directly from a pattern list.

Add patterns and build

proc addPattern(ac: var AhoCorasick, pattern: string): int
proc build(ac: var AhoCorasick)

Operations

proc len(ac: AhoCorasick): int
proc patternCount(ac: AhoCorasick): int

proc nextState(ac: AhoCorasick, state: int, ch: char): int
proc transition(ac: AhoCorasick, state: int, ch: char): int

proc findAll(ac: AhoCorasick, text: string): seq[AhoCorasickMatch]
proc countMatches(ac: AhoCorasick, text: string): int
proc matchCount(ac: AhoCorasick, text: string): int
proc contains(ac: AhoCorasick, text: string): bool

Example

var ac = initAhoCorasick()let  he = ac.addPattern("he")  she = ac.addPattern("she")  hers = ac.addPattern("hers")ac.build()let matches = ac.findAll("ushers")for m in matches:  echo "pattern ", m.patternId, " appears at [", m.l, ", ", m.r, ")"doAssert ac.countMatches("ushers") == 3doAssert ac.contains("ushers")

You can also build directly from a list.

let ac = initAhoCorasick(@["a", "aa", "aaa"])doAssert ac.countMatches("aaaa") == 9

Nim-friendly usage

Since contains is defined, matching can be written with in.

if text in ac:
  echo "matched"

transition is useful when using the automaton as a DP state.

var state = 0

for ch in text:
  state = ac.transition(state, ch)

Complexity

Let M be the total pattern length, N be the text length, and Z be the number of matches.

Notes