logic: Tweaked autocompletions results

* mix command and token results
* tweaked scoring
* added scoring for matched letters after non letters like _
* increased performance by removing separate sort step using single results vector in recursive search
* made sorting alphabetical: A < a < B < b
This commit is contained in:
Eberhard Graether
2016-04-20 16:00:26 +02:00
parent ea4acef2de
commit 1a843db3b7
3 changed files with 80 additions and 53 deletions
+21 -12
View File
@@ -219,9 +219,13 @@ std::vector<SearchMatch> Storage::getAutocompletionMatches(const std::string& qu
const size_t maxResultCount = 100; const size_t maxResultCount = 100;
std::vector<SearchResult> elementResults = m_elementIndex.search(query, maxResultCount); std::vector<SearchResult> elementResults = m_elementIndex.search(query, maxResultCount);
std::sort(elementResults.begin(), elementResults.end(), [](
const SearchResult& a, std::vector<SearchResult> results;
const SearchResult& b) utility::append(results, commandResults);
utility::append(results, elementResults);
std::sort(results.begin(), results.end(),
[](const SearchResult& a, const SearchResult& b)
{ {
// should a be ranked higher than b? // should a be ranked higher than b?
if (a.score > b.score) if (a.score > b.score)
@@ -230,27 +234,32 @@ std::vector<SearchMatch> Storage::getAutocompletionMatches(const std::string& qu
} }
else if (a.score == b.score) else if (a.score == b.score)
{ {
if (a.text.size() < b.text.size()) if (a.text.size() < b.text.size())
{ {
return true; return true;
} }
else if (a.text.size() == b.text.size()) else if (a.text.size() == b.text.size())
{ {
// move uppercase letters to higher ascii range for (size_t i = 0; i < a.text.size(); i++)
std::string sA = utility::switchCases(a.text); {
std::string sB = utility::switchCases(b.text); if (tolower(a.text[i]) != tolower(b.text[i]))
return (sA.compare(sB) < 0); {
return tolower(a.text[i]) < tolower(b.text[i]);
}
else
{
if (a.text[i] < b.text[i])
{
return true;
}
}
}
} }
} }
return false; return false;
} }
); );
std::vector<SearchResult> results;
utility::append(results, commandResults);
utility::append(results, elementResults);
std::vector<SearchMatch> matches; std::vector<SearchMatch> matches;
for (size_t i = 0; i < results.size(); i++) for (size_t i = 0; i < results.size(); i++)
{ {
+58 -40
View File
@@ -4,6 +4,7 @@
#include <ctype.h> #include <ctype.h>
#include "utility/utility.h" #include "utility/utility.h"
#include "utility/utilityString.h"
SearchIndex::SearchIndex() SearchIndex::SearchIndex()
{ {
@@ -106,81 +107,101 @@ void SearchIndex::clear()
std::vector<SearchResult> SearchIndex::search(const std::string& query, size_t maxResultCount) const std::vector<SearchResult> SearchIndex::search(const std::string& query, size_t maxResultCount) const
{ {
std::string lowerCaseQuery = ""; std::string lowerCaseQuery = utility::toLowerCase(query);
for (size_t i = 0; i < query.size(); i++)
{
lowerCaseQuery += tolower(query[i]);
}
Path startPath; Path startPath;
startPath.node = m_root; startPath.node = m_root;
std::vector<Path> paths = search(startPath, lowerCaseQuery); std::vector<Path> paths;
search(startPath, lowerCaseQuery, &paths);
std::set<char> noLetters;
noLetters.insert(' ');
noLetters.insert('.');
noLetters.insert(',');
noLetters.insert('_');
noLetters.insert(':');
noLetters.insert('<');
noLetters.insert('>');
// scoring paths // scoring paths
std::vector<std::pair<int, Path>> scoredPaths; std::multiset<std::pair<int, Path>, bool(*)(const std::pair<int, Path>&, const std::pair<int, Path>&)> scoredPaths(
[](const std::pair<int, Path>& a, const std::pair<int, Path>& b)
{
return a.first > b.first;
}
);
for (size_t i = 0; i < paths.size(); i++) for (size_t i = 0; i < paths.size(); i++)
{ {
const std::vector<size_t>& currentIndices = paths[i].indices; const std::vector<size_t>& currentIndices = paths[i].indices;
const std::string& currentText = paths[i].text;
const int unmatchedLetterBonus = -1; const int unmatchedLetterBonus = -1;
const int consecutiveLetterBonus = 5; const int consecutiveLetterBonus = 5;
const int camelCaseBonus = 10; const int camelCaseBonus = 5;
const int noLetterBonus = 3;
const int delayedStartBonus = -3; const int delayedStartBonus = -3;
const int minDelayedStartBonus = -9; const int minDelayedStartBonus = -15;
int unmatchedLetterScore = 0; int unmatchedLetterScore = 0;
int consecutiveLetterScore = 0; int consecutiveLetterScore = 0;
for (size_t j = 1; j < currentIndices.size(); j++)
{
unmatchedLetterScore += (currentIndices[j] - currentIndices[j-1] - 1) * unmatchedLetterBonus;
consecutiveLetterScore += (currentIndices[j] - currentIndices[j-1] == 1 ? consecutiveLetterBonus : 0);
}
int camelCaseScore = 0; int camelCaseScore = 0;
int noLetterScore = 0;
for (size_t j = 0; j < currentIndices.size(); j++) for (size_t j = 0; j < currentIndices.size(); j++)
{ {
// unmatched and consecutive
if (j > 0)
{
unmatchedLetterScore += (currentIndices[j] - currentIndices[j-1] - 1) * unmatchedLetterBonus;
consecutiveLetterScore += (currentIndices[j] - currentIndices[j-1] == 1 ? consecutiveLetterBonus : 0);
}
size_t index = currentIndices[j]; size_t index = currentIndices[j];
if (isupper(paths[i].text[index])) // camel case
if (isupper(currentText[index]))
{ {
bool prevIsLower = (index == 0 || islower(paths[i].text[index-1])); bool prevIsLower = (index > 0 && islower(currentText[index-1]));
bool nextIsLower = (index + 1 == paths[i].text.size() || islower(paths[i].text[index+1])); bool nextIsLower = (index + 1 == currentText.size() || islower(currentText[index+1]));
if (prevIsLower && nextIsLower) if (prevIsLower && nextIsLower)
{ {
camelCaseScore += camelCaseBonus; camelCaseScore += camelCaseBonus;
} }
} }
// after no letter
bool prevIsNoLetter = (index > 0 && noLetters.find(currentText[index-1]) != noLetters.end());
if (prevIsNoLetter)
{
noLetterScore += noLetterBonus;
}
} }
int leadingStartScore = 0; int leadingStartScore = std::max(int(currentIndices[0]) * delayedStartBonus, minDelayedStartBonus);
leadingStartScore += std::max(int(currentIndices[0]) * delayedStartBonus, minDelayedStartBonus);
int score = int score =
unmatchedLetterScore + unmatchedLetterScore +
consecutiveLetterScore + consecutiveLetterScore +
camelCaseScore + camelCaseScore +
noLetterScore +
leadingStartScore; leadingStartScore;
scoredPaths.push_back(std::make_pair(score, paths[i])); scoredPaths.insert(std::make_pair(score, paths[i]));
} }
// sorting paths
std::sort(scoredPaths.begin(), scoredPaths.end(), [](
std::pair<int, Path> a,
std::pair<int, Path> b)
{
return b.first < a.first;
}
);
// preparing results // preparing results
std::vector<SearchResult> searchResults; std::vector<SearchResult> searchResults;
for (size_t i = 0; i < scoredPaths.size() && (maxResultCount == 0 || searchResults.size() < maxResultCount); i++) for (const std::pair<int, Path> currentResult : scoredPaths)
{ {
int currentScore = scoredPaths[i].first; if (maxResultCount > 0 && searchResults.size() >= maxResultCount)
{
break;
}
int currentScore = currentResult.first;
std::vector<Path> currentPaths; std::vector<Path> currentPaths;
currentPaths.push_back(scoredPaths[i].second); currentPaths.push_back(currentResult.second);
while (currentPaths.size() > 0) while (currentPaths.size() > 0)
{ {
@@ -236,13 +257,11 @@ void SearchIndex::populateEdgeGate(Edge* e)
} }
} }
std::vector<SearchIndex::Path> SearchIndex::search(const Path& path, const std::string& remainingQuery) const void SearchIndex::search(const Path& path, const std::string& remainingQuery, std::vector<SearchIndex::Path>* results) const
{ {
std::vector<Path> results;
if (remainingQuery.size() == 0) if (remainingQuery.size() == 0)
{ {
results.push_back(path); results->push_back(path);
} }
else else
{ {
@@ -283,9 +302,8 @@ std::vector<SearchIndex::Path> SearchIndex::search(const Path& path, const std::
currentPath.indices = currentFoundIds; currentPath.indices = currentFoundIds;
currentPath.text = path.text + edgeString; currentPath.text = path.text + edgeString;
utility::append(results, search(currentPath, currentRemainingQuery)); search(currentPath, currentRemainingQuery, results);
} }
} }
} }
return results;
} }
+1 -1
View File
@@ -57,7 +57,7 @@ private:
}; };
void populateEdgeGate(Edge* e); void populateEdgeGate(Edge* e);
std::vector<Path> search(const Path& path, const std::string& remainingQuery) const; void search(const Path& path, const std::string& remainingQuery, std::vector<SearchIndex::Path>* results) const;
std::vector<std::shared_ptr<Node>> m_nodes; std::vector<std::shared_ptr<Node>> m_nodes;
std::vector<std::shared_ptr<Edge>> m_edges; std::vector<std::shared_ptr<Edge>> m_edges;