{
  "description": "Browse aymeric's, an independent, searchable archive of readable snapshots from followed feeds, preserved in Git with aggr.",
  "feed_url": "https://aggr.aymericbeaumet.com/sources/compiler.club/feed.json",
  "home_page_url": "https://aggr.aymericbeaumet.com/sources/compiler.club/",
  "items": [
    {
      "authors": [
        {
          "name": "compiler.club via abhin4v"
        }
      ],
      "content_html": "<h3>Introduction</h3>\n<p>This post describes the construction of an Aho-Corasick automaton for the simultaneous matching of substrings within a sequence. I’m fond of this algorithm because it constructs an automaton from an existing tree data structure in a rather pleasant way.</p>\n<h3>Tries</h3>\n<p>A trie (or “prefix tree”) is an <span class=\"math\">n</span>-ary tree that stores a set of strings. Each edge in the trie is labelled with a character and each node conceptually represents the concatenation of all the edge characters required to reach it (starting from the root).</p>\n<p>Tries are designed to reduce redundancy by ensuring entries with common prefixes share these prefixes within the tree data structure.</p>\n<p>See the trie below that stores the strings <code class=\"math\">{\\lbrace \\text{suit}, \\text{suited}, \\text{suitable} \\rbrace}</code>:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://compiler.club/img/aho-corasick/trie.svg\" alt=\"example trie\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>You can see that the common prefix of <code>suit</code> is shared by all entries. Also note that nodes representing complete entries in the trie are explicitly marked.</p>\n<h3>Suffix Links</h3>\n<p>Aho-Corasick automatons recover from transition failure by following so-called suffix links. These links preserve the longest suffix of the string represented by each node that happens to exist as a prefix of a pattern in the trie. This allows the machine to transition to a state that permits further matching of patterns that happen to have the failing node’s longest suffix as a prefix.</p>\n<p>Consider the suffix links applied (in red) to the trie constructed for the strings <code class=\"math\">\\lbrace \\text{item}, \\text{suits} \\rbrace</code> below:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://compiler.club/img/aho-corasick/first.svg\" alt=\"example trie with suffix links\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>The majority of nodes have the root node as their suffix link. However, if we look at node <span class=\"math\">8</span>, representing the state reached by following suit, we see that its suffix link (node <span class=\"math\">3</span>) points to the node one would reach if, starting from the root, we had followed its suffix, it. This permits the potential for matching the patterns prefixed with it; in this case, only <code class=\"math\">\\lbrace \\text{item} \\rbrace</code>.</p>\n<p>For example, if we were scanning the input <code>suitems</code>, we would reach node <span class=\"math\">8</span>, see that there is no outgoing edge labelled <code>e</code>, we would transition via the suffix link and continue scanning from the failing character, eventually matching <code>su[item]s</code>.</p>\n<p>The construction of suffix links is rather pleasant, they’re computed in a breadth-first traversal of the trie. The cases for each node are computed as follows:</p>\n<ul>\n<li>\n<p>The root and its children have root as their suffix link.</p>\n</li>\n<li>\n<p>To compute the suffix link for each other node, you start by looking at the node’s parent’s suffix link. For a pattern of characters <span class=\"math\">( c₁, c₂, c₃, …, cₙ)</span>, to compute a suffix link for a node <span class=\"math\">cₖ</span> (<span class=\"math\">k ≥ 3</span>), you examine the suffix link of <span class=\"math\">cₖ₋₁</span>. That suffix link preserves the longest suffix of <span class=\"math\">(c₁, …, cₖ₋₁)</span> that represents a prefix of a pattern in the trie. If the node at that suffix link has an outgoing edge labelled <span class=\"math\">cₖ</span>, then the target of that edge is <span class=\"math\">cₖ</span>’s suffix link. Otherwise, you continue to chase up the trie by following successive suffix links. If you reach the root, you stop (to avoid iterating indefinitely by following its suffix link to itself).</p>\n</li>\n</ul>\n<p>Despite my best efforts to describe the suffix link construction process above formally, it’s best explained visually. Consider the trie constructed for the strings <code class=\"math\">\\lbrace \\text{cadence}, \\text{facade} \\rbrace</code> with partially constructed suffix links below:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://compiler.club/img/aho-corasick/two.svg\" alt=\"incremental example of suffix links\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>The nodes are numbered with their breadth-first traversal order.</p>\n<p>After processing the nodes with suffix links computed above, the BFS queue will contain <span class=\"math\">[(c, 5), (d, 6)]</span>. If we examine <span class=\"math\">(c, 5)</span>, we look at its parent’s suffix link. In this case, it’s the root of the trie. We see that root has an outgoing edge labelled with <span class=\"math\">c</span>, so the node reached by that edge is the suffix link for node <span class=\"math\">5</span>:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://compiler.club/img/aho-corasick/three.svg\" alt=\"updated incremental example of suffix links\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>After the above, the queue will contain <span class=\"math\">[(d, 6), (a, 7)]</span>. The processing of <span class=\"math\">(d, 6)</span> is straightforward. As before, its parent’s suffix link is the root. However, there is no outgoing edge labelled <span class=\"math\">d</span>, therefore node <span class=\"math\">6</span>’s suffix link is simply the root (capturing the idea that there’s no other pattern in the tree that has any of <code class=\"math\">\\lbrace \\text{c}, \\text{ca}, \\text{cad} \\rbrace</code> as a prefix). In operational terms, there’s no suffix to preserve as another pattern’s prefix if we get to node <span class=\"math\">6</span> and the input character is not <span class=\"math\">e</span>. We dispose of the seen <span class=\"math\">cad</span> and try the failing input character from the root. If a character fails to advance from the root, we stay at the root but advance the character stream (as it’s a non-starter for every pattern).</p>\n<p>The next interesting case is that of processing the <span class=\"math\">(a, 7)</span> edge. Node <span class=\"math\">7</span>’s parent’s suffix link is the previously computed, non-root, node <span class=\"math\">2</span> - which does have an outgoing edge labelled <span class=\"math\">a</span>, therefore the suffix link of node <span class=\"math\">7</span> is node <span class=\"math\">4</span>. This preserves the <span class=\"math\">ca</span> suffix of <span class=\"math\">faca</span> as being a valid prefix of the other pattern, <span class=\"math\">cadence</span>.</p>\n<p>When all nodes have been processed, the suffix links are as follows:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://compiler.club/img/aho-corasick/four.svg\" alt=\"final example of suffix links\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>For clarity, I’ve omitted the suffix links that go to the root node. Interestingly, you can see that node <span class=\"math\">12</span> goes to node <span class=\"math\">2</span>, attempting to preserve prefix context for matching <span class=\"math\">cadence</span>, from a node reached by assuming it was making progress in matching <span class=\"math\">cadence</span>!</p>\n<h3>Output Links</h3>\n<p>When a pattern is introduced into the trie, the last node traversed during insertion is annotated as being an “output” node (usually storing the inserted pattern). If a node has an output pattern associated with it, this identifies a match that should be output when entering the state represented by that node. However, it may be the case that a node with an output’s pattern has a suffix that also happens to be a pattern in the trie - and, so, must also be output at the same time.</p>\n<p>In order to capture this information, Aho-Corasick employs output links. As with suffix links, it will always be the case that output links point to nodes representing shorter strings (visited first in the breadth-first algorithm).</p>\n<p>Consider the trie below (with suffix links in red and output links in blue), constructed from the strings <code class=\"math\">\\lbrace \\text{spin}, \\text{pin}, \\text{in} \\rbrace</code>.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://compiler.club/img/aho-corasick/five.svg\" alt=\"example of suffix links and output links\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>The blue links go between node with an associated output. It’s clear that on reaching state <span class=\"math\">9</span> (representing that <code>spin</code> has been matched), the outputs for <span class=\"math\">8</span> and <span class=\"math\">6</span> must also be output (representing pattern suffixes <code>pin</code> and <code>in</code>, respectively). You can think of output links as being a linked list of nodes that must be iteratively output if one was interpreting this matcher directly.</p>\n<p>The output link for a node (if it has one) can be computed directly after its suffix link has been computed. This makes sense because the suffix link attempts to capture the longest suffix of the current node’s pattern that happens to be a prefix of a pattern in the trie. So, the output link for a node is its suffix node if its suffix node has an associated output (is a pattern itself), otherwise a node’s output is its suffix node’s output link.</p>\n<h3>Algorithm Pseudocode</h3>\n<p>The pseudocode for computing suffix and output links is as follows:</p>\n<p>let Q be a queue of (char, node)</p>\n<pre><code class=\"language-plain\"><span class=\"code-snippet\"># root and its immediate children have root as their suffix link\nroot.suffix &lt;- root\nfor each (char, child) in root.arrows {\n  child.suffix &lt;- root\n\n  # queue root&#39;s grandchildren for traversal\n  add (char, child) to Q\n}\n\nwhile Q is not empty {\n  let (char, node) = Q.poll()\n  \n  # start from parent&#39;s suffix link node\n  let suffix = node.parent.suffix\n\n  while suffix has no edge labelled char {\n    # follow its suffix link\n    suffix &lt;- suffix.suffix\n\n    # avoid looping endlessly if we reach root\n    if suffix == root then\n      break\n  }\n\n  # capture case where root has outgoing edge labelled char\n  if suffix has edge (char, actual) then\n    node.suffix &lt;- actual\n  else\n    node.suffix &lt;- suffix \n\t\n  # a node&#39;s output is its suffix if its suffix link is an output, \n  # otherwise follow its suffix&#39;s output\n  if (node.suffix.pattern != null) then\n    node.output = node.suffix\n  else\n    node.output = node.suffix.output\n}\n</span></code></pre>\n<h3>The Automaton</h3>\n<p>The computation of suffix and output links is the core of Aho-Corasick, but only an intermediary step as far as computing the automaton is concerned. Of course, the trie - augmented with these internal links - can be interpreted directly. However, due to the potential to repetitively chase up suffix links to resolve the next state to transition to on a given symbol, the amount of work for each transition is not constant.</p>\n<p>Once the suffix and output links are in place, the transitions and outputs are all statically resolvable into a deterministic finite automaton. To compute this, a final breadth first traversal is performed.</p>\n<p>First, the root node (the base case) is processed. For every symbol, <span class=\"math\">a</span>, if the root node has an edge reaching some state, <span class=\"math\">s</span>, labelled <span class=\"math\">a</span>, then that’s where the root transitions on <span class=\"math\">a</span>. If no such edge for <span class=\"math\">a</span> exists, then you stay in the same place (self-looping on the root - effectively skipping over the symbol as it’s a non-starter for every pattern in the trie). All states reachable from the root are added to the traversal queue during this processing.</p>\n<p>For every other node processed in breadth-first fashion: for every symbol, <span class=\"math\">a</span>, you transition to the state reachable on an edge labelled <span class=\"math\">a</span>. If no such edge exists, then you transition to the state reached if you transitioned from the node’s suffix node. This encodes the idea that if no progress can be made on a certain path through the trie, then the state is transitioned into one that hopefully preserves the longest suffix of the current state that is also a prefix of a pattern in the trie. Often times, many transitions simply go to the root (preserving <span class=\"math\">ϵ</span>), so a sparse matrix storage representation is recommendable for many offline Aho-Corasick automatons.</p>\n<p>For example, the trie with suffix and output links constructed from the strings <code class=\"math\">\\lbrace \\text{he}, \\text{she}, \\text{her} \\rbrace</code> would be as follows:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://compiler.club/img/aho-corasick/before.svg\" alt=\"automaton before determinisation\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>The DFA computed from the above trie (by resolving transitions and merging outputs into sets) would be:</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://compiler.club/img/aho-corasick/after.svg\" alt=\"DFA computed from trie augmented with suffix and output links\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<h3>Demo</h3>\n<p>Below you can build an Aho-Corasick trie from a set of strings:</p>\n<h3>Further Reading</h3>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://cr.yp.to/bib/1975/aho.pdf\">Efficient String Matching: An Aid to Bibliographic Search</a> - original paper describing entire algorithm.</li>\n</ul>\n",
      "date_modified": "2026-09-29T14:49:08+00:00",
      "date_published": "2026-09-29T14:49:08+00:00",
      "external_url": "https://compiler.club/aho-corasick/",
      "id": "urn:aggr:item:240726b4931a28f5dd6b68f44678e261d160fb40",
      "summary": "Comments",
      "tags": [
        "news",
        "compsci"
      ],
      "title": "Aho-Corasick Algorithm",
      "url": "https://aggr.aymericbeaumet.com/items/lobste-rs/2026-09-29-aho-corasick-algorithm/"
    }
  ],
  "language": "en",
  "title": "compiler.club",
  "version": "https://jsonfeed.org/version/1.1"
}