logic: search ui performance optimization

* replaced the implementation of the search index. the search index now uses a search-trie. edges have gates that can be used to check if a character can be matched when following the edge.
* SearchMatches now have a text and a list of NameHierarchies that share this node (instead of Ids, so that nodes must be found by names (which makes sure that SearchMatches are still valid after refresh).
* when parsing a project's description the CodeController now looks for NameHierarchies instead of SearchNode names to find ids of linked elements.
This commit is contained in:
malte_langkabel
2016-04-04 15:39:39 +02:00
parent ffc00829f0
commit f3dc12b0ed
21 changed files with 410 additions and 1367 deletions
+214 -164
View File
@@ -1,208 +1,258 @@
#include "data/search/SearchIndex.h"
#include <algorithm>
#include <cctype>
#include <ctype.h>
#include "utility/logging/logging.h"
#include "data/search/SearchMatch.h"
std::vector<SearchMatch> SearchIndex::getMatches(
const SearchResults& searchResults,
const std::string& query
){
std::vector<SearchMatch> result;
for (SearchResultsIterator it = searchResults.begin(); it != searchResults.end(); it++)
{
SearchMatch match = it->node->fuzzyMatchData(query, it->parent->getParent());
result.push_back(match);
}
return result;
}
#include "utility/utility.h"
SearchIndex::SearchIndex()
: m_root(nullptr, DELIMITER, 0)
{
clear();
}
SearchIndex::~SearchIndex()
{
}
void SearchIndex::clear()
void SearchIndex::addNode(Id id, const NameHierarchy& nameHierarchy)
{
m_root.m_nodes.clear();
m_dictionary.clear();
m_tokenIds.clear();
}
Node* currentNode = m_root;
size_t SearchIndex::getNodeCount() const
{
return m_root.getNodeCount() - 1;
}
// we don't use the signature here, so elements with the same signature share the same node in the search index.
std::string remaining = nameHierarchy.getQualifiedName();
size_t SearchIndex::getCharCount() const
{
return m_dictionary.getCharCount();
}
size_t SearchIndex::getWordCount() const
{
return m_dictionary.getWordCount();
}
Id SearchIndex::getWordId(const std::string& word)
{
return m_dictionary.getWordId(word);
}
const std::string& SearchIndex::getWord(Id wordId) const
{
return m_dictionary.getWord(wordId);
}
SearchNode* SearchIndex::addNode(NameHierarchy nameHierarchy)
{
std::deque<Id> nameIds;
for (size_t i = 0; i < nameHierarchy.size(); i++)
while (remaining.size() > 0)
{
nameIds.push_back(m_dictionary.getWordId(nameHierarchy[i]->getName()));
}
if (nameIds.size())
{
return m_root.addNodeRecursive(&nameIds, m_dictionary).get();
}
return nullptr;
}
SearchNode* SearchIndex::getNode(const NameHierarchy& nameHierarchy) const
{
std::deque<Id> nameIds = m_dictionary.getWordIdsConst(nameHierarchy);
if (nameIds.size())
{
return m_root.getNodeRecursive(&nameIds).get();
}
return nullptr;
}
SearchNode* SearchIndex::getNode(const std::string& fullName) const
{
std::deque<Id> nameIds = m_dictionary.getWordIdsConst(fullName, DELIMITER);
if (nameIds.size())
{
return m_root.getNodeRecursive(&nameIds).get();
}
return nullptr;
}
SearchNode* SearchIndex::getNode(const SearchNode* searchNode) const
{
if(searchNode == nullptr)
{
return nullptr;
}
std::deque<Id> nameIds = searchNode->getNameIdsRecursive();
if (nameIds.size())
{
return m_root.getNodeRecursive(&nameIds).get();
}
return nullptr;
}
void SearchIndex::removeNode(SearchNode* searchNode)
{
if(searchNode == nullptr)
{
return;
}
SearchNode* parent = searchNode->getParent();
if (!parent)
{
LOG_ERROR_STREAM(<< "SearchNode to be removed has no parent: " << searchNode->getFullName());
return;
}
parent->removeSearchNode(searchNode);
}
bool SearchIndex::removeNodeIfUnreferencedRecursive(SearchNode* searchNode)
{
if(searchNode == nullptr)
{
return false;
}
if (!searchNode->hasTokenIdsRecursive())
{
SearchNode* parent = searchNode->getParent();
removeNode(searchNode);
if (parent && parent != &m_root)
bool matchingEdgeFound = false;
// has edge starting with c?
for (size_t i = 0; i < currentNode->edges.size(); i++)
{
removeNodeIfUnreferencedRecursive(parent);
Edge* currentEdge = currentNode->edges[i];
const std::string& edgeString = currentEdge->s;
int matchCount = 0;
for (size_t j = 0; j < edgeString.size() && j < remaining.size(); j++)
{
if (edgeString[j] != remaining[j])
{
break;
}
matchCount++;
}
if (matchCount != 0)
{
remaining = remaining.substr(matchCount);
if (matchCount < edgeString.size())
{
// split current edge
std::shared_ptr<Node> n = std::make_shared<Node>();
m_nodes.push_back(n);
std::shared_ptr<Edge> e = std::make_shared<Edge>();
m_edges.push_back(e);
n->edges.push_back(e.get());
e->s = edgeString.substr(matchCount);
e->target = currentEdge->target;
currentEdge->s = edgeString.substr(0, matchCount);
currentEdge->target = n.get();
}
currentNode = currentEdge->target;
matchingEdgeFound = true;
break;
}
}
return true;
if (!matchingEdgeFound)
{
std::shared_ptr<Node> n = std::make_shared<Node>();
m_nodes.push_back(n);
std::shared_ptr<Edge> e = std::make_shared<Edge>();
m_edges.push_back(e);
e->s = remaining;
e->target = n.get();
currentNode->edges.push_back(e.get());
currentNode = n.get();
remaining = "";
}
}
return false;
currentNode->elementIds.insert(id);
}
void SearchIndex::addTokenId(SearchNode* node, Id tokenId)
void SearchIndex::finishSetup()
{
if(node == nullptr)
for (size_t i = 0; i < m_root->edges.size(); i++)
{
LOG_ERROR("Node points to nullptr: Can't add tokenid");
return;
populateEdgeGate(m_root->edges[i]);
}
node->addTokenId(tokenId);
m_tokenIds.emplace(tokenId, node);
}
NameHierarchy SearchIndex::getNameHierarchyForTokenId(Id tokenId) const
void SearchIndex::clear()
{
std::map<Id, SearchNode*>::const_iterator it = m_tokenIds.find(tokenId);
m_nodes.clear();
m_edges.clear();
if (it != m_tokenIds.end())
std::shared_ptr<Node> n = std::make_shared<Node>();
m_nodes.push_back(n);
m_root = n.get();
}
std::vector<SearchResult> SearchIndex::search(const std::string& query, size_t maxResultCount) const
{
std::string lowerCaseQuery = "";
for (size_t i = 0; i < query.size(); i++)
{
SearchNode* node = it->second;
return node->getNameHierarchy();
lowerCaseQuery += tolower(query[i]);
}
return NameHierarchy();
Path startPath;
startPath.node = m_root;
std::vector<Path> paths = search(startPath, lowerCaseQuery);
// scoring paths
std::vector<std::pair<float, Path>> scoredPaths;
for (size_t i = 0; i < paths.size(); i++)
{
const std::vector<size_t>& currentIndices = paths[i].indices;
int gapCount = 0;
for (size_t j = 1; j < currentIndices.size(); j++)
{
if (currentIndices[j] - currentIndices[j-1] > 1)
{
gapCount++;
}
}
float score = (1.0f / (gapCount + 1)) + (1.0f / (paths[i].text.size() + 1));
scoredPaths.push_back(std::make_pair(score, paths[i]));
}
// sorting paths
std::sort(scoredPaths.begin(), scoredPaths.end(), [](
std::pair<float, Path> a,
std::pair<float, Path> b)
{
return b.first < a.first;
}
);
// preparing results
std::vector<SearchResult> searchResults;
for (size_t i = 0; i < scoredPaths.size() && (maxResultCount == 0 || searchResults.size() < maxResultCount); i++)
{
std::vector<Path> currentPaths;
currentPaths.push_back(scoredPaths[i].second);
while (currentPaths.size() > 0)
{
std::vector<Path> nextPaths;
for (size_t j = 0; j < currentPaths.size(); j++)
{
Path& currentPath = currentPaths[j];
if (currentPath.node->elementIds.size() > 0 && (maxResultCount == 0 || searchResults.size() < maxResultCount))
{
SearchResult result;
result.elementIds = currentPath.node->elementIds;
result.indices = currentPath.indices;
result.text = currentPath.text;
searchResults.push_back(result);
}
for (size_t k = 0; k < currentPath.node->edges.size(); k++)
{
Path nextPath;
nextPath.indices = currentPath.indices;
nextPath.node = currentPath.node->edges[k]->target;
nextPath.text = currentPath.text + currentPath.node->edges[k]->s;
nextPaths.push_back(nextPath);
}
}
currentPaths = nextPaths;
if (!(maxResultCount == 0 || searchResults.size() < maxResultCount))
{
break;
}
}
}
return searchResults;
}
SearchResults SearchIndex::runFuzzySearch(const std::string& query) const
void SearchIndex::populateEdgeGate(Edge* e)
{
return m_root.runFuzzySearch(query);
Node* target = e->target;
for (size_t i = 0; i < target->edges.size(); i++)
{
Edge* targetEdge = target->edges[i];
populateEdgeGate(targetEdge);
utility::append(e->gate, targetEdge->gate);
}
for (size_t i = 0; i < e->s.size(); i++)
{
e->gate.insert(tolower(e->s[i]));
}
}
SearchResults SearchIndex::runFuzzySearchCached(const std::string& query, const SearchResults& searchResults) const
std::vector<SearchIndex::Path> SearchIndex::search(const Path& path, const std::string& remainingQuery) const
{
return m_root.runFuzzySearchCached(query, searchResults);
}
std::vector<Path> results;
std::vector<SearchMatch> SearchIndex::runFuzzySearchAndGetMatches(const std::string& query) const
{
return getMatches(runFuzzySearch(query), query);
}
if (remainingQuery.size() == 0)
{
results.push_back(path);
}
else
{
for (size_t i = 0; i < path.node->edges.size(); i++)
{
const Edge* currentEdge = path.node->edges[i];
const std::string SearchIndex::DELIMITER = "::";
// test if s passes the edge's gate.
bool passesGate = true;
for (size_t j = 0; j < remainingQuery.size(); j++)
{
if (currentEdge->gate.find(tolower(remainingQuery[j])) == currentEdge->gate.end())
{
passesGate = false;
break;
}
}
std::ostream& operator<<(std::ostream& ostream, const SearchIndex& index)
{
ostream << "SearchIndex:\n";
ostream << &index.m_root;
return ostream;
if (passesGate)
{
// consume characters for edge
const std::string& edgeString = currentEdge->s;
std::vector<size_t> currentFoundIds = path.indices;
std::string currentRemainingQuery = remainingQuery;
for (size_t j = 0; j < edgeString.size() && currentRemainingQuery.size() > 0; j++)
{
if (currentRemainingQuery[0] == tolower(edgeString[j]))
{
currentFoundIds.push_back(path.text.size() + j);
currentRemainingQuery = currentRemainingQuery.substr(1);
}
}
Path currentPath;
currentPath.node = currentEdge->target;
currentPath.indices = currentFoundIds;
currentPath.text = path.text + edgeString;
utility::append(results, search(currentPath, currentRemainingQuery));
}
}
}
return results;
}