FuzzyRegex 1.2.0

dotnet add package FuzzyRegex --version 1.2.0
                    
NuGet\Install-Package FuzzyRegex -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FuzzyRegex" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FuzzyRegex" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="FuzzyRegex" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FuzzyRegex --version 1.2.0
                    
#r "nuget: FuzzyRegex, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FuzzyRegex@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FuzzyRegex&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=FuzzyRegex&version=1.2.0
                    
Install as a Cake Tool

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

  • Match searches the whole subject. Upstream's anchored match is MatchAtStart, and fullmatch is FullMatch. See "Match means upstream's search; upstream's anchored match is MatchAtStart" in docs/DIVERGENCES.md.
  • Replacement templates use \1 where .NET uses $1. See "Replacement templates speak upstream's language" in docs/DIVERGENCES.md.
  • Split returns string?[] with a null entry where a capturing group did not take part, rather than omitting the entry as the built-in Regex.Split does. See "Split returns string?[] and puts null where a capturing group did not take part" in docs/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", where System.Text.RegularExpressions offers subtraction alone ([a-z-[aeiou]]);
  • full Unicode case-folding under IgnoreCase, so ß matches SS and fi matches fi. System.Text.RegularExpressions applies 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

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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.

Version Downloads Last Updated
1.2.0 0 11/5/2026
1.1.0 132 10/26/2026
1.0.0 145 10/20/2026