Skip to content

Commit 95cb6b6

Browse files
committed
Common: offer suggested on correct spelling
Signed-off-by: Felix Schlepper <felix.schlepper@cern.ch>
1 parent 8dbf758 commit 95cb6b6

3 files changed

Lines changed: 117 additions & 2 deletions

File tree

Common/Utils/include/CommonUtils/ConfigurableParamHelper.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,56 @@
1616

1717
#include "CommonUtils/ConfigurableParam.h"
1818

19+
#include <algorithm>
1920
#include <memory>
21+
#include <numeric>
22+
#include <string_view>
2023
#include <TClass.h>
2124
#include <TFile.h>
2225
#include <TDataMember.h>
2326
#include <type_traits>
2427
#include <typeinfo>
28+
#include <utility>
2529

2630
namespace o2::conf
2731
{
2832

2933
// ----------------------------------------------------------------
3034

35+
inline std::size_t damerauLevenshteinDistance(std::string_view a, std::string_view b)
36+
{
37+
const std::size_t n = a.size();
38+
const std::size_t m = b.size();
39+
if (n == 0) {
40+
return m;
41+
}
42+
if (m == 0) {
43+
return n;
44+
}
45+
std::vector<std::size_t> prev(m + 1), curr(m + 1), prev2(m + 1);
46+
std::iota(prev.begin(), prev.end(), 0);
47+
for (std::size_t i = 1; i <= n; ++i) {
48+
curr[0] = i;
49+
for (std::size_t j = 1; j <= m; ++j) {
50+
std::size_t cost = (a[i - 1] == b[j - 1]) ? 0 : 1;
51+
curr[j] = std::min({
52+
prev[j] + 1,
53+
curr[j - 1] + 1,
54+
prev[j - 1] + cost});
55+
if (i > 1 && j > 1 && a[i - 1] == b[j - 2] &&
56+
a[i - 2] == b[j - 1]) {
57+
curr[j] = std::min(curr[j], prev2[j - 2] + 1);
58+
}
59+
}
60+
prev2 = std::move(prev);
61+
prev = std::move(curr);
62+
curr.assign(m + 1, 0);
63+
}
64+
return prev[m];
65+
}
66+
67+
// ----------------------------------------------------------------
68+
3169
// Utility structure for passing around ConfigurableParam data member info
3270
// (where value is the string representation)
3371
struct ParamDataMember {

Common/Utils/src/ConfigurableParam.cxx

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include "CommonUtils/ConfigurableParam.h"
1515
#include <cstddef>
16+
#include "CommonUtils/ConfigurableParamHelper.h"
1617
#include "CommonUtils/StringUtils.h"
1718
#include "CommonUtils/KeyValParam.h"
1819
#include "CommonUtils/ConfigurableParamReaders.h"
@@ -433,6 +434,60 @@ const ContainerHandler* getContainerHandler(const std::type_info& type)
433434
return iter == handlers.end() ? nullptr : &iter->second;
434435
}
435436

437+
std::pair<std::string_view, std::string_view> splitConfigurableParamKey(std::string_view key)
438+
{
439+
const auto separator = key.find('.');
440+
if (separator == std::string_view::npos) {
441+
return {key, {}};
442+
}
443+
return {key.substr(0, separator), key.substr(separator + 1)};
444+
}
445+
446+
std::string findClosestConfigurableParamKey(const std::string& requestedKey,
447+
const std::map<std::string, std::pair<std::type_info const&, void*>>& storageMap)
448+
{
449+
if (storageMap.empty()) {
450+
return {};
451+
}
452+
453+
const auto [requestedMainKey, requestedSubKey] = splitConfigurableParamKey(requestedKey);
454+
bool mainKeyExists = false;
455+
for (const auto& entry : storageMap) {
456+
const auto mainKey = splitConfigurableParamKey(entry.first).first;
457+
if (mainKey == requestedMainKey) {
458+
mainKeyExists = true;
459+
break;
460+
}
461+
}
462+
463+
std::string closest;
464+
std::size_t closestDistance = std::numeric_limits<std::size_t>::max();
465+
for (const auto& entry : storageMap) {
466+
const auto& key = entry.first;
467+
const auto [mainKey, subKey] = splitConfigurableParamKey(key);
468+
if (mainKeyExists && mainKey != requestedMainKey) {
469+
continue;
470+
}
471+
const auto distance = mainKeyExists ? damerauLevenshteinDistance(requestedSubKey, subKey) : damerauLevenshteinDistance(requestedKey, key);
472+
if (distance < closestDistance || (distance == closestDistance && (closest.empty() || key < closest))) {
473+
closest = key;
474+
closestDistance = distance;
475+
}
476+
}
477+
return closest;
478+
}
479+
480+
std::string formatUnknownConfigurableParamKeyMessage(const std::string& prefix, const std::string& key,
481+
const std::map<std::string, std::pair<std::type_info const&, void*>>& storageMap)
482+
{
483+
std::string message = prefix + key;
484+
auto closest = findClosestConfigurableParamKey(key, storageMap);
485+
if (!closest.empty()) {
486+
message += ". Did you mean '" + closest + "'?";
487+
}
488+
return message;
489+
}
490+
436491
} // namespace
437492

438493
// ------------------------------------------------------------------
@@ -978,10 +1033,10 @@ void ConfigurableParam::setValues(std::vector<std::pair<std::string, std::string
9781033

9791034
if (!keyInTree(sPtree, key)) {
9801035
if (nonFatal) {
981-
LOG(warn) << "Ignoring non-existent ConfigurableParam key: " << key;
1036+
LOG(warn) << formatUnknownConfigurableParamKeyMessage("Ignoring non-existent ConfigurableParam key: ", key, *sKeyToStorageMap);
9821037
continue;
9831038
}
984-
LOG(fatal) << "Inexistent ConfigurableParam key: " << key;
1039+
LOG(fatal) << formatUnknownConfigurableParamKeyMessage("Inexistent ConfigurableParam key: ", key, *sKeyToStorageMap);
9851040
}
9861041

9871042
auto iter = sKeyToStorageMap->find(key);

Common/Utils/test/testConfigurableParam.cxx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include <boost/test/unit_test.hpp>
1919
#include <boost/property_tree/ptree.hpp>
20+
#include <cstdlib>
2021
#include <filesystem>
2122

2223
#include "CommonUtils/ConfigurableParamTest.h"
@@ -199,6 +200,27 @@ BOOST_AUTO_TEST_CASE(ConfigurableParam_ContainerParserMap)
199200
BOOST_CHECK_EQUAL(m["beta"], 0.3);
200201
}
201202

203+
BOOST_AUTO_TEST_CASE(ConfigurableParam_DamerauLevenshteinDistance)
204+
{
205+
BOOST_CHECK_EQUAL(damerauLevenshteinDistance("TestParam.iValue", "TestParam.iValue"), 0);
206+
BOOST_CHECK_EQUAL(damerauLevenshteinDistance("TestParam.iValu", "TestParam.iValue"), 1);
207+
BOOST_CHECK_EQUAL(damerauLevenshteinDistance("TestParam.jValue", "TestParam.iValue"), 1);
208+
BOOST_CHECK_EQUAL(damerauLevenshteinDistance("TestParam.iVaule", "TestParam.iValue"), 1);
209+
}
210+
211+
BOOST_AUTO_TEST_CASE(ConfigurableParam_UnknownKeySuggestionsNonFatal)
212+
{
213+
setenv("ALICEO2_CONFIGURABLEPARAM_WRONGKEYISNONFATAL", "1", 1);
214+
ConfigurableParam::setValue("TestParam.iValue", "222");
215+
ConfigurableParam::setValue("TestParam.caValue[1]", "33");
216+
ConfigurableParam::setValues({{"TestParam.iValu", "777"},
217+
{"TstParam.iValue", "888"},
218+
{"TestParam.caValue[", "999"}});
219+
BOOST_CHECK_EQUAL(TestParam::Instance().iValue, 222);
220+
BOOST_CHECK_EQUAL(TestParam::Instance().caValue[1], 33);
221+
unsetenv("ALICEO2_CONFIGURABLEPARAM_WRONGKEYISNONFATAL");
222+
}
223+
202224
BOOST_AUTO_TEST_CASE(ConfigurableParam_Container_FileIO_ROOT)
203225
{
204226
// test for root file serialization

0 commit comments

Comments
 (0)