From 1a843db3b7bc2bd3db62bd2d49691d4b73617ea9 Mon Sep 17 00:00:00 2001 From: Eberhard Graether Date: Wed, 20 Apr 2016 16:00:26 +0200 Subject: [PATCH] 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 --- src/lib/data/Storage.cpp | 33 ++++++---- src/lib/data/search/SearchIndex.cpp | 98 +++++++++++++++++------------ src/lib/data/search/SearchIndex.h | 2 +- 3 files changed, 80 insertions(+), 53 deletions(-) diff --git a/src/lib/data/Storage.cpp b/src/lib/data/Storage.cpp index 107a8bac..2a2882be 100644 --- a/src/lib/data/Storage.cpp +++ b/src/lib/data/Storage.cpp @@ -219,9 +219,13 @@ std::vector Storage::getAutocompletionMatches(const std::string& qu const size_t maxResultCount = 100; std::vector elementResults = m_elementIndex.search(query, maxResultCount); - std::sort(elementResults.begin(), elementResults.end(), []( - const SearchResult& a, - const SearchResult& b) + + std::vector results; + 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? if (a.score > b.score) @@ -230,27 +234,32 @@ std::vector Storage::getAutocompletionMatches(const std::string& qu } else if (a.score == b.score) { - if (a.text.size() < b.text.size()) { return true; } else if (a.text.size() == b.text.size()) { - // move uppercase letters to higher ascii range - std::string sA = utility::switchCases(a.text); - std::string sB = utility::switchCases(b.text); - return (sA.compare(sB) < 0); + for (size_t i = 0; i < a.text.size(); i++) + { + if (tolower(a.text[i]) != tolower(b.text[i])) + { + return tolower(a.text[i]) < tolower(b.text[i]); + } + else + { + if (a.text[i] < b.text[i]) + { + return true; + } + } + } } } return false; } ); - std::vector results; - utility::append(results, commandResults); - utility::append(results, elementResults); - std::vector matches; for (size_t i = 0; i < results.size(); i++) { diff --git a/src/lib/data/search/SearchIndex.cpp b/src/lib/data/search/SearchIndex.cpp index 589de072..ba94e6a3 100644 --- a/src/lib/data/search/SearchIndex.cpp +++ b/src/lib/data/search/SearchIndex.cpp @@ -4,6 +4,7 @@ #include #include "utility/utility.h" +#include "utility/utilityString.h" SearchIndex::SearchIndex() { @@ -106,81 +107,101 @@ void SearchIndex::clear() std::vector SearchIndex::search(const std::string& query, size_t maxResultCount) const { - std::string lowerCaseQuery = ""; - for (size_t i = 0; i < query.size(); i++) - { - lowerCaseQuery += tolower(query[i]); - } - + std::string lowerCaseQuery = utility::toLowerCase(query); Path startPath; startPath.node = m_root; - std::vector paths = search(startPath, lowerCaseQuery); + std::vector paths; + search(startPath, lowerCaseQuery, &paths); + + std::set noLetters; + noLetters.insert(' '); + noLetters.insert('.'); + noLetters.insert(','); + noLetters.insert('_'); + noLetters.insert(':'); + noLetters.insert('<'); + noLetters.insert('>'); // scoring paths - std::vector> scoredPaths; + std::multiset, bool(*)(const std::pair&, const std::pair&)> scoredPaths( + [](const std::pair& a, const std::pair& b) + { + return a.first > b.first; + } + ); + for (size_t i = 0; i < paths.size(); i++) { const std::vector& currentIndices = paths[i].indices; + const std::string& currentText = paths[i].text; const int unmatchedLetterBonus = -1; const int consecutiveLetterBonus = 5; - const int camelCaseBonus = 10; + const int camelCaseBonus = 5; + const int noLetterBonus = 3; const int delayedStartBonus = -3; - const int minDelayedStartBonus = -9; + const int minDelayedStartBonus = -15; int unmatchedLetterScore = 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 noLetterScore = 0; + 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]; - if (isupper(paths[i].text[index])) + // camel case + if (isupper(currentText[index])) { - bool prevIsLower = (index == 0 || islower(paths[i].text[index-1])); - bool nextIsLower = (index + 1 == paths[i].text.size() || islower(paths[i].text[index+1])); + bool prevIsLower = (index > 0 && islower(currentText[index-1])); + bool nextIsLower = (index + 1 == currentText.size() || islower(currentText[index+1])); if (prevIsLower && nextIsLower) { camelCaseScore += camelCaseBonus; } } + + // after no letter + bool prevIsNoLetter = (index > 0 && noLetters.find(currentText[index-1]) != noLetters.end()); + if (prevIsNoLetter) + { + noLetterScore += noLetterBonus; + } } - int leadingStartScore = 0; - leadingStartScore += std::max(int(currentIndices[0]) * delayedStartBonus, minDelayedStartBonus); + int leadingStartScore = std::max(int(currentIndices[0]) * delayedStartBonus, minDelayedStartBonus); int score = unmatchedLetterScore + consecutiveLetterScore + camelCaseScore + + noLetterScore + 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 a, - std::pair b) - { - return b.first < a.first; - } - ); - // preparing results std::vector searchResults; - for (size_t i = 0; i < scoredPaths.size() && (maxResultCount == 0 || searchResults.size() < maxResultCount); i++) + for (const std::pair currentResult : scoredPaths) { - int currentScore = scoredPaths[i].first; + if (maxResultCount > 0 && searchResults.size() >= maxResultCount) + { + break; + } + + int currentScore = currentResult.first; std::vector currentPaths; - currentPaths.push_back(scoredPaths[i].second); + currentPaths.push_back(currentResult.second); while (currentPaths.size() > 0) { @@ -236,13 +257,11 @@ void SearchIndex::populateEdgeGate(Edge* e) } } -std::vector SearchIndex::search(const Path& path, const std::string& remainingQuery) const +void SearchIndex::search(const Path& path, const std::string& remainingQuery, std::vector* results) const { - std::vector results; - if (remainingQuery.size() == 0) { - results.push_back(path); + results->push_back(path); } else { @@ -283,9 +302,8 @@ std::vector SearchIndex::search(const Path& path, const std:: currentPath.indices = currentFoundIds; currentPath.text = path.text + edgeString; - utility::append(results, search(currentPath, currentRemainingQuery)); + search(currentPath, currentRemainingQuery, results); } } } - return results; } diff --git a/src/lib/data/search/SearchIndex.h b/src/lib/data/search/SearchIndex.h index 499e965c..c813ffe6 100644 --- a/src/lib/data/search/SearchIndex.h +++ b/src/lib/data/search/SearchIndex.h @@ -57,7 +57,7 @@ private: }; void populateEdgeGate(Edge* e); - std::vector search(const Path& path, const std::string& remainingQuery) const; + void search(const Path& path, const std::string& remainingQuery, std::vector* results) const; std::vector> m_nodes; std::vector> m_edges;