mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Add Mapping to original string after translation. Not sure about the performance, but seems satisfying.
It requires at most n times loop (n: number of translated charater) mapping once.
This commit is contained in:
parent
8a76ad000d
commit
59e61cebe3
2 changed files with 108 additions and 57 deletions
|
|
@ -1,21 +1,77 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using ToolGood.Words.Pinyin;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure
|
||||
{
|
||||
public class TranslationMapping
|
||||
{
|
||||
private bool constructed;
|
||||
|
||||
private List<int> originalIndexs = new List<int>();
|
||||
private List<int> translatedIndexs = new List<int>();
|
||||
private int translaedLength = 0;
|
||||
|
||||
public void AddNewIndex(int originalIndex, int translatedIndex, int length)
|
||||
{
|
||||
if (constructed)
|
||||
throw new InvalidOperationException("Mapping shouldn't be changed after constructed");
|
||||
|
||||
originalIndexs.Add(originalIndex);
|
||||
translatedIndexs.Add(translatedIndex);
|
||||
translatedIndexs.Add(translatedIndex + length);
|
||||
translaedLength += length - 1;
|
||||
}
|
||||
|
||||
public int? MapToOriginalIndex(int translatedIndex)
|
||||
{
|
||||
if (translatedIndex > translatedIndexs.Last())
|
||||
return translatedIndex - translaedLength - 1;
|
||||
|
||||
for (var i = 0; i < originalIndexs.Count; i++)
|
||||
{
|
||||
if (translatedIndex >= translatedIndexs[i * 2] && translatedIndex < translatedIndexs[i * 2 + 1])
|
||||
return originalIndexs[i];
|
||||
if (translatedIndex < translatedIndexs[i * 2])
|
||||
{
|
||||
int indexDiff = 0;
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
indexDiff += translatedIndexs[i * 2 + 1] - translatedIndexs[i * 2] - 1;
|
||||
}
|
||||
|
||||
return translatedIndex - indexDiff;
|
||||
}
|
||||
}
|
||||
|
||||
return translatedIndex;
|
||||
}
|
||||
|
||||
public void endConstruct()
|
||||
{
|
||||
if (constructed)
|
||||
throw new InvalidOperationException("Mapping has already been constructed");
|
||||
constructed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAlphabet
|
||||
{
|
||||
string Translate(string stringToTranslate);
|
||||
public (string translation, TranslationMapping map) Translate(string stringToTranslate);
|
||||
}
|
||||
|
||||
public class PinyinAlphabet : IAlphabet
|
||||
{
|
||||
private ConcurrentDictionary<string, string> _pinyinCache = new ConcurrentDictionary<string, string>();
|
||||
private ConcurrentDictionary<string, (string translation, TranslationMapping map)> _pinyinCache =
|
||||
new ConcurrentDictionary<string, (string translation, TranslationMapping map)>();
|
||||
|
||||
|
||||
private Settings _settings;
|
||||
|
||||
public void Initialize([NotNull] Settings settings)
|
||||
|
|
@ -23,7 +79,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
public string Translate(string content)
|
||||
public (string translation, TranslationMapping map) Translate(string content)
|
||||
{
|
||||
if (_settings.ShouldUsePinyin)
|
||||
{
|
||||
|
|
@ -34,14 +90,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
var resultList = WordsHelper.GetPinyinList(content);
|
||||
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < resultList.Length; i++)
|
||||
{
|
||||
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
|
||||
resultBuilder.Append(resultList[i].First());
|
||||
}
|
||||
|
||||
resultBuilder.Append(' ');
|
||||
TranslationMapping map = new TranslationMapping();
|
||||
|
||||
bool pre = false;
|
||||
|
||||
|
|
@ -49,6 +98,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
{
|
||||
if (content[i] >= 0x3400 && content[i] <= 0x9FD5)
|
||||
{
|
||||
map.AddNewIndex(i, resultBuilder.Length, resultList[i].Length + 1);
|
||||
resultBuilder.Append(' ');
|
||||
resultBuilder.Append(resultList[i]);
|
||||
pre = true;
|
||||
|
|
@ -60,15 +110,18 @@ namespace Flow.Launcher.Infrastructure
|
|||
pre = false;
|
||||
resultBuilder.Append(' ');
|
||||
}
|
||||
|
||||
resultBuilder.Append(resultList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return _pinyinCache[content] = resultBuilder.ToString();
|
||||
map.endConstruct();
|
||||
|
||||
return _pinyinCache[content] = (resultBuilder.ToString(), map);
|
||||
}
|
||||
else
|
||||
{
|
||||
return content;
|
||||
return (content, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -78,7 +131,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
}
|
||||
else
|
||||
{
|
||||
return content;
|
||||
return (content, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,22 +44,12 @@ namespace Flow.Launcher.Infrastructure
|
|||
/// </summary>
|
||||
public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption opt)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query)) return new MatchResult(false, UserSettingSearchPrecision);
|
||||
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query))
|
||||
return new MatchResult(false, UserSettingSearchPrecision);
|
||||
|
||||
query = query.Trim();
|
||||
|
||||
stringToCompare = _alphabet?.Translate(stringToCompare) ?? stringToCompare;
|
||||
|
||||
// This also can be done by spliting the query
|
||||
|
||||
//(var spaceSplit, var upperSplit) = stringToCompare switch
|
||||
//{
|
||||
// string s when s.Contains(' ') => (s.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(w => w.First()),
|
||||
// default(IEnumerable<char>)),
|
||||
// string s when s.Any(c => char.IsUpper(c)) && s.Any(c => char.IsLower(c)) =>
|
||||
// (null, Regex.Split(s, @"(?<!^)(?=[A-Z])").Select(w => w.First())),
|
||||
// _ => ((IEnumerable<char>)null, (IEnumerable<char>)null)
|
||||
//};
|
||||
TranslationMapping map;
|
||||
(stringToCompare, map) = _alphabet?.Translate(stringToCompare) ?? (stringToCompare, null);
|
||||
|
||||
var currentQueryIndex = 0;
|
||||
var acronymMatchData = new List<int>();
|
||||
|
|
@ -72,28 +62,24 @@ namespace Flow.Launcher.Infrastructure
|
|||
if (currentQueryIndex >= queryWithoutCase.Length)
|
||||
break;
|
||||
|
||||
if (compareIndex == 0 && queryWithoutCase[currentQueryIndex] == char.ToLower(stringToCompare[compareIndex]))
|
||||
{
|
||||
acronymMatchData.Add(compareIndex);
|
||||
currentQueryIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (stringToCompare[compareIndex])
|
||||
{
|
||||
case char c when compareIndex == 0 && queryWithoutCase[currentQueryIndex] == char.ToLower(stringToCompare[compareIndex])
|
||||
|| (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex])
|
||||
|| (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) == queryWithoutCase[currentQueryIndex])
|
||||
|| (char.IsNumber(c) && c == queryWithoutCase[currentQueryIndex]):
|
||||
acronymMatchData.Add(compareIndex);
|
||||
case var c when (compareIndex == 0 && queryWithoutCase[currentQueryIndex] ==
|
||||
char.ToLower(stringToCompare[compareIndex]))
|
||||
|| (char.IsUpper(c) && char.ToLower(c) == queryWithoutCase[currentQueryIndex])
|
||||
|| (char.IsWhiteSpace(c) && char.ToLower(stringToCompare[++compareIndex]) ==
|
||||
queryWithoutCase[currentQueryIndex])
|
||||
|| (char.IsNumber(c) && c == queryWithoutCase[currentQueryIndex]):
|
||||
acronymMatchData.Add(map?.MapToOriginalIndex(compareIndex) ?? compareIndex);
|
||||
currentQueryIndex++;
|
||||
continue;
|
||||
|
||||
case char c when char.IsWhiteSpace(c):
|
||||
case var c when char.IsWhiteSpace(c):
|
||||
compareIndex++;
|
||||
acronymScore -= 10;
|
||||
break;
|
||||
case char c when char.IsUpper(c) || char.IsNumber(c):
|
||||
case var c when char.IsUpper(c) || char.IsNumber(c):
|
||||
acronymScore -= 10;
|
||||
break;
|
||||
}
|
||||
|
|
@ -105,7 +91,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
|
||||
|
||||
|
||||
var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var querySubstrings = queryWithoutCase.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
|
||||
int currentQuerySubstringIndex = 0;
|
||||
var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
|
||||
var currentQuerySubstringCharacterIndex = 0;
|
||||
|
|
@ -120,9 +106,10 @@ namespace Flow.Launcher.Infrastructure
|
|||
var indexList = new List<int>();
|
||||
List<int> spaceIndices = new List<int>();
|
||||
|
||||
for (var compareStringIndex = 0; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++)
|
||||
for (var compareStringIndex = 0;
|
||||
compareStringIndex < fullStringToCompareWithoutCase.Length;
|
||||
compareStringIndex++)
|
||||
{
|
||||
|
||||
// To maintain a list of indices which correspond to spaces in the string to compare
|
||||
// To populate the list only for the first query substring
|
||||
if (fullStringToCompareWithoutCase[compareStringIndex].Equals(' ') && currentQuerySubstringIndex == 0)
|
||||
|
|
@ -130,7 +117,8 @@ namespace Flow.Launcher.Infrastructure
|
|||
spaceIndices.Add(compareStringIndex);
|
||||
}
|
||||
|
||||
if (fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex])
|
||||
if (fullStringToCompareWithoutCase[compareStringIndex] !=
|
||||
currentQuerySubstring[currentQuerySubstringCharacterIndex])
|
||||
{
|
||||
matchFoundInPreviousLoop = false;
|
||||
continue;
|
||||
|
|
@ -154,14 +142,16 @@ namespace Flow.Launcher.Infrastructure
|
|||
// in order to do so we need to verify all previous chars are part of the pattern
|
||||
var startIndexToVerify = compareStringIndex - currentQuerySubstringCharacterIndex;
|
||||
|
||||
if (AllPreviousCharsMatched(startIndexToVerify, currentQuerySubstringCharacterIndex, fullStringToCompareWithoutCase, currentQuerySubstring))
|
||||
if (AllPreviousCharsMatched(startIndexToVerify, currentQuerySubstringCharacterIndex,
|
||||
fullStringToCompareWithoutCase, currentQuerySubstring))
|
||||
{
|
||||
matchFoundInPreviousLoop = true;
|
||||
|
||||
// if it's the beginning character of the first query substring that is matched then we need to update start index
|
||||
firstMatchIndex = currentQuerySubstringIndex == 0 ? startIndexToVerify : firstMatchIndex;
|
||||
|
||||
indexList = GetUpdatedIndexList(startIndexToVerify, currentQuerySubstringCharacterIndex, firstMatchIndexInWord, indexList);
|
||||
indexList = GetUpdatedIndexList(startIndexToVerify, currentQuerySubstringCharacterIndex,
|
||||
firstMatchIndexInWord, indexList);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,11 +164,13 @@ namespace Flow.Launcher.Infrastructure
|
|||
if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length)
|
||||
{
|
||||
// if any of the substrings was not matched then consider as all are not matched
|
||||
allSubstringsContainedInCompareString = matchFoundInPreviousLoop && allSubstringsContainedInCompareString;
|
||||
allSubstringsContainedInCompareString =
|
||||
matchFoundInPreviousLoop && allSubstringsContainedInCompareString;
|
||||
|
||||
currentQuerySubstringIndex++;
|
||||
|
||||
allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length);
|
||||
allQuerySubstringsMatched =
|
||||
AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length);
|
||||
if (allQuerySubstringsMatched)
|
||||
break;
|
||||
|
||||
|
|
@ -188,13 +180,16 @@ namespace Flow.Launcher.Infrastructure
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// proceed to calculate score if every char or substring without whitespaces matched
|
||||
if (allQuerySubstringsMatched)
|
||||
{
|
||||
var nearestSpaceIndex = CalculateClosestSpaceIndex(spaceIndices, firstMatchIndex);
|
||||
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
|
||||
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1,
|
||||
lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
|
||||
|
||||
return new MatchResult(true, UserSettingSearchPrecision, indexList, score);
|
||||
var resultList = indexList.Distinct().Select(x => map?.MapToOriginalIndex(x) ?? x).ToList();
|
||||
return new MatchResult(true, UserSettingSearchPrecision, resultList, score);
|
||||
}
|
||||
|
||||
return new MatchResult(false, UserSettingSearchPrecision);
|
||||
|
|
@ -209,14 +204,15 @@ namespace Flow.Launcher.Infrastructure
|
|||
}
|
||||
else
|
||||
{
|
||||
int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item)).Where(item => firstMatchIndex > item).FirstOrDefault();
|
||||
int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item))
|
||||
.FirstOrDefault(item => firstMatchIndex > item);
|
||||
int closestSpaceIndex = ind ?? -1;
|
||||
return closestSpaceIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
|
||||
string fullStringToCompareWithoutCase, string currentQuerySubstring)
|
||||
string fullStringToCompareWithoutCase, string currentQuerySubstring)
|
||||
{
|
||||
var allMatch = true;
|
||||
for (int indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++)
|
||||
|
|
@ -231,7 +227,8 @@ namespace Flow.Launcher.Infrastructure
|
|||
return allMatch;
|
||||
}
|
||||
|
||||
private static List<int> GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List<int> indexList)
|
||||
private static List<int> GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex,
|
||||
int firstMatchIndexInWord, List<int> indexList)
|
||||
{
|
||||
var updatedList = new List<int>();
|
||||
|
||||
|
|
@ -252,7 +249,8 @@ namespace Flow.Launcher.Infrastructure
|
|||
return currentQuerySubstringIndex >= querySubstringsLength;
|
||||
}
|
||||
|
||||
private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, bool allSubstringsContainedInCompareString)
|
||||
private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen,
|
||||
bool allSubstringsContainedInCompareString)
|
||||
{
|
||||
// A match found near the beginning of a string is scored more than a match found near the end
|
||||
// A match is scored more if the characters in the patterns are closer to each other,
|
||||
|
|
@ -347,7 +345,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
|
||||
private bool IsSearchPrecisionScoreMet(int rawScore)
|
||||
{
|
||||
return rawScore >= (int)SearchPrecision;
|
||||
return rawScore >= (int) SearchPrecision;
|
||||
}
|
||||
|
||||
private int ScoreAfterSearchPrecisionFilter(int rawScore)
|
||||
|
|
@ -360,4 +358,4 @@ namespace Flow.Launcher.Infrastructure
|
|||
{
|
||||
public bool IgnoreCase { get; set; } = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue