user system elapsed
0.07 0.00 0.06
system.time(fast.string::fgrepl("Smith", x)) user system elapsed
0.07 0.06 0.00
round(fast.string::jaro_winkler_tokens("Kyle John Haynes", "John Kylie Haynes"), 3)[1] 0.984
Parallel string, date & phonetic-matching functions for R
fast.string is an R package of drop-in-faster equivalents for the string, date, and phonetic-matching functions that show up constantly in record linkage and ETL work — grepl(), grep(), sub(), gsub(), trimws(), substr(), nchar(), chartr(), plus a handful of functions base R simply doesn’t have at all: Soundex/NYSIIS phonetic codes, and vectorised Jaro-Winkler similarity (including a token-reorder/punctuation-aware variant for multi-word names).
Large workloads are parallelised via Intel TBB (through RcppParallel), with PCRE2 doing the regex work and a prepared byte-oriented searcher handling fixed/literal matching. Performance depends on string length, match density, value reuse, matrix shape, and available cores. Some operations (parallel Jaro-Winkler, multi-pattern substitution in one pass) have no base R equivalent at all.
Record linkage and data-cleaning pipelines spend a surprising fraction of their wall-clock time in string operations that look trivial in isolation — trimws() on a column, a gsub() to strip punctuation, grepl() to flag a pattern — but get called over and over on multi-million-row name/address files. Individually each call is fast; in aggregate, on a 5M-row file, they add up to minutes of CPU time spent doing work a single core could be doing across eight or sixteen instead.
Four things are conspicuously not one function each in base R, despite being everywhere in this kind of pipeline:
==) or pattern (grepl()) matching."Kyle John Haynes" and "John Kylie Haynes" are obviously the same person with two tokens swapped, but a plain character-by-character comparison scores that badly.grepl() answers “is there a match”; counting how many means gregexpr() plus a vapply() that allocates a position vector per row only to take its length.fast.string exists to make all of the above fast enough to run unblinkingly on real-sized data, and to package the boring-but-essential 80% (trimming, substitution, date parsing) so a project doesn’t need five different dependencies for five different speed problems.
fast.string is a toolbox of vectorised string primitives. The blocking-strategy selection, EM-based match probability estimation, and persistence of a linked dataset are handled well already by packages like RecordLinkage or reclin2 — pair fast.string with one of those for the per-string comparison work they call underneath. 6 Benchmarks at scale compares fast.string’s comparison primitives directly against the equivalent functions in RecordLinkage and stringdist at scale.
fast.string is for and how it’s positioned against base R, stringi/stringr, stringdist, and RecordLinkage (1 About fast.string), then a hands-on tour of every function group (2 Quickstart).base, stringi, stringdist, and RecordLinkage as comparison baselines (6 Benchmarks at scale); and a troubleshooting/FAQ chapter (7 Troubleshooting & FAQ). user system elapsed
0.07 0.00 0.06
system.time(fast.string::fgrepl("Smith", x)) user system elapsed
0.07 0.06 0.00
round(fast.string::jaro_winkler_tokens("Kyle John Haynes", "John Kylie Haynes"), 3)[1] 0.984
That last call is the headline example for the multi-token comparison function: two names with reordered tokens and one typo still score above 0.95. The full story — including why it also handles "O'Brien" vs. "O Brien" vs. "OBrien" — is in 3 Algorithms under the hood and 5 Function reference.