fast.string

Parallel string, date & phonetic-matching functions for R

Author

Kyle Haynes

Published

8 August 2026

Preface

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.

Why this exists

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:

  • Phonetic blocking keys (Soundex, NYSIIS, Refined Soundex, Cologne, Double Metaphone, Caverphone) — used to group probably-same names before running an expensive pairwise comparison within each group, rather than comparing every record against every other record.
  • Fuzzy string similarity (Jaro-Winkler, edit distance, q-gram overlap) — base R has no notion of “how similar are these two strings,” only exact (==) or pattern (grepl()) matching.
  • Multi-token name comparison"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.
  • Per-element match countinggrepl() 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.

How it fits with record-linkage packages

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.

How this book is organised

  • Getting started — what 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).
  • Under the hood — the algorithms (3  Algorithms under the hood): how regex and fixed-literal matching differ, how the bit-parallel Jaro-Winkler implementation works, how the phonetic schemes trade block size against recall, and how multi-token name comparison resolves reordered tokens — followed by a full worked record-linkage pipeline (4  A worked record-linkage pipeline) that takes a messy incoming batch through profiling, parsing, deduplication, multi-key blocking, scoring, and a defensible accept/review decision.
  • Reference — an exhaustive per-function reference with every argument and runnable examples (5  Function reference); the heavy benchmark chapter at 3 million strings, with base, stringi, stringdist, and RecordLinkage as comparison baselines (6  Benchmarks at scale); and a troubleshooting/FAQ chapter (7  Troubleshooting & FAQ).

A quick taste

n <- 2e5
x <- sprintf("%s-%04d", sample(c("John Smith", "Mary O'Brien", "Kyle Haynes"), n, TRUE), sample(9999, n, TRUE))

system.time(base::grepl("Smith", x))
   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.