Skip to content

Keep the prefix penalty capped when a later copy replaces the first letter - #113

Merged
matt-edmondson merged 1 commit into
mainfrom
claude/fuzzysearch-94-prefix-cap-on-rematch
Oct 7, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
claude/fuzzysearch-94-prefix-cap-on-rematch

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #94

What changed

In CalculateScoreCore, the prefix refund and the capped PenalizeNonPatternCharacters call ran only once, at the first provisional match of the first pattern letter. A later copy of that letter could still replace it as the best letter, for example a copy that earns the separator bonus. When that happened, everything before the copy became prefix, but those codepoints had already been charged unmatchedLetterPenalty each with no cap, plus −1 for the superseded letter. The result was that "a" + N×"x" + "_ab" scored 13 − N for "ab".

While the best letter is still the uncommitted first pattern codepoint, the method now records two things:

  • the capped prefix penalty it applied
  • every per-codepoint charge made after it, including skipped letters and lost rematches

When a copy of that letter replaces it, both are refunded, the capped prefix penalty is charged again for the new position, and the superseded letter is not charged −1. The provisional state ends as soon as the first letter is committed, when the next pattern codepoint matches or a pattern repeat arrives. From then on nothing changes.

The new condition is in a small helper, ReplacesProvisionalFirstLetter, which keeps CalculateScoreCore under the CA1502 complexity limit.

Tests

  • CalculateScore_ReplacedFirstLetter_KeepsThePrefixPenaltyCapped (N = 5, 20, 100): "a" + x + "_ab" now scores the same as x + "_ab".
  • Score_StrayEarlyFirstLetter_DoesNotSinkAPathMatch: tools/build/scripts/unit_test and docs/build/scripts/unit_test now score the same for "test".

Results:

  • With the Fuzzy.cs change reverted, all 4 new cases fail.
  • Full suite: 102 passed, 0 failed, including ReadmeTests. The library builds on all its targets.

Scope

The triage notes that #89, #91 and #94 all touch the rematch and prefix accounting. #91 is already on main, and this fix sits on top of it with the existing ranking tests still passing. Related open issues #101 (rematch keeping +5 adjacency) and #112 (an early partial match locking the alignment) are separate behaviours in the same loop, and this PR deliberately leaves them alone.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Vpup29Ycn8SGi7LvBjAq1c


Generated by Claude Code

…etter [patch]

The prefix refund and capped prefix penalty ran only when the first pattern
letter first matched. A later, better-placed copy of that letter could
replace it as the best letter, making everything before it prefix, yet
those codepoints stayed charged one point each with no cap, plus a point
for the superseded letter. "a" + N x "_ab" scored 13 - N for "ab".

Track what was charged while the first letter is still provisional, and
when a copy replaces it, refund that and charge the capped prefix penalty
for the new position instead.

Fixes #94

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vpup29Ycn8SGi7LvBjAq1c
@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants