319 lines
8.8 KiB
C++
319 lines
8.8 KiB
C++
// Copyright (c) 2020-present, Roland Munguia & Tristan Florian Bouchard.
|
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
|
|
|
#pragma once
|
|
|
|
#ifndef CSYS_HEADER_ONLY
|
|
|
|
#include "csys/autocomplete.h"
|
|
|
|
#endif
|
|
|
|
namespace csys
|
|
{
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Constructor/Destructors ////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
CSYS_INLINE AutoComplete::~AutoComplete()
|
|
{
|
|
delete m_Root;
|
|
}
|
|
|
|
CSYS_INLINE AutoComplete::AutoComplete(const AutoComplete &tree) : m_Size(tree.m_Size), m_Count(tree.m_Count)
|
|
{
|
|
DeepClone(tree.m_Root, m_Root);
|
|
}
|
|
|
|
CSYS_INLINE AutoComplete &AutoComplete::operator=(const AutoComplete &rhs)
|
|
{
|
|
// Prevent self assignment.
|
|
if (&rhs == this) return *this;
|
|
|
|
// Clean.
|
|
delete m_Root;
|
|
|
|
// Copy from source tree.
|
|
DeepClone(rhs.m_Root, m_Root);
|
|
m_Size = rhs.m_Size;
|
|
m_Count = rhs.m_Count;
|
|
|
|
return *this;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Public methods /////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
CSYS_INLINE size_t AutoComplete::Size() const
|
|
{
|
|
return m_Size;
|
|
}
|
|
|
|
CSYS_INLINE size_t AutoComplete::Count() const
|
|
{
|
|
return m_Count;
|
|
}
|
|
|
|
CSYS_INLINE bool AutoComplete::Search(const char *word)
|
|
{
|
|
ACNode *ptr = m_Root;
|
|
|
|
// Traverse tree in look for the given string.
|
|
while (ptr)
|
|
{
|
|
if (*word < ptr->m_Data)
|
|
{
|
|
ptr = ptr->m_Less;
|
|
} else if (*word == ptr->m_Data)
|
|
{
|
|
// Word was found.
|
|
if (*(word + 1) == '\0' && ptr->m_IsWord)
|
|
return true;
|
|
|
|
ptr = ptr->m_Equal;
|
|
++word;
|
|
} else
|
|
{
|
|
ptr = ptr->m_Greater;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CSYS_INLINE void AutoComplete::Insert(const char *word)
|
|
{
|
|
ACNode **ptr = &m_Root;
|
|
++m_Count;
|
|
|
|
while (*word != '\0')
|
|
{
|
|
// Insert char into tree.
|
|
if (*ptr == nullptr)
|
|
{
|
|
*ptr = new ACNode(*word);
|
|
++m_Size;
|
|
}
|
|
|
|
// Traverse tree.
|
|
if (*word < (*ptr)->m_Data)
|
|
{
|
|
ptr = &(*ptr)->m_Less;
|
|
} else if (*word == (*ptr)->m_Data)
|
|
{
|
|
// String is already in tree, therefore only mark as word.
|
|
if (*(word + 1) == '\0')
|
|
{
|
|
if ((*ptr)->m_IsWord)
|
|
--m_Count;
|
|
|
|
(*ptr)->m_IsWord = true;
|
|
}
|
|
|
|
// Advance.
|
|
ptr = &(*ptr)->m_Equal;
|
|
++word;
|
|
} else
|
|
{
|
|
ptr = &(*ptr)->m_Greater;
|
|
}
|
|
}
|
|
}
|
|
|
|
CSYS_INLINE void AutoComplete::Insert(const std::string &word)
|
|
{
|
|
Insert(word.c_str());
|
|
}
|
|
|
|
CSYS_INLINE void AutoComplete::Remove(const std::string &word)
|
|
{
|
|
RemoveAux(m_Root, word.c_str());
|
|
}
|
|
|
|
CSYS_INLINE void AutoComplete::Suggestions(const char *prefix, std::vector<std::string> &ac_options)
|
|
{
|
|
ACNode *ptr = m_Root;
|
|
auto temp = prefix;
|
|
|
|
// Traverse tree and check if prefix exists.
|
|
while (ptr)
|
|
{
|
|
if (*prefix < ptr->m_Data)
|
|
{
|
|
ptr = ptr->m_Less;
|
|
} else if (*prefix == ptr->m_Data)
|
|
{
|
|
// Prefix exists in tree.
|
|
if (*(prefix + 1) == '\0')
|
|
break;
|
|
|
|
ptr = ptr->m_Equal;
|
|
++prefix;
|
|
} else
|
|
{
|
|
ptr = ptr->m_Greater;
|
|
}
|
|
}
|
|
|
|
// Already a word. (No need to auto complete).
|
|
if (ptr && ptr->m_IsWord) return;
|
|
|
|
// Prefix is not in tree.
|
|
if (!ptr) return;
|
|
|
|
// Retrieve auto complete options.
|
|
SuggestionsAux(ptr->m_Equal, ac_options, temp);
|
|
}
|
|
|
|
CSYS_INLINE std::string AutoComplete::Suggestions(const std::string &prefix, r_sVector &ac_options)
|
|
{
|
|
std::string temp = prefix;
|
|
Suggestions(temp, ac_options, true);
|
|
return temp;
|
|
}
|
|
|
|
CSYS_INLINE void AutoComplete::Suggestions(std::string &prefix, r_sVector ac_options, bool partial_complete)
|
|
{
|
|
ACNode *ptr = m_Root;
|
|
const char *temp = prefix.data();
|
|
size_t prefix_end = prefix.size();
|
|
|
|
// Traverse tree and check if prefix exists.
|
|
while (ptr)
|
|
{
|
|
if (*temp < ptr->m_Data)
|
|
{
|
|
ptr = ptr->m_Less;
|
|
} else if (*temp == ptr->m_Data)
|
|
{
|
|
// Prefix exists in tree.
|
|
if (*(temp + 1) == '\0')
|
|
{
|
|
if (partial_complete)
|
|
{
|
|
ACNode *pc_ptr = ptr->m_Equal;
|
|
|
|
// Get partially completed string.
|
|
while (pc_ptr)
|
|
{
|
|
if (pc_ptr->m_Equal && !pc_ptr->m_Less && !pc_ptr->m_Greater)
|
|
prefix.push_back(pc_ptr->m_Data);
|
|
else
|
|
break;
|
|
|
|
pc_ptr = pc_ptr->m_Equal;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
ptr = ptr->m_Equal;
|
|
++temp;
|
|
} else
|
|
{
|
|
ptr = ptr->m_Greater;
|
|
}
|
|
}
|
|
|
|
// Already a word. (No need to auto complete).
|
|
if (ptr && ptr->m_IsWord) return;
|
|
|
|
// Prefix is not in tree.
|
|
if (!ptr) return;
|
|
|
|
// Retrieve auto complete options.
|
|
SuggestionsAux(ptr->m_Equal, ac_options, prefix.substr(0, prefix_end));
|
|
}
|
|
|
|
CSYS_INLINE std::unique_ptr<AutoComplete::sVector> AutoComplete::Suggestions(const char *prefix)
|
|
{
|
|
auto temp = std::make_unique<sVector>();
|
|
Suggestions(prefix, *temp);
|
|
return temp;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Private methods ////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
CSYS_INLINE void AutoComplete::SuggestionsAux(AutoComplete::ACNode *root, r_sVector ac_options, std::string buffer)
|
|
{
|
|
if (!root) return;
|
|
|
|
// Continue looking in left branch.
|
|
if (root->m_Less) SuggestionsAux(root->m_Less, ac_options, buffer);
|
|
|
|
// Word was found, push into autocomplete options.
|
|
if (root->m_IsWord)
|
|
{
|
|
ac_options.push_back(buffer.append(1, root->m_Data));
|
|
buffer.pop_back();
|
|
}
|
|
|
|
// Continue in middle branch, and push character.
|
|
if (root->m_Equal)
|
|
{
|
|
SuggestionsAux(root->m_Equal, ac_options, buffer.append(1, root->m_Data));
|
|
buffer.pop_back();
|
|
}
|
|
|
|
// Continue looking in right branch.
|
|
if (root->m_Greater)
|
|
{
|
|
SuggestionsAux(root->m_Greater, ac_options, buffer);
|
|
}
|
|
}
|
|
|
|
CSYS_INLINE bool AutoComplete::RemoveAux(AutoComplete::ACNode *root, const char *word)
|
|
{
|
|
if (!root) return false;
|
|
|
|
// String is in TST.
|
|
if (*(word + 1) == '\0' && root->m_Data == *word)
|
|
{
|
|
// Un-mark word node.
|
|
if (root->m_IsWord)
|
|
{
|
|
root->m_IsWord = false;
|
|
return (!root->m_Equal && !root->m_Less && !root->m_Greater);
|
|
}
|
|
// String is a prefix.
|
|
else
|
|
return false;
|
|
} else
|
|
{
|
|
// String is a prefix.
|
|
if (*word < root->m_Data)
|
|
RemoveAux(root->m_Less, word);
|
|
else if (*word > root->m_Data)
|
|
RemoveAux(root->m_Greater, word);
|
|
|
|
// String is in TST.
|
|
else if (*word == root->m_Data)
|
|
{
|
|
// Char is unique.
|
|
if (RemoveAux(root->m_Equal, word + 1))
|
|
{
|
|
delete root->m_Equal;
|
|
root->m_Equal = nullptr;
|
|
return !root->m_IsWord && (!root->m_Equal && !root->m_Less && !root->m_Greater);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CSYS_INLINE void AutoComplete::DeepClone(AutoComplete::ACNode *src, AutoComplete::ACNode *&dest)
|
|
{
|
|
if (!src) return;
|
|
|
|
dest = new ACNode(*src);
|
|
DeepClone(src->m_Less, dest->m_Less);
|
|
DeepClone(src->m_Equal, dest->m_Equal);
|
|
DeepClone(src->m_Greater, dest->m_Greater);
|
|
}
|
|
}
|