From cf5dd576e4c9c84d1823d4180aa1b6f1338bc0d4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 20:23:57 +0000 Subject: [PATCH] Rank real fuzzy matches above near-misses [patch] Rank computed isMatch for every item but sorted on score alone. A short near-miss such as "hell" avoids the skip and gap penalties a real match inside a longer string pays, so it outscored "say hello to everyone" for "hello". Sort on isMatch first, then score. Non-matches are still kept. Fixes ktsu-dev/TextFilter#123 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01M9aefrfAYJpanQVrUuFpJh --- TextFilter.Test/TextFilterTests.cs | 19 +++++++++++++++++++ TextFilter/TextFilter.cs | 5 +++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/TextFilter.Test/TextFilterTests.cs b/TextFilter.Test/TextFilterTests.cs index 66011f5..ed384b1 100644 --- a/TextFilter.Test/TextFilterTests.cs +++ b/TextFilter.Test/TextFilterTests.cs @@ -327,6 +327,25 @@ public void RankFuzzyReturnsCorrectRanking() CollectionAssert.AreEqual(new List { "hello", "hello world", "world" }, result); } + [TestMethod] + [DataRow("hello", "hell", "say hello to everyone in the room")] + [DataRow("abc", "ab", "axxxxxxbxxxxxxc")] + [DataRow("readme", "read", "docs/ReleaseNotes/readme.md")] + [DataRow("config", "conf", "src/app/settings/config.json")] + public void RankPutsARealMatchAboveAHigherScoringNearMiss(string filter, string nearMiss, string match) + { + List result = [.. TextFilter.Rank([nearMiss, match], filter)]; + CollectionAssert.AreEqual(new List { match, nearMiss }, result); + } + + [TestMethod] + public void RankWithKeySelectorPutsARealMatchAboveAHigherScoringNearMiss() + { + List<(int Id, string Text)> items = [(1, "hell"), (2, "say hello to everyone in the room")]; + List<(int Id, string Text)> result = [.. TextFilter.Rank(items, item => item.Text, "hello")]; + CollectionAssert.AreEqual(new List<(int, string)> { (2, "say hello to everyone in the room"), (1, "hell") }, result); + } + [TestMethod] public void RankEmptyStringsReturnsEmpty() { diff --git a/TextFilter/TextFilter.cs b/TextFilter/TextFilter.cs index 65c8a36..36d6c02 100644 --- a/TextFilter/TextFilter.cs +++ b/TextFilter/TextFilter.cs @@ -201,7 +201,7 @@ public static IEnumerable Rank(IEnumerable strings, string fuzzy /// A function to extract the string key from an item. /// The fuzzy filter pattern. /// The collection of items sorted by their match score. - /// Uses fuzzy matching to rank the items by their match score. + /// Uses fuzzy matching to rank the items by their match score. Items that match the pattern always sort above items that don't, because a short near-miss can outscore a real match inside a longer string. public static IEnumerable Rank(IEnumerable items, Func keySelector, string fuzzyFilter) { Ensure.NotNull(items); @@ -213,7 +213,8 @@ public static IEnumerable Rank(IEnumerable items, Func t.score) + .OrderByDescending(t => t.isMatch) + .ThenByDescending(t => t.score) .Select(t => t.item); }