RapidFuzz
Loading...
Searching...
No Matches
DamerauLevenshtein_impl.hpp
1/* SPDX-License-Identifier: MIT */
2/* Copyright © 2022-present Max Bachmann */
3
4#include <cassert>
5#include <cstddef>
6#include <limits>
7#include <numeric>
8#include <rapidfuzz/details/GrowingHashmap.hpp>
9#include <rapidfuzz/details/Matrix.hpp>
10#include <rapidfuzz/details/Range.hpp>
11#include <rapidfuzz/details/common.hpp>
12#include <rapidfuzz/details/distance.hpp>
13
14namespace rapidfuzz {
15namespace detail {
16
17template <typename IntType>
18struct RowId {
19 IntType val = -1;
20 friend bool operator==(const RowId& lhs, const RowId& rhs)
21 {
22 return lhs.val == rhs.val;
23 }
24
25 friend bool operator!=(const RowId& lhs, const RowId& rhs)
26 {
27 return !(lhs == rhs);
28 }
29};
30
31/*
32 * based on the paper
33 * "Linear space string correction algorithm using the Damerau-Levenshtein distance"
34 * from Chunchun Zhao and Sartaj Sahni
35 */
36template <typename IntType, typename InputIt1, typename InputIt2>
37size_t damerau_levenshtein_distance_zhao(const Range<InputIt1>& s1, const Range<InputIt2>& s2, size_t max)
38{
39 // todo check types
40 IntType len1 = static_cast<IntType>(s1.size());
41 IntType len2 = static_cast<IntType>(s2.size());
42 IntType maxVal = static_cast<IntType>(std::max(len1, len2) + 1);
43 assert(std::numeric_limits<IntType>::max() > maxVal);
44
45 HybridGrowingHashmap<typename Range<InputIt1>::value_type, RowId<IntType>> last_row_id;
46 size_t size = s2.size() + 2;
47 assume(size != 0);
48 std::vector<IntType> FR_arr(size, maxVal);
49 std::vector<IntType> R1_arr(size, maxVal);
50 std::vector<IntType> R_arr(size);
51 R_arr[0] = maxVal;
52 std::iota(R_arr.begin() + 1, R_arr.end(), IntType(0));
53
54 IntType* R = &R_arr[1];
55 IntType* R1 = &R1_arr[1];
56 IntType* FR = &FR_arr[1];
57
58 auto iter_s1 = s1.begin();
59 for (IntType i = 1; i <= len1; i++) {
60 std::swap(R, R1);
61 IntType last_col_id = -1;
62 IntType last_i2l1 = R[0];
63 R[0] = i;
64 IntType T = maxVal;
65
66 auto iter_s2 = s2.begin();
67 for (IntType j = 1; j <= len2; j++) {
68 int64_t diag = R1[j - 1] + static_cast<IntType>(*iter_s1 != *iter_s2);
69 int64_t left = R[j - 1] + 1;
70 int64_t up = R1[j] + 1;
71 int64_t temp = std::min({diag, left, up});
72
73 if (*iter_s1 == *iter_s2) {
74 last_col_id = j; // last occurence of s1_i
75 FR[j] = R1[j - 2]; // save H_k-1,j-2
76 T = last_i2l1; // save H_i-2,l-1
77 }
78 else {
79 int64_t k = last_row_id.get(static_cast<uint64_t>(*iter_s2)).val;
80 int64_t l = last_col_id;
81
82 if ((j - l) == 1) {
83 int64_t transpose = FR[j] + (i - k);
84 temp = std::min(temp, transpose);
85 }
86 else if ((i - k) == 1) {
87 int64_t transpose = T + (j - l);
88 temp = std::min(temp, transpose);
89 }
90 }
91
92 last_i2l1 = R[j];
93 R[j] = static_cast<IntType>(temp);
94 iter_s2++;
95 }
96 last_row_id[*iter_s1].val = i;
97 iter_s1++;
98 }
99
100 size_t dist = static_cast<size_t>(R[s2.size()]);
101 return (dist <= max) ? dist : max + 1;
102}
103
104template <typename InputIt1, typename InputIt2>
105size_t damerau_levenshtein_distance(Range<InputIt1> s1, Range<InputIt2> s2, size_t max)
106{
107 size_t min_edits = abs_diff(s1.size(), s2.size());
108 if (min_edits > max) return max + 1;
109
110 /* common affix does not effect Levenshtein distance */
111 remove_common_affix(s1, s2);
112
113 size_t maxVal = std::max(s1.size(), s2.size()) + 1;
114 if (std::numeric_limits<int16_t>::max() > maxVal)
115 return damerau_levenshtein_distance_zhao<int16_t>(s1, s2, max);
116 else if (std::numeric_limits<int32_t>::max() > maxVal)
117 return damerau_levenshtein_distance_zhao<int32_t>(s1, s2, max);
118 else
119 return damerau_levenshtein_distance_zhao<int64_t>(s1, s2, max);
120}
121
122class DamerauLevenshtein
123 : public DistanceBase<DamerauLevenshtein, size_t, 0, std::numeric_limits<int64_t>::max()> {
124 friend DistanceBase<DamerauLevenshtein, size_t, 0, std::numeric_limits<int64_t>::max()>;
125 friend NormalizedMetricBase<DamerauLevenshtein>;
126
127 template <typename InputIt1, typename InputIt2>
128 static size_t maximum(const Range<InputIt1>& s1, const Range<InputIt2>& s2)
129 {
130 return std::max(s1.size(), s2.size());
131 }
132
133 template <typename InputIt1, typename InputIt2>
134 static size_t _distance(const Range<InputIt1>& s1, const Range<InputIt2>& s2, size_t score_cutoff, size_t)
135 {
136 return damerau_levenshtein_distance(s1, s2, score_cutoff);
137 }
138};
139
140} // namespace detail
141} // namespace rapidfuzz