Bitap Algorithm: Bit-Parallel NFA Fuzzy Search
The Bitap algorithm—also known as the Shift-Or, Shift-And, or Baeza-Yates–Gonnet algorithm—is an approximate string matching technique that simulates a Non-deterministic Finite Automaton (NFA) using bitwise operations. By encoding the states of an NFA as individual bits within standard binary computer words, the algorithm evaluates multiple potential match states simultaneously. This article explains how bit-parallel NFA simulation operates at the binary level to perform fast, efficient fuzzy text searches capable of handling substitutions, insertions, and deletions.
The NFA Model for Approximate Matching
In string searching, an NFA can be designed to recognize a pattern of length \(m\) along with up to \(k\) allowable errors (Levenshtein distance). The automaton contains a grid of states: * Horizontal axis (\(m\)): Represents matching successive characters of the target pattern. * Vertical axis (\(k\)): Represents the number of accumulated edit errors (0 errors, 1 error, …, up to \(k\) errors).
Transitioning across states depends on the next input character (an exact match), while moving diagonally or vertically models an error: * Substitution: Advances the pattern and increases the error count by 1. * Insertion: Consumes a character from the text, remains at the current pattern position, and increments the error count. * Deletion: Advances the pattern position without consuming a text character, incrementing the error count.
The Bit-Parallel Concept
Standard NFA simulations can be computationally expensive because maintaining sets of active states requires tracking many pointers or graph nodes. The Bitap algorithm eliminates this overhead using bit-parallelism.
Instead of tracking states with complex data structures, a state
vector of length \(m\) is packed
directly into an integer (a binary word, such as a 32-bit or 64-bit
integer). Each bit position corresponds to an active or inactive state
in the NFA: * A 0 (or 1, depending on whether
the implementation uses Shift-Or or Shift-And) indicates that the state
is active. * Transitioning all active states simultaneously across the
entire pattern requires only a few native CPU bitwise instructions:
shifts (<<), bitwise OR (|), bitwise AND
(&), and bitwise NOT (~).
How the Algorithm Executes in the Binary Number System
Precomputing Pattern Masks: Before scanning the text, a bitmask table \(B[c]\) is created for every unique character \(c\) in the alphabet. Each bitmask is an \(m\)-bit binary word where the \(i\)-th bit indicates whether character \(c\) matches the \(i\)-th character of the pattern.
Exact Matching State Transitions (0 Errors): For an exact match search (Shift-And variation), a single binary word \(R\) tracks the active states. For each incoming character \(T[j]\) from the text: \[R = ((R \ll 1) \mid 1) \ \& \ B[T[j]]\]
(R << 1) | 1: Propagates active states forward by one position and sets the initial state active.& B[T[j]]: Filters out any states where the current pattern character does not match the incoming text character.- A match is found when the \(m\)-th bit of \(R\) becomes active.
Handling Errors (\(k > 0\)): To support fuzzy search up to \(k\) errors, the algorithm maintains an array of \(k + 1\) binary state vectors: \(R_0, R_1, \dots, R_k\).
- \(R_0\) tracks matches with 0 errors.
- \(R_d\) tracks matches with \(d\) errors.
For each text character \(T[j]\), the states update using bitwise logic that combines exact transitions with all possible edit operations: \[R_d' = \underbrace{((R_d \ll 1) \ \& \ B[T[j]])}_{\text{Exact Match}} \ \mid \ \underbrace{(R_{d-1} \ll 1)}_{\text{Substitution}} \ \mid \ \underbrace{R_{d-1}'}_{\text{Insertion}} \ \mid \ \underbrace{(R_{d-1}' \ll 1)}_{\text{Deletion}}\]
Because these operations occur directly on binary words, all possible branches of the non-deterministic search tree for up to \(k\) errors are evaluated in parallel in constant time \(O(1)\) per character.
Performance and Trade-Offs
- Speed: Processing time is \(O(k \cdot n)\) where \(n\) is the length of the text, running directly on hardware arithmetic logic units (ALUs).
- Word Size Limitation: The standard implementation requires the pattern length \(m\) to be less than or equal to the machine word size (typically 64 bits). For longer patterns, multi-word bit vectors or alternative algorithms (such as Myers’ bit-parallel algorithm) are used.