Skip to content

Commit 95325a8

Browse files
authored
Merge pull request #4 from Flow-Launcher/acronym_scoring
Acronym Scoring Change
2 parents c395cc7 + 65a6548 commit 95325a8

File tree

4 files changed

+173
-109
lines changed

4 files changed

+173
-109
lines changed

Flow.Launcher.Infrastructure/StringMatcher.cs

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,13 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption
6666
var currentAcronymQueryIndex = 0;
6767
var acronymMatchData = new List<int>();
6868

69-
// preset acronymScore
70-
int acronymScore = 100;
69+
int acronymsTotalCount = 0;
70+
int acronymsMatched = 0;
7171

7272
var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
7373
var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query;
7474

75-
var querySubstrings = queryWithoutCase.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
75+
var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
7676
int currentQuerySubstringIndex = 0;
7777
var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
7878
var currentQuerySubstringCharacterIndex = 0;
@@ -87,12 +87,18 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption
8787
var indexList = new List<int>();
8888
List<int> spaceIndices = new List<int>();
8989

90-
bool spaceMet = false;
91-
9290
for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++)
9391
{
94-
if (currentAcronymQueryIndex >= queryWithoutCase.Length
95-
|| allQuerySubstringsMatched && acronymScore < (int) UserSettingSearchPrecision)
92+
// If acronyms matching successfully finished, this gets the remaining not matched acronyms for score calculation
93+
if (currentAcronymQueryIndex >= query.Length && acronymsMatched == query.Length)
94+
{
95+
if (IsAcronymCount(stringToCompare, compareStringIndex))
96+
acronymsTotalCount++;
97+
continue;
98+
}
99+
100+
if (currentAcronymQueryIndex >= query.Length ||
101+
currentAcronymQueryIndex >= query.Length && allQuerySubstringsMatched)
96102
break;
97103

98104
// To maintain a list of indices which correspond to spaces in the string to compare
@@ -101,42 +107,21 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption
101107
spaceIndices.Add(compareStringIndex);
102108

103109
// Acronym check
104-
if (char.IsUpper(stringToCompare[compareStringIndex]) ||
105-
char.IsNumber(stringToCompare[compareStringIndex]) ||
106-
char.IsWhiteSpace(stringToCompare[compareStringIndex]) ||
107-
spaceMet)
110+
if (IsAcronym(stringToCompare, compareStringIndex))
108111
{
109112
if (fullStringToCompareWithoutCase[compareStringIndex] ==
110113
queryWithoutCase[currentAcronymQueryIndex])
111114
{
112-
if (!spaceMet)
113-
{
114-
char currentCompareChar = stringToCompare[compareStringIndex];
115-
spaceMet = char.IsWhiteSpace(currentCompareChar);
116-
// if is space, no need to check whether upper or digit, though insignificant
117-
if (!spaceMet && compareStringIndex == 0 || char.IsUpper(currentCompareChar) ||
118-
char.IsDigit(currentCompareChar))
119-
{
120-
acronymMatchData.Add(compareStringIndex);
121-
}
122-
}
123-
else if (!(spaceMet = char.IsWhiteSpace(stringToCompare[compareStringIndex])))
124-
{
125-
acronymMatchData.Add(compareStringIndex);
126-
}
115+
acronymMatchData.Add(compareStringIndex);
116+
acronymsMatched++;
127117

128118
currentAcronymQueryIndex++;
129119
}
130-
else
131-
{
132-
spaceMet = char.IsWhiteSpace(stringToCompare[compareStringIndex]);
133-
// Acronym Penalty
134-
if (!spaceMet)
135-
{
136-
acronymScore -= 10;
137-
}
138-
}
139120
}
121+
122+
if (IsAcronymCount(stringToCompare, compareStringIndex))
123+
acronymsTotalCount++;
124+
140125
// Acronym end
141126

142127
if (allQuerySubstringsMatched || fullStringToCompareWithoutCase[compareStringIndex] !=
@@ -204,11 +189,16 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption
204189
}
205190
}
206191

207-
// return acronym Match if possible
208-
if (acronymMatchData.Count == query.Length && acronymScore >= (int) UserSettingSearchPrecision)
192+
// return acronym match if all query char matched
193+
if (acronymsMatched > 0 && acronymsMatched == query.Length)
209194
{
210-
acronymMatchData = acronymMatchData.Select(x => translationMapping?.MapToOriginalIndex(x) ?? x).Distinct().ToList();
211-
return new MatchResult(true, UserSettingSearchPrecision, acronymMatchData, acronymScore);
195+
int acronymScore = acronymsMatched * 100 / acronymsTotalCount;
196+
197+
if (acronymScore >= (int)UserSettingSearchPrecision)
198+
{
199+
acronymMatchData = acronymMatchData.Select(x => translationMapping?.MapToOriginalIndex(x) ?? x).Distinct().ToList();
200+
return new MatchResult(true, UserSettingSearchPrecision, acronymMatchData, acronymScore);
201+
}
212202
}
213203

214204
// proceed to calculate score if every char or substring without whitespaces matched
@@ -225,20 +215,49 @@ public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption
225215
return new MatchResult(false, UserSettingSearchPrecision);
226216
}
227217

218+
private bool IsAcronym(string stringToCompare, int compareStringIndex)
219+
{
220+
if (IsAcronymChar(stringToCompare, compareStringIndex) || IsAcronymNumber(stringToCompare, compareStringIndex))
221+
return true;
222+
223+
return false;
224+
}
225+
226+
// When counting acronyms, treat a set of numbers as one acronym ie. Visual 2019 as 2 acronyms instead of 5
227+
private bool IsAcronymCount(string stringToCompare, int compareStringIndex)
228+
{
229+
if (IsAcronymChar(stringToCompare, compareStringIndex))
230+
return true;
231+
232+
if (IsAcronymNumber(stringToCompare, compareStringIndex))
233+
return compareStringIndex == 0 || char.IsWhiteSpace(stringToCompare[compareStringIndex - 1]);
234+
235+
236+
return false;
237+
}
238+
239+
private bool IsAcronymChar(string stringToCompare, int compareStringIndex)
240+
=> char.IsUpper(stringToCompare[compareStringIndex]) ||
241+
compareStringIndex == 0 || // 0 index means char is the start of the compare string, which is an acronym
242+
char.IsWhiteSpace(stringToCompare[compareStringIndex - 1]);
243+
244+
private bool IsAcronymNumber(string stringToCompare, int compareStringIndex) => stringToCompare[compareStringIndex] >= 0 && stringToCompare[compareStringIndex] <= 9;
245+
228246
// To get the index of the closest space which preceeds the first matching index
229247
private int CalculateClosestSpaceIndex(List<int> spaceIndices, int firstMatchIndex)
230248
{
231-
if (spaceIndices.Count == 0)
232-
{
233-
return -1;
234-
}
235-
else
249+
var closestSpaceIndex = -1;
250+
251+
// spaceIndices should be ordered asc
252+
foreach (var index in spaceIndices)
236253
{
237-
int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item))
238-
.FirstOrDefault(item => firstMatchIndex > item);
239-
int closestSpaceIndex = ind ?? -1;
240-
return closestSpaceIndex;
254+
if (index < firstMatchIndex)
255+
closestSpaceIndex = index;
256+
else
257+
break;
241258
}
259+
260+
return closestSpaceIndex;
242261
}
243262

244263
private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex,

Flow.Launcher.Test/FuzzyMatcherTest.cs

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -131,16 +131,17 @@ public void GivenQueryString_WhenAppliedPrecisionFiltering_ThenShouldReturnGreat
131131

