leven.h: C++ interace: Added `string_ord<S>`: Compares strings based on levenshtein distaence, and `sim_map<T,S={}>`: an ordered map where the key is of `string_ord<S>` and the value is `T`.

Fortune for libexopt's current commit: Blessing − 吉
boxed_is_boxed_value
Avril 1 year ago
parent 1752c89056
commit 7ce1fd2ad0
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -9,6 +9,7 @@
#include <string_view>
#include <concepts>
#include <vector>
#include <map>
#include <array>
#include <span>
#include <tuple>
@ -22,6 +23,8 @@ size_t _EO(leven_diff)(const char* _EO(restrict), const char* _EO(restrict), siz
__attribute__((nonnull(1, 2)))
;
//TODO: C interface for `string_ord` and `sim_map`?
#ifdef __cplusplus
}
@ -79,17 +82,68 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
return leven_diff(str{sa}, str{sb});
}
//TODO: Add overloads for when one bound is known statically but the other is not. Would this be useful?
template<size_t M, size_t N>
constexpr decltype(auto) leven_diff(const char (&sa)[M], const char (&sb)[N]) noexcept {
if constexpr(!N) return M;
else if constexpr(!M) return N;
else if constexpr(M < N) return N - M; //XXX: Are these lines correct?
else if constexpr(N < M) return M - N; // ^
//else if constexpr(M < N) return N - M; // These lines are not correct, just fixing the sizes does not make the edit valid
//else if constexpr(N < M) return M - N; // ^
else return leven_diff<N>(std::string_view{sa}, std::string_view{sb});
}
} }
/// String that is ordered by Levenshtein distance.
///
/// Used as sorted map key for finding suggestions for possible typos. All valid strings are added as `string_ord`, and the invalid string lookup looks for its closest neighbour(s), those are presented to the user in a "did you mean ..." format with the lowest distance first and so on.
template<std::convertible_to<std::string_view> S>
struct string_ord {
using string_type = std::remove_reference_t<S>;
constexpr string_ord(string_type&& str) noexcept
: m_string(std::move(str)) {}
constexpr string_ord(string_ord const&) = default;
constexpr string_ord(string_ord &&) = default;
constexpr string_ord& operator=(string_ord const&) = default;
constexpr string_ord& operator=(string_ord &&) = default;
constexpr ~string_ord() = default;
constexpr auto difference_from(std::convertible_to<std::string_view> auto const& str) const noexcept {
return leven_diff(m_string, std::forward<decltype(str)>(str));
}
constexpr auto difference_to(std::convertible_to<std::string_view> auto const& str) const noexcept {
return leven_diff(std::forward<decltype(str)>(str), m_string);
}
constexpr friend auto operator<=>(const string_ord& a, const string_ord& b) noexcept {
auto ab = leven_diff(a.m_string, b.m_string);
auto ba = leven_diff(b.m_string, a.m_string);
return ab <=> ba;
}
constexpr operator std::string_view() const noexcept { return m_string; }
constexpr string_type& string() noexcept { return m_string; }
constexpr string_type const& string() const noexcept { return m_string; }
constexpr string_type& operator*() & noexcept { return m_string; }
constexpr string_type const& operator*() const& noexcept { return m_string; }
constexpr string_type&& operator*() && noexcept { return std::move(m_string); }
constexpr string_type const&& operator*() const&& noexcept { return std::move(m_string); }
private:
string_type m_string;
};
/// Used to store all valid command names, so when an invalid one is found, the closest matching one(s) can be suggested to the user in a "did you mean ..." format with the *lowest difference* neighbour(s) to the invalid string first.
template<typename T, std::convertible_to<std::string_view> S = std::string_view>
using sim_map = std::map<string_ord<S>, T>;
} // ::util //
}
#endif
#endif /* _LEVEN_H */

Loading…
Cancel
Save