← All libraries

CandyFuzzy

CandyFuzzy

Fuzzy string matching with scored matched indices

foundation-package fuzzy filter

Fuzzy string matching that returns which characters matched and their score. Two algorithms: Smith-Waterman (local alignment) and Sahilm (gum-style scoring with separator/camel-case bonuses). The matched indices enable filter-highlighting UIs that were impossible with score-only matchers. Extracted from candy-forms; back-compat shims left in candy-forms and candy-lister for migration.

Install

composer require sugarcraft/candy-fuzzy:@dev

Quickstart

use SugarCraft\Fuzzy\FuzzyMatcher;
use SugarCraft\Fuzzy\Matcher\SmithWatermanMatcher;
use SugarCraft\Fuzzy\MatchResult;
use SugarCraft\Fuzzy\Highlighter;

$matcher = new SmithWatermanMatcher();

// Single match — returns null when no match
$result = $matcher->match('foo', 'foobar');
// $result->score     === 3
// $result->indices    === [0, 1, 2]  (0-based character positions)
// $result->candidate === 'foobar'

// Match all candidates, sorted by score desc then candidate asc
$results = $matcher->matchAll('ab', ['apple', 'banana', 'cabbage', 'cabbage']);
// Results sorted: cabbage (score 2) before banana (score 1) before apple (score 0)

// Use Highlighter to mark matched runs with ANSI codes
$highlighter = new Highlighter();
echo $highlighter->highlight($result, fn($s) => "\e[1;31m{$s}\e[0m");
// Output: 'foober' with positions [0,1,2] highlighted in bold red

FuzzyMatcher interface

match(string $query, string $candidate): ?MatchResultReturns null when no match, MatchResult with score + indices otherwise.
matchAll(string $query, iterable $candidates): list<MatchResult>Returns all matches sorted by score desc, then candidate asc as tiebreak.

MatchResult

score(): intMatch quality score — higher is better; 0 means no match.
indices(): list<int>0-based character offsets of matched characters (UTF-8 safe — indices are at character boundaries, not bytes).
candidate(): stringThe candidate string that was matched.

Algorithms

SmithWatermanMatcherLocal sequence alignment — optimal for finding best subsequence match within a candidate. Bit-equivalent in score and ranking to the original candy-forms FuzzyMatcher.
SahilmMatcherPorts the sahilm/fuzzy Go algorithm — separator bonus (/, _, -, space, .), camel-case bonus, exact-prefix bonus. Good for file/path matching.

Highlighter

Takes a MatchResult and a styler callable(string): string, returns the candidate string with matched runs passed through the styler. Non-matched runs are returned unchanged.

Use it for

Source & demos

Try the quickstart →

API

ClassMethodDescription
FuzzyMatchermatch(string $query, string $candidate): ?MatchResultSingle match or null
FuzzyMatchermatchAll(string $query, iterable $candidates): list<MatchResult>All matches sorted by score desc
MatchResultscore(): intMatch quality score (higher = better)
MatchResultindices(): list<int>0-based matched character offsets
MatchResultcandidate(): stringThe matched candidate string
SmithWatermanMatcherimplements FuzzyMatcherLocal sequence alignment algorithm
SahilmMatcherimplements FuzzyMatcherSeparator/camelCase bonus algorithm
Highlighterhighlight(MatchResult, callable(string): string): stringApply styler to matched runs