xapian-core  2.1.0
editdistance.cc
Go to the documentation of this file.
1 
12 /* Copyright (C) 2003 Richard Boulton
13  * Copyright (C) 2007,2008,2009,2017,2019,2020,2026 Olly Betts
14  *
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 2 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program; if not, see
27  * <https://www.gnu.org/licenses/>.
28  */
29 
30 #include <config.h>
31 
32 #include "editdistance.h"
33 
34 #include "omassert.h"
35 #include "overflow.h"
36 #include "popcount.h"
37 
38 #include <algorithm>
39 #include <climits>
40 #include <cstdlib>
41 #include <cstring>
42 
43 using namespace std;
44 
45 template<class Char>
46 struct edist_seq {
47  edist_seq(const Char* ptr_, int len_) : ptr(ptr_), len(len_) { }
48  const Char* ptr;
49  int len;
50 };
51 
52 template<class Char>
53 class edist_state {
55  edist_state& operator=(const edist_state&) = delete;
56 
58  edist_state(const edist_state&) = delete;
59 
62 
63  /* Array of f(k,p) values, where f(k,p) = the largest index i such that
64  * d(i,j) = p and d(i,j) is on diagonal k.
65  * ie: f(k,p) = largest i s.t. d(i,k+i) = p
66  * Where: d(i,j) = edit distance between substrings of length i and j.
67  */
68  int* fkp;
69  int fkp_rows;
70 
71  /* Maximum possible edit distance (this is referred to as ZERO_K in
72  * the algorithm description by Berghel and Roach). */
73  int maxdist;
74 
75  int calc_index(int k, int p) const {
76  return k + maxdist + fkp_rows * (p + 1);
77  }
78 
79  public:
80  edist_state(const Char* ptr1, int len1, const Char* ptr2, int len2,
81  int* fkp_)
82  : seq1(ptr1, len1), seq2(ptr2, len2), fkp(fkp_), maxdist(len2) {
83  Assert(len2 >= len1);
84  // fkp is stored as a rectangular array, column by column. Each entry
85  // represents a value of p, from -1 to maxdist or a special value
86  // close-ish to INT_MIN.
87  fkp_rows = 2 * maxdist + 1;
88  // It's significantly faster to memset() than std::fill_n() with an int
89  // value, so fill with the msb of INT_MIN, which for 32-bit 2's
90  // complement int means -2139062144 instead of -2147483648, which is
91  // fine what we need here.
92  memset(fkp, unsigned(INT_MIN) >> (8 * (sizeof(int) - 1)),
93  sizeof(int) * (calc_index(maxdist, maxdist - 2) + 1));
94  set_f_kp(0, -1, -1);
95  for (int k = 1; k <= maxdist; ++k) {
96  set_f_kp(k, k - 1, -1);
97  set_f_kp(-k, k - 1, k - 1);
98  }
99  }
100 
101  int get_f_kp(int k, int p) const {
102  return fkp[calc_index(k, p)];
103  }
104 
105  void set_f_kp(int k, int p, int val) {
106  fkp[calc_index(k, p)] = val;
107  }
108 
109  bool is_transposed(int pos1, int pos2) const {
110  if (pos1 <= 0 || pos2 <= 0 || pos1 >= seq1.len || pos2 >= seq2.len)
111  return false;
112  return (seq1.ptr[pos1 - 1] == seq2.ptr[pos2] &&
113  seq1.ptr[pos1] == seq2.ptr[pos2 - 1]);
114  }
115 
116  void edist_calc_f_kp(int k, int p);
117 };
118 
119 template<class Char>
121 {
122  int maxlen = get_f_kp(k, p - 1) + 1; /* dist if do substitute */
123  int maxlen2 = get_f_kp(k - 1, p - 1); /* dist if do insert */
124  int maxlen3 = get_f_kp(k + 1, p - 1) + 1; /* dist if delete */
125 
126  if (is_transposed(maxlen, maxlen + k)) {
127  // Transposition.
128  ++maxlen;
129  }
130 
131  if (maxlen >= maxlen2) {
132  if (maxlen >= maxlen3) {
133  // Transposition or Substitution.
134  } else {
135  // Deletion.
136  maxlen = maxlen3;
137  }
138  } else {
139  if (maxlen2 >= maxlen3) {
140  // Insertion.
141  maxlen = maxlen2;
142  } else {
143  // Deletion.
144  maxlen = maxlen3;
145  }
146  }
147 
148  /* Check for exact matches, and increase the length until we don't have
149  * one. */
150  while (maxlen < seq1.len &&
151  maxlen + k < seq2.len &&
152  seq1.ptr[maxlen] == seq2.ptr[maxlen + k]) {
153  ++maxlen;
154  }
155  set_f_kp(k, p, maxlen);
156 }
157 
158 template<class Char>
159 static int
160 seqcmp_editdist(const Char* ptr1, int len1, const Char* ptr2, int len2,
161  int* fkp_, int max_distance)
162 {
163  int lendiff = len2 - len1;
164  /* Make sure second sequence is longer (or same length). */
165  if (lendiff < 0) {
166  lendiff = -lendiff;
167  swap(ptr1, ptr2);
168  swap(len1, len2);
169  }
170 
171  /* Special case for if one or both sequences are empty. */
172  if (len1 == 0) return len2;
173 
174  edist_state<Char> state(ptr1, len1, ptr2, len2, fkp_);
175 
176  int p = lendiff; /* This is the minimum possible edit distance. */
177  while (p <= max_distance) {
178  for (int temp_p = 0; temp_p != p; ++temp_p) {
179  int inc = p - temp_p;
180  if (abs(lendiff - inc) <= temp_p) {
181  state.edist_calc_f_kp(lendiff - inc, temp_p);
182  }
183  if (abs(lendiff + inc) <= temp_p) {
184  state.edist_calc_f_kp(lendiff + inc, temp_p);
185  }
186  }
187  state.edist_calc_f_kp(lendiff, p);
188 
189  if (state.get_f_kp(lendiff, p) == len1) break;
190  ++p;
191  }
192 
193  return p;
194 }
195 
196 int
197 EditDistanceCalculator::calc(const unsigned* ptr, int len,
198  int max_distance) const
199 {
200  // Calculate a cheap lower bound on the edit distance by considering
201  // frequency histograms.
202  freqs_bitmap freqs = 0;
203  freqs_bitmap freqs2 = 0;
204  for (int i = 0; i != len; ++i) {
205  unsigned ch = ptr[i];
206  auto bit = freqs_bitmap(1) << (ch & FREQS_MASK);
207  freqs2 |= (freqs & bit);
208  freqs |= bit;
209  }
210  // Each insertion or deletion adds at most 1 to total. Each transposition
211  // doesn't change it at all. But each substitution can change it by 2 so
212  // we need to divide it by 2. We round up since the unpaired change must
213  // be due to an actual edit.
214  unsigned bits = 1;
215  add_popcount(bits, freqs ^ target_freqs);
216  add_popcount(bits, freqs2 ^ target_freqs2);
217  int ed_lower_bound = bits / 2;
218  if (ed_lower_bound > max_distance) {
219  // It's OK to return any distance > max_distance if the true answer is
220  // > max_distance.
221  return ed_lower_bound;
222  }
223 
224  if (rare(target.size() > size_t{INT_MAX})) {
225  return INT_MAX;
226  }
227  int target_size = int(target.size());
228 
229  if (!array) {
230  // Allocate space for the largest case we need to consider, which is
231  // when the second sequence is len + max_distance long. Any second
232  // sequence which is longer must be more than max_distance edits
233  // away.
234  unsigned maxdist;
235  if (rare(add_overflows(unsigned(target_size), unsigned(max_distance),
236  maxdist))) {
237  return INT_MAX;
238  }
239  unsigned max_cols;
240  if (rare(mul_overflows(maxdist, 2u, max_cols))) {
241  return INT_MAX;
242  }
243  unsigned max_rows;
244  if (rare(add_overflows(max_cols, 1u, max_rows))) {
245  return INT_MAX;
246  }
247  unsigned alloc_size;
248  if (rare(mul_overflows(max_rows, max_cols, alloc_size))) {
249  return INT_MAX;
250  }
251  array = new int[alloc_size];
252  }
253 
254  return seqcmp_editdist<unsigned>(ptr, len, &target[0], target_size,
255  array, max_distance);
256 }
int calc(const unsigned *ptr, int len, int max_distance) const
Calculate edit distance.
unsigned long long freqs_bitmap
The type to use for the occurrence bitmaps.
Definition: editdistance.h:74
edist_state & operator=(const edist_state &)=delete
Don't allow assignment.
bool is_transposed(int pos1, int pos2) const
edist_seq< Char > seq1
Definition: editdistance.cc:60
void edist_calc_f_kp(int k, int p)
int get_f_kp(int k, int p) const
edist_seq< Char > seq2
Definition: editdistance.cc:61
int calc_index(int k, int p) const
Definition: editdistance.cc:75
void set_f_kp(int k, int p, int val)
edist_state(const edist_state &)=delete
Don't allow copying.
edist_state(const Char *ptr1, int len1, const Char *ptr2, int len2, int *fkp_)
Definition: editdistance.cc:80
#define rare(COND)
Definition: config.h:616
PositionList * p
static int seqcmp_editdist(const Char *ptr1, int len1, const Char *ptr2, int len2, int *fkp_, int max_distance)
Edit distance calculation algorithm.
Various assertion macros.
#define Assert(COND)
Definition: omassert.h:122
Arithmetic operations with overflow checks.
std::enable_if_t< std::is_unsigned_v< T1 > &&std::is_unsigned_v< T2 > &&std::is_unsigned_v< R >, bool > add_overflows(T1 a, T2 b, R &res)
Addition with overflow checking.
Definition: overflow.h:58
std::enable_if_t< std::is_unsigned_v< T1 > &&std::is_unsigned_v< T2 > &&std::is_unsigned_v< R >, bool > mul_overflows(T1 a, T2 b, R &res)
Multiplication with overflow checking.
Definition: overflow.h:188
Count the number of set bits in an integer type.
static void add_popcount(A &accumulator, V value)
Add the number of set bits in value to accumulator.
Definition: popcount.h:39
edist_seq(const Char *ptr_, int len_)
Definition: editdistance.cc:47
const Char * ptr
Definition: editdistance.cc:48