132132
[TestCase(Chrome, Chrome, 157)]
133133
[TestCase(Chrome, LastIsChrome, 147)]
134-
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 90)]
134+
[TestCase("chro", HelpCureHopeRaiseOnMindEntityChrome, 50)]
135+
[TestCase("chr", HelpCureHopeRaiseOnMindEntityChrome, 30)]
135136
[TestCase(Chrome, UninstallOrChangeProgramsOnYourComputer, 21)]
136137
[TestCase(Chrome, CandyCrushSagaFromKing, 0)]
137-
[TestCase("sql", MicrosoftSqlServerManagementStudio, 90)]
138+
[TestCase("sql", MicrosoftSqlServerManagementStudio, 110)]
138139
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, 121)] //double spacing intended
139140
public void WhenGivenQueryString_ThenShouldReturn_TheDesiredScoring(
140141
string queryString, string compareString, int expectedScore)
141142
{
142143
// When, Given
143-
var matcher = new StringMatcher();
144+
var matcher = new StringMatcher {UserSettingSearchPrecision = SearchPrecisionScore.Regular};
144145
var rawScore = matcher.FuzzyMatch(queryString, compareString).RawScore;
145146

146147
// Should
@@ -151,13 +152,22 @@ public void WhenGivenQueryString_ThenShouldReturn_TheDesiredScoring(
151152
[TestCase("goo", "Google Chrome", SearchPrecisionScore.Regular, true)]
152153
[TestCase("chr", "Google Chrome", SearchPrecisionScore.Low, true)]
153154
[TestCase("chr", "Chrome", SearchPrecisionScore.Regular, true)]
155+
[TestCase("chr", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Regular, false)]
154156
[TestCase("chr", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Low, true)]
155157
[TestCase("chr", "Candy Crush Saga from King", SearchPrecisionScore.Regular, false)]
156158
[TestCase("chr", "Candy Crush Saga from King", SearchPrecisionScore.None, true)]
157-
[TestCase("ccs", "Candy Crush Saga from King", SearchPrecisionScore.Regular, true)]
159+
[TestCase("ccs", "Candy Crush Saga from King", SearchPrecisionScore.Low, true)]
158160
[TestCase("cand", "Candy Crush Saga from King", SearchPrecisionScore.Regular, true)]
159-
[TestCase("cand", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Regular,
160-
false)]
161+
[TestCase("cand", "Help cure hope raise on mind entity Chrome", SearchPrecisionScore.Regular, false)]
162+
[TestCase("vsc", VisualStudioCode, SearchPrecisionScore.Regular, true)]
163+
[TestCase("vs", VisualStudioCode, SearchPrecisionScore.Regular, true)]
164+
[TestCase("vc", VisualStudioCode, SearchPrecisionScore.Regular, true)]
165+
[TestCase("vts", VisualStudioCode, SearchPrecisionScore.Regular, false)]
166+
[TestCase("vcs", VisualStudioCode, SearchPrecisionScore.Regular, false)]
167+
[TestCase("wt", "Windows Terminal From Microsoft Store", SearchPrecisionScore.Regular, false)]
168+
[TestCase("vsp", "Visual Studio 2019 Preview", SearchPrecisionScore.Regular, true)]
169+
[TestCase("vsp", "2019 Visual Studio Preview", SearchPrecisionScore.Regular, true)]
170+
[TestCase("2019p", "Visual Studio 2019 Preview", SearchPrecisionScore.Regular, true)]
161171
public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual(
162172
string queryString,
163173
string compareString,
@@ -180,18 +190,16 @@ public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual(
180190

181191
// Should
182192
Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
183-
$"Query:{queryString}{Environment.NewLine} " +
184-
$"Compare:{compareString}{Environment.NewLine}" +
193+
$"Query: {queryString}{Environment.NewLine} " +
194+
$"Compare: {compareString}{Environment.NewLine}" +
185195
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
186196
$"Precision Score: {(int)expectedPrecisionScore}");
187197
}
188198

189199
[TestCase("exce", "OverLeaf-Latex: An online LaTeX editor", SearchPrecisionScore.Regular, false)]
190200
[TestCase("term", "Windows Terminal (Preview)", SearchPrecisionScore.Regular, true)]
191-
[TestCase("sql s managa", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular,
192-
false)]
193-
[TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular,
194-
false)]
201+
[TestCase("sql s managa", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)]
202+
[TestCase("sql' s manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, false)]
195203
[TestCase("sql s manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
196204
[TestCase("sql manag", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
197205
[TestCase("sql", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
@@ -204,18 +212,13 @@ public void WhenGivenDesiredPrecision_ThenShouldReturn_AllResultsGreaterOrEqual(
204212
[TestCase("mssms", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
205213
[TestCase("msms", MicrosoftSqlServerManagementStudio, SearchPrecisionScore.Regular, true)]
206214
[TestCase("chr", "Shutdown", SearchPrecisionScore.Regular, false)]
207-
[TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).",
208-
SearchPrecisionScore.Regular, false)]
209-
[TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).",
210-
SearchPrecisionScore.Regular, true)]
215+
[TestCase("chr", "Change settings for text-to-speech and for speech recognition (if installed).", SearchPrecisionScore.Regular, false)]
216+
[TestCase("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", SearchPrecisionScore.Regular, true)]
211217
[TestCase("a test", "This is a test", SearchPrecisionScore.Regular, true)]
212218
[TestCase("test", "This is a test", SearchPrecisionScore.Regular, true)]
213219
[TestCase("cod", VisualStudioCode, SearchPrecisionScore.Regular, true)]
214220
[TestCase("code", VisualStudioCode, SearchPrecisionScore.Regular, true)]
215221
[TestCase("codes", "Visual Studio Codes", SearchPrecisionScore.Regular, true)]
216-
[TestCase("vsc", VisualStudioCode, SearchPrecisionScore.Regular, true)]
217-
[TestCase("vs", VisualStudioCode, SearchPrecisionScore.Regular, true)]
218-
[TestCase("vc", VisualStudioCode, SearchPrecisionScore.Regular, true)]
219222
public void WhenGivenQuery_ShouldReturnResults_ContainingAllQuerySubstrings(
220223
string queryString,
221224
string compareString,
@@ -300,15 +303,19 @@ public void WhenMultipleResults_ExactMatchingResult_ShouldHaveGreatestScore(
300303
$"Should be greater than{Environment.NewLine}" +
301304
$"Name of second: \"{secondName}\", Final Score: {secondScore}{Environment.NewLine}");
302305
}
303-
304-
[TestCase("vsc","Visual Studio Code", 100)]
305-
[TestCase("jbr","JetBrain Rider",100)]
306-
[TestCase("jr","JetBrain Rider",90)]
307-
[TestCase("vs","Visual Studio",100)]
308-
[TestCase("vs","Visual Studio Preview",100)]
309-
[TestCase("vsp","Visual Studio Preview",100)]
310-
[TestCase("vsp","Visual Studio",0)]
311-
[TestCase("pc","Postman Canary",100)]
306+
307+
[TestCase("vsc", "Visual Studio Code", 100)]
308+
[TestCase("jbr", "JetBrain Rider", 100)]
309+
[TestCase("jr", "JetBrain Rider", 66)]
310+
[TestCase("vs", "Visual Studio", 100)]
311+
[TestCase("vs", "Visual Studio Preview", 66)]
312+
[TestCase("vsp", "Visual Studio Preview", 100)]
313+
[TestCase("pc", "postman canary", 100)]
314+
[TestCase("psc", "Postman super canary", 100)]
315+
[TestCase("psc", "Postman super Canary", 100)]
316+
[TestCase("vsp", "Visual Studio", 0)]
317+
[TestCase("vps", "Visual Studio", 0)]
318+
[TestCase(Chrome, HelpCureHopeRaiseOnMindEntityChrome, 75)]
312319
public void WhenGivenAnAcronymQuery_ShouldReturnAcronymScore(string queryString, string compareString,
313320
int desiredScore)
314321
{

0 commit comments

Comments
 (0)