FuzzyRegex 1.2.0
dotnet add package FuzzyRegex --version 1.2.0
NuGet\Install-Package FuzzyRegex -Version 1.2.0
<PackageReference Include="FuzzyRegex" Version="1.2.0" />
<PackageVersion Include="FuzzyRegex" Version="1.2.0" />
<PackageReference Include="FuzzyRegex" />
paket add FuzzyRegex --version 1.2.0
#r "nuget: FuzzyRegex, 1.2.0"
#:package FuzzyRegex@1.2.0
#addin nuget:?package=FuzzyRegex&version=1.2.0
#tool nuget:?package=FuzzyRegex&version=1.2.0
FuzzyRegex
A .NET 10+ port of mrab-regex (the Python regex
module): a full-featured regex engine whose headline capability is fuzzy (approximate) matching
with per-error-type budgets - insertions, deletions and substitutions inside patterns, e.g.
(?:foobar){e<=2} - which no existing .NET library provides. The public namespace is
Fuzzy.Text.RegularExpressions, and the main type is FuzzyRegex, shaped after
System.Text.RegularExpressions.Regex.
Status: 1.2.0, released on nuget.org (release
notes). Feature-complete against the
pinned upstream release - the whole ported upstream test suite passes (docs/STATUS.md) - and the
public API follows semantic versioning from 1.0.0. Where this port deliberately answers differently
from upstream is listed in docs/DIVERGENCES.md,
and open defects in docs/KNOWN-DEFECTS.md.
Try it in your browser: the FuzzyRegex demo. This is
v1 of the page: pattern, flags and subject, the matches highlighted, every group and capture in a
table, and eight worked examples. The engine runs in a Web Worker in your own browser and nothing is
sent anywhere. Its source, and how to run it locally, is in
demo/.
See docs/superpowers/specs/2026-08-29-fuzzy-regex-port-design.md
for the design and docs/plan/OPERATIONS.md for how the port is run.
Install
dotnet add package FuzzyRegex
Quick start
Every example below compiles against the public API in
src/FuzzyRegex/PublicAPI.Shipped.txt. Add
using Fuzzy.Text.RegularExpressions; before running any of them.
Exact match with named groups
FuzzyRegex.Match is the static convenience for a search, and Match.Groups is a dictionary
as well as a list: it is keyed by every group's name, or by its number as text where it has
none, in ascending group number.
using Fuzzy.Text.RegularExpressions;
Match match = FuzzyRegex.Match("2026-09-16", @"(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})");
foreach (string key in match.Groups.Keys)
{
Console.WriteLine($"{key}: {match.Groups[key].Value}");
}
// 0: 2026-09-16
// year: 2026
// month: 09
// day: 16
The static conveniences keep the fifteen most recently used patterns compiled, the bound
Regex.CacheSize uses and FuzzyRegex.CacheSize changes. Hold a new FuzzyRegex(pattern) instead
when one pattern runs over and over, or when more than fifteen are in rotation and each call evicts
the pattern the next one wants: a constructor never reads the cache, so the pattern is parsed once
and stays compiled for as long as the object is held.
Fuzzy match with an error budget
(?:pattern){e<=2} allows up to two errors - substitutions, insertions or deletions - inside
the group. Match.FuzzyCounts reports how many of each kind the match actually used, and is
(0, 0, 0) for an exact match.
using Fuzzy.Text.RegularExpressions;
var regex = new FuzzyRegex(@"(?:foo){e<=2}");
Match match = regex.MatchAtStart("fou");
(int substitutions, int insertions, int deletions) = match.FuzzyCounts;
Console.WriteLine(match.Value);
Console.WriteLine($"{substitutions} substitution(s), {insertions} insertion(s), {deletions} deletion(s)");
// fou
// 1 substitution(s), 0 insertion(s), 0 deletion(s)
Enumerating matches with a timeout
EnumerateMatches finds matches lazily, one at a time, so an early exit does not pay for the
whole subject. Matches is lazy in the same way, and keeps what it has found. The timeout of
either bounds each step rather than the whole walk, so it surfaces from the foreach (or, for a
MatchCollection, from Count and the indexer too); see the divergence "A per-call
timeout on every input-dependent method" in docs/DIVERGENCES.md.
using Fuzzy.Text.RegularExpressions;
var regex = new FuzzyRegex(@"\w+");
foreach (Match match in regex.EnumerateMatches("one two three", timeout: TimeSpan.FromSeconds(1)))
{
Console.WriteLine(match.Value);
}
// one
// two
// three
Given a ReadOnlySpan<char>, EnumerateMatches returns a ValueMatchEnumerator instead, the
shape Regex.EnumerateMatches has. Each step yields a ValueMatch holding only Index and
Length, and the walk builds no Match objects. It cannot be stored in a field of a class, used with LINQ
or held across an await; see docs/GUIDE.md
for when to choose it.
Coming from Python regex
Python regex |
FuzzyRegex |
|---|---|
regex.search(pattern, subject) |
FuzzyRegex.Match(subject, pattern) |
regex.match(pattern, subject) |
FuzzyRegex.MatchAtStart(subject, pattern) |
regex.fullmatch(pattern, subject) |
FuzzyRegex.FullMatch(subject, pattern) |
regex.findall(pattern, subject) |
No direct equivalent. Read Match.Groups[1].Value over FuzzyRegex.Matches(subject, pattern), or over EnumerateMatches for a lazy projection. |
regex.sub(pattern, replacement, subject) |
FuzzyRegex.Replace(subject, pattern, replacement), with the replacement template in upstream's syntax (\1, not $1) |
Coming from System.Text.RegularExpressions
Matchsearches the whole subject. Upstream's anchoredmatchisMatchAtStart, andfullmatchisFullMatch. See "Matchmeans upstream'ssearch; upstream's anchoredmatchisMatchAtStart" indocs/DIVERGENCES.md.- Replacement templates use
\1where .NET uses$1. See "Replacement templates speak upstream's language" indocs/DIVERGENCES.md. Splitreturnsstring?[]with anullentry where a capturing group did not take part, rather than omitting the entry as the built-inRegex.Splitdoes. See "Splitreturnsstring?[]and putsnullwhere a capturing group did not take part" indocs/DIVERGENCES.md.
These are the three that catch a caller by surprise. See
docs/COMPARISON.md for the
complete Python regex and System.Text.RegularExpressions mapping and every other difference,
worked examples included.
One deliberate difference from mrab-regex's defaults
Patterns compile as mrab-regex's version 1 by default, where mrab-regex itself defaults to
version 0. Upstream's front end picks version 0 so that regex stays a drop-in replacement for
Python's re; this library has no re users to protect, and the two behaviours version 1 adds are
the reason to use it over System.Text.RegularExpressions:
- nested sets and set operations -
[[a-z]--[aeiou]]is "a to z except the vowels", whereSystem.Text.RegularExpressionsoffers subtraction alone ([a-z-[aeiou]]); - full Unicode case-folding under
IgnoreCase, soßmatchesSSandfimatchesfi.System.Text.RegularExpressionsapplies simple case folding and matches neither.
Pass FuzzyRegexOptions.Version0, or write (?V0) at the start of the pattern, to get the re
and Regex reading back. The only pattern that changes meaning is an unescaped [ inside a set:
[[] is a set containing [ under version 0 and an unterminated nested set under version 1, and
the parse error says so. Every other deliberate difference from upstream is listed in
docs/DIVERGENCES.md.
Thread safety
A compiled FuzzyRegex is immutable and thread safe. Compile it on any thread, share it
between threads, and call its matching methods from as many at once as you like; nothing it does
alters global state. A Match may be read from any thread too, and from several at once - it
holds a copy of everything it reports.
The one exception is the one .NET has everywhere: an enumerator belongs to the thread that made
it. A MatchCollection can be read from several threads, but each needs its own foreach. The
collection finds its matches as they are read, and two threads asking for the same new match find
it once between them.
This is a slightly stronger promise than System.Text.RegularExpressions.Regex, which is itself
thread safe but documents its Match and MatchCollection results as single-thread objects,
because they may compute parts of their answer lazily. Compiling a pattern is the expensive step
here as it is there, so share one instance rather than constructing per call.
Upstream's concurrent=True argument has no equivalent and needs none: it asks mrab-regex's C to
release CPython's global interpreter lock during a match, and this port never takes a global lock.
Where the docs are
docs/GUIDE.md- the guide for a reader who knowsSystem.Text.RegularExpressionsand has never used Pythonregex: every flag, every entry point, and how to read a result.docs/COMPARISON.md- the complete PythonregexandSystem.Text.RegularExpressionsmapping, with a worked example for every difference.docs/DIVERGENCES.md- the running ledger of every deliberate difference from upstream: what, why, and how to get upstream's behaviour back.docs/superpowers/specs/2026-08-29-fuzzy-regex-port-design.md- the design spec.docs/plan/OPERATIONS.md- how the port itself is run, for contributors.
Licensing
Apache-2.0 (this port), derived from mrab-regex, which is Apache-2.0 AND CNRI-Python
(portions derived from CPython's re module). See LICENSE and NOTICE (added with the first code)
for full attribution. The upstream source is vendored read-only as the upstream/ git submodule,
pinned to the exact commit this port tracks.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.