Skip to content

Commit f90876a

Browse files
committed
feat: levenshtein_distance() function (#511)
1 parent 529ab1f commit f90876a

File tree

3 files changed

+49
-0
lines changed

3 files changed

+49
-0
lines changed

libutil/include/libutil/Strings.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ void write_integer(
130130
*/
131131
[[nodiscard]] auto words_view(string_view text);
132132

133+
/** Computes the Levenshtein distance between the two strings. */
134+
[[nodiscard, gnu::const]] auto levenshtein_distance(
135+
string_view first, string_view second) -> size_t;
136+
133137
/// @}
134138

135139
/*

libutil/src/Strings.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <ranges>
2424
#include <string>
2525
#include <string_view>
26+
#include <vector>
2627

2728
namespace {
2829

@@ -150,4 +151,31 @@ auto split_at_first_space_or_newline(const string_view input) -> StringViewPair
150151
};
151152
}
152153

154+
auto levenshtein_distance(
155+
const string_view first, const string_view second) -> size_t
156+
{
157+
const auto size_a = first.size();
158+
const auto size_b = second.size();
159+
160+
auto distances = std::views::iota(0uz, size_b + 1uz)
161+
| std::ranges::to<std::vector>();
162+
163+
for (auto i = 0uz; i < size_a; ++i) {
164+
auto prevDist = 0uz;
165+
166+
for (auto j = 0uz; j < size_b; ++j) {
167+
const auto next = distances.at(j + 1uz);
168+
169+
const auto dist = std::exchange(prevDist, next)
170+
+ (first.at(i) == second.at(j) ? 0uz : 1uz);
171+
172+
distances.at(j + 1uz) = std::min({ dist,
173+
distances.at(j) + 1uz,
174+
next + 1uz });
175+
}
176+
}
177+
178+
return distances.at(size_b);
179+
}
180+
153181
} // namespace util::strings

tests/unit/libutil/Strings.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,20 @@ TEST_CASE("Strings - words_view()", TAGS)
219219
REQUIRE(words.back() == "456");
220220
}
221221
}
222+
223+
TEST_CASE("Strings - Levenshtein distance", TAGS)
224+
{
225+
using util::strings::levenshtein_distance;
226+
227+
REQUIRE(
228+
levenshtein_distance("kitten", "sitting") == 3uz);
229+
230+
REQUIRE(
231+
levenshtein_distance("corporate", "cooperation") == 5uz);
232+
233+
REQUIRE(
234+
levenshtein_distance("123", { }) == 0uz);
235+
236+
REQUIRE(
237+
levenshtein_distance({ }, { }) == 0uz);
238+
}

0 commit comments

Comments
 (0)