Skip to content
38 changes: 22 additions & 16 deletions components/core/src/clp/DictionaryReader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ class DictionaryReader {
*/
std::string const& get_value(DictionaryIdType id) const;
/**
* Gets the entry exactly matching the given search string
* Gets the entries matching the given search string
* @param search_string
* @param ignore_case
* @return nullptr if an exact match is not found, the entry otherwise
* @return a vector of matching entries, or an empty vector if no entry matches.
*/
EntryType const*
std::vector<EntryType const*>
get_entry_matching_value(std::string const& search_string, bool ignore_case) const;
/**
* Gets the entries that match a given wildcard string
Expand Down Expand Up @@ -233,26 +233,32 @@ std::string const& DictionaryReader<DictionaryIdType, EntryType>::get_value(Dict
}

template <typename DictionaryIdType, typename EntryType>
EntryType const* DictionaryReader<DictionaryIdType, EntryType>::get_entry_matching_value(
std::vector<EntryType const*>
DictionaryReader<DictionaryIdType, EntryType>::get_entry_matching_value(
Comment thread
aestriplex marked this conversation as resolved.
Resolved
std::string const& search_string,
bool ignore_case
) const {
if (false == ignore_case) {
for (auto const& entry : m_entries) {
if (entry.get_value() == search_string) {
return &entry;
}
}
} else {
auto const& search_string_uppercase = boost::algorithm::to_upper_copy(search_string);
for (auto const& entry : m_entries) {
if (boost::algorithm::to_upper_copy(entry.get_value()) == search_string_uppercase) {
return &entry;
}
// In case-sensitive match, there can be only one matched entry.
if (auto const it = std::ranges::find_if(
m_entries,
[&](auto const& entry) { return entry.get_value() == search_string; }
);
m_entries.cend() != it)
{
return {&(*it)};
}
return {};
}

return nullptr;
std::vector<EntryType const*> entries;
auto const search_string_uppercase = boost::algorithm::to_upper_copy(search_string);
for (auto const& entry : m_entries) {
if (boost::algorithm::to_upper_copy(entry.get_value()) == search_string_uppercase) {
entries.push_back(&entry);
}
}
return entries;
}

template <typename DictionaryIdType, typename EntryType>
Expand Down
23 changes: 19 additions & 4 deletions components/core/src/clp/EncodedVariableInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <cassert>
#include <cmath>
#include <unordered_set>

#include <string_utils/string_utils.hpp>

Expand Down Expand Up @@ -386,15 +387,29 @@ bool EncodedVariableInterpreter::encode_and_search_dictionary(
LogTypeDictionaryEntry::add_float_var(logtype);
sub_query.add_non_dict_var(encoded_var);
} else {
auto entry = var_dict.get_entry_matching_value(var_str, ignore_case);
if (nullptr == entry) {
auto const entries = var_dict.get_entry_matching_value(var_str, ignore_case);
if (entries.empty()) {
// Not in dictionary
return false;
}
encoded_var = encode_var_dict_id(entry->get_id());

LogTypeDictionaryEntry::add_dict_var(logtype);
sub_query.add_dict_var(encoded_var, entry);

if (entries.size() == 1) {
auto const* entry = entries.at(0);
sub_query.add_dict_var(encode_var_dict_id(entry->get_id()), entry);
return true;
}

std::unordered_set<clp::VariableDictionaryEntry const*> const entries_set{
entries.cbegin(),
entries.cend()
};
std::unordered_set<encoded_variable_t> encoded_vars;
Comment thread
aestriplex marked this conversation as resolved.
Resolved
Comment thread
aestriplex marked this conversation as resolved.
Resolved
for (auto const* entry : entries) {
encoded_vars.emplace(encode_var_dict_id(entry->get_id()));
}
Comment on lines +404 to +412

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::unordered_set<encoded_variable_t> encoded_vars;
std::unordered_set<clp::VariableDictionaryEntry const*> const entries_set(
entries.begin(),
entries.end()
);
for (auto const entry : entries) {
encoded_vars.insert(encode_var_dict_id(entry->get_id()));
}
std::unordered_set<clp::VariableDictionaryEntry const*> const entries_set{
entries.cbegin(),
entries.cend()
};
std::unordered_set<encoded_variable_t> encoded_vars;
for (auto const* entry : entries) {
encoded_vars.emplace(encode_var_dict_id(entry->get_id()));
}
sub_query.add_imprecise_dict_var(encoded_vars, entries_set);

Some coding style related comments. If we choose to go with the design as discussed in another comment we might not need to apply this change in the end, but I still leave it here just to get u on board to our C++ coding guidelines for future development :D

  • We should order entries_set before encoded_vars to make the declaration of encoded_vars as close to its first use as possible
  • We use {} instead of () for explicitly typed variable initialization. It's a bit tricky to use {} in std containers sometimes (e.g., using {1, 2} to initialize a vector would not be equivalent to (1, 2)), but in this case the inputs are iterators so we should use {}.
  • Let's use the const version of iterators whenever possible for const correctness.
  • When using auto to declare a variable, we should add * for ptrs and & for references. This is actually checked by our clang-tidy rules, which is to improve the readability.
  • In most cases we should use emplace rather than insert (same as emplace_back rather than push_back) for in-place object construct. Since the underlying type is a fundamental type, so in this case using insert would be fine and there's no rule to not strictly enforce u to use emplace. If u don't want to think about emplace vs. insert everytime, u could always use emplace.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @LinZhihao-723 for this feedback. I implemented your suggestions both in clp and in clp_s namespaces

sub_query.add_imprecise_dict_var(encoded_vars, entries_set);
Comment on lines +404 to +413

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about adding a new signature for sub_query.add_imprecise_dict_var that takes in a vector of entires as argument?. So we can replace this piece of code witha a single call to the new signature @LinZhihao-723

Note this won't solve the code duplication problem, because CLP-S duplicates the entire Query class

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of returning a vector, can we directly return an unordered set?

}

return true;
Expand Down
38 changes: 22 additions & 16 deletions components/core/src/clp_s/DictionaryReader.hpp

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gibber9809 The change in CLP-S looks reasonable to me, but can you take another look?

Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ class DictionaryReader {
std::string const& get_value(DictionaryIdType id) const;

/**
* Gets the entry exactly matching the given search string
* Gets the entries matching the given search string
* @param search_string
* @param ignore_case
* @return nullptr if an exact match is not found, the entry otherwise
* @return a vector of matching entries, or an empty vector if no entry matches.
*/
EntryType const*
std::vector<EntryType const*>
get_entry_matching_value(std::string const& search_string, bool ignore_case) const;

/**
Expand Down Expand Up @@ -156,26 +156,32 @@ std::string const& DictionaryReader<DictionaryIdType, EntryType>::get_value(Dict
}

template <typename DictionaryIdType, typename EntryType>
EntryType const* DictionaryReader<DictionaryIdType, EntryType>::get_entry_matching_value(
std::vector<EntryType const*>
DictionaryReader<DictionaryIdType, EntryType>::get_entry_matching_value(
std::string const& search_string,
bool ignore_case
) const {
if (false == ignore_case) {
for (auto const& entry : m_entries) {
if (entry.get_value() == search_string) {
return &entry;
}
}
} else {
auto const& search_string_uppercase = boost::algorithm::to_upper_copy(search_string);
for (auto const& entry : m_entries) {
if (boost::algorithm::to_upper_copy(entry.get_value()) == search_string_uppercase) {
return &entry;
}
// In case-sensitive match, there can be only one matched entry.
if (auto const it = std::ranges::find_if(
m_entries,
[&](auto const& entry) { return entry.get_value() == search_string; }
);
m_entries.cend() != it)
{
return {&(*it)};
}
return {};
}

return nullptr;
std::vector<EntryType const*> entries;
auto const search_string_uppercase = boost::algorithm::to_upper_copy(search_string);
for (auto const& entry : m_entries) {
if (boost::algorithm::to_upper_copy(entry.get_value()) == search_string_uppercase) {
entries.push_back(&entry);
}
}
return entries;
}

template <typename DictionaryIdType, typename EntryType>
Expand Down
4 changes: 2 additions & 2 deletions components/core/src/clp_s/search/Output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -932,12 +932,12 @@ void Output::populate_string_queries(std::shared_ptr<Expression> const& expr) {
}
}

auto const* entry = m_var_dict->get_entry_matching_value(
auto const entries = m_var_dict->get_entry_matching_value(
unescaped_query_string,
m_ignore_case
);

if (entry != nullptr) {
for (auto const& entry : entries) {
matching_vars.insert(entry->get_id());
}
} else if (EncodedVariableInterpreter::
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <cassert>
#include <cmath>
#include <unordered_set>

#include <spdlog/spdlog.h>

Expand Down Expand Up @@ -34,15 +35,29 @@ bool EncodedVariableInterpreter::encode_and_search_dictionary(
LogTypeDictionaryEntry::add_double_var(logtype);
sub_query.add_non_dict_var(encoded_var);
} else {
auto entry = var_dict.get_entry_matching_value(var_str, ignore_case);
if (nullptr == entry) {
auto const entries = var_dict.get_entry_matching_value(var_str, ignore_case);
if (entries.empty()) {
// Not in dictionary
return false;
}
encoded_var = VariableEncoder::encode_var_dict_id(entry->get_id());

LogTypeDictionaryEntry::add_non_double_var(logtype);
sub_query.add_dict_var(encoded_var, entry);

if (entries.size() == 1) {
auto const* entry = entries.at(0);
sub_query.add_dict_var(VariableEncoder::encode_var_dict_id(entry->get_id()), entry);
return true;
}

std::unordered_set<VariableDictionaryEntry const*> const entries_set{
entries.cbegin(),
entries.cend()
};
std::unordered_set<encoded_variable_t> encoded_vars;
for (auto const* entry : entries) {
encoded_vars.emplace(VariableEncoder::encode_var_dict_id(entry->get_id()));
}
sub_query.add_imprecise_dict_var(encoded_vars, entries_set);
}

return true;
Expand Down
41 changes: 41 additions & 0 deletions components/core/tests/test-EncodedVariableInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,47 @@ TEST_CASE("EncodedVariableInterpreter", "[EncodedVariableInterpreter]") {
));
}

SECTION("Test multiple metching values") {
char const cVarDictPath[] = "var.dict";
char const cVarSegmentIndexPath[] = "var.segindex";
Comment thread
aestriplex marked this conversation as resolved.
Resolved
vector<string> var_strs = {"python2.7.3", "Python2.7.3", "PyThOn2.7.3", "PYTHON2.7.3"};
Comment thread
aestriplex marked this conversation as resolved.
Resolved
clp::VariableDictionaryWriter var_dict_writer;
Comment thread
aestriplex marked this conversation as resolved.
Resolved

var_dict_writer.open(cVarDictPath, cVarSegmentIndexPath, cVariableDictionaryIdMax);

vector<encoded_variable_t> encoded_vars;
Comment thread
aestriplex marked this conversation as resolved.
Resolved
vector<clp::variable_dictionary_id_t> var_ids;
clp::LogTypeDictionaryEntry logtype_dict_entry;
string const msg_template = "here is a string with a dictionary var: ";
Comment thread
aestriplex marked this conversation as resolved.
Resolved

for (auto const& var_str : var_strs) {
EncodedVariableInterpreter::encode_and_add_to_dictionary(
Comment thread
aestriplex marked this conversation as resolved.
Resolved
msg_template + var_str,
logtype_dict_entry,
var_dict_writer,
encoded_vars,
var_ids
);
}
var_dict_writer.close();

clp::VariableDictionaryReader var_dict_reader;
var_dict_reader.open(cVarDictPath, cVarSegmentIndexPath);
var_dict_reader.read_new_entries();

REQUIRE(var_dict_reader.get_entry_matching_value(var_strs.at(0), true).size()
== var_strs.size());
REQUIRE(var_dict_reader.get_entry_matching_value(var_strs.at(0), false).size() == 1);

var_dict_reader.close();

// Clean-up
int retval = unlink(cVarDictPath);
REQUIRE(0 == retval);
retval = unlink(cVarSegmentIndexPath);
REQUIRE(0 == retval);
Comment thread
aestriplex marked this conversation as resolved.
Resolved
}

SECTION("Test encoding and decoding") {
string msg;

Expand Down