RapidFuzz
Loading...
Searching...
No Matches
types.hpp
1/* SPDX-License-Identifier: MIT */
2/* Copyright © 2020 Max Bachmann */
3
4#pragma once
5
6#include <algorithm>
7#include <rapidfuzz/details/config.hpp>
8#include <stddef.h>
9#include <stdexcept>
10#include <vector>
11
12namespace rapidfuzz {
13
14struct StringAffix {
15 size_t prefix_len;
16 size_t suffix_len;
17};
18
19struct LevenshteinWeightTable {
20 size_t insert_cost;
21 size_t delete_cost;
22 size_t replace_cost;
23};
24
28enum class EditType {
29 None = 0,
30 Replace = 1,
31 Insert = 2,
32 Delete = 3
33};
34
45struct EditOp {
46 EditType type;
47 size_t src_pos;
48 size_t dest_pos;
50 EditOp() : type(EditType::None), src_pos(0), dest_pos(0)
51 {}
52
53 EditOp(EditType type_, size_t src_pos_, size_t dest_pos_)
54 : type(type_), src_pos(src_pos_), dest_pos(dest_pos_)
55 {}
56};
57
58inline bool operator==(EditOp a, EditOp b)
59{
60 return (a.type == b.type) && (a.src_pos == b.src_pos) && (a.dest_pos == b.dest_pos);
61}
62
63inline bool operator!=(EditOp a, EditOp b)
64{
65 return !(a == b);
66}
67
81struct Opcode {
82 EditType type;
83 size_t src_begin;
84 size_t src_end;
85 size_t dest_begin;
86 size_t dest_end;
88 Opcode() : type(EditType::None), src_begin(0), src_end(0), dest_begin(0), dest_end(0)
89 {}
90
91 Opcode(EditType type_, size_t src_begin_, size_t src_end_, size_t dest_begin_, size_t dest_end_)
92 : type(type_), src_begin(src_begin_), src_end(src_end_), dest_begin(dest_begin_), dest_end(dest_end_)
93 {}
94};
95
96inline bool operator==(Opcode a, Opcode b)
97{
98 return (a.type == b.type) && (a.src_begin == b.src_begin) && (a.src_end == b.src_end) &&
99 (a.dest_begin == b.dest_begin) && (a.dest_end == b.dest_end);
100}
101
102inline bool operator!=(Opcode a, Opcode b)
103{
104 return !(a == b);
105}
106
107namespace detail {
108template <typename Vec>
109auto vector_slice(const Vec& vec, int start, int stop, int step) -> Vec
110{
111 Vec new_vec;
112
113 if (step == 0) throw std::invalid_argument("slice step cannot be zero");
114 if (step < 0) throw std::invalid_argument("step sizes below 0 lead to an invalid order of editops");
115
116 if (start < 0)
117 start = std::max<int>(start + static_cast<int>(vec.size()), 0);
118 else if (start > static_cast<int>(vec.size()))
119 start = static_cast<int>(vec.size());
120
121 if (stop < 0)
122 stop = std::max<int>(stop + static_cast<int>(vec.size()), 0);
123 else if (stop > static_cast<int>(vec.size()))
124 stop = static_cast<int>(vec.size());
125
126 if (start >= stop) return new_vec;
127
128 int count = (stop - 1 - start) / step + 1;
129 new_vec.reserve(static_cast<size_t>(count));
130
131 for (int i = start; i < stop; i += step)
132 new_vec.push_back(vec[static_cast<size_t>(i)]);
133
134 return new_vec;
135}
136
137template <typename Vec>
138void vector_remove_slice(Vec& vec, int start, int stop, int step)
139{
140 if (step == 0) throw std::invalid_argument("slice step cannot be zero");
141 if (step < 0) throw std::invalid_argument("step sizes below 0 lead to an invalid order of editops");
142
143 if (start < 0)
144 start = std::max<int>(start + static_cast<int>(vec.size()), 0);
145 else if (start > static_cast<int>(vec.size()))
146 start = static_cast<int>(vec.size());
147
148 if (stop < 0)
149 stop = std::max<int>(stop + static_cast<int>(vec.size()), 0);
150 else if (stop > static_cast<int>(vec.size()))
151 stop = static_cast<int>(vec.size());
152
153 if (start >= stop) return;
154
155 auto iter = vec.begin() + start;
156 for (int i = start; i < static_cast<int>(vec.size()); i++)
157 if (i >= stop || ((i - start) % step != 0)) *(iter++) = vec[static_cast<size_t>(i)];
158
159 vec.resize(static_cast<size_t>(std::distance(vec.begin(), iter)));
160 vec.shrink_to_fit();
161}
162
163} // namespace detail
164
165class Opcodes;
166
167class Editops : private std::vector<EditOp> {
168public:
169 using std::vector<EditOp>::size_type;
170
171 Editops() noexcept : src_len(0), dest_len(0)
172 {}
173
174 Editops(size_type count, const EditOp& value) : std::vector<EditOp>(count, value), src_len(0), dest_len(0)
175 {}
176
177 explicit Editops(size_type count) : std::vector<EditOp>(count), src_len(0), dest_len(0)
178 {}
179
180 Editops(const Editops& other)
181 : std::vector<EditOp>(other), src_len(other.src_len), dest_len(other.dest_len)
182 {}
183
184 Editops(const Opcodes& other);
185
186 Editops(Editops&& other) noexcept
187 {
188 swap(other);
189 }
190
191 Editops& operator=(Editops other) noexcept
192 {
193 swap(other);
194 return *this;
195 }
196
197 /* Element access */
198 using std::vector<EditOp>::at;
199 using std::vector<EditOp>::operator[];
200 using std::vector<EditOp>::front;
201 using std::vector<EditOp>::back;
202 using std::vector<EditOp>::data;
203
204 /* Iterators */
205 using std::vector<EditOp>::begin;
206 using std::vector<EditOp>::cbegin;
207 using std::vector<EditOp>::end;
208 using std::vector<EditOp>::cend;
209 using std::vector<EditOp>::rbegin;
210 using std::vector<EditOp>::crbegin;
211 using std::vector<EditOp>::rend;
212 using std::vector<EditOp>::crend;
213
214 /* Capacity */
215 using std::vector<EditOp>::empty;
216 using std::vector<EditOp>::size;
217 using std::vector<EditOp>::max_size;
218 using std::vector<EditOp>::reserve;
219 using std::vector<EditOp>::capacity;
220 using std::vector<EditOp>::shrink_to_fit;
221
222 /* Modifiers */
223 using std::vector<EditOp>::clear;
224 using std::vector<EditOp>::insert;
225 using std::vector<EditOp>::emplace;
226 using std::vector<EditOp>::erase;
227 using std::vector<EditOp>::push_back;
228 using std::vector<EditOp>::emplace_back;
229 using std::vector<EditOp>::pop_back;
230 using std::vector<EditOp>::resize;
231
232 void swap(Editops& rhs) noexcept
233 {
234 std::swap(src_len, rhs.src_len);
235 std::swap(dest_len, rhs.dest_len);
236 std::vector<EditOp>::swap(rhs);
237 }
238
239 Editops slice(int start, int stop, int step = 1) const
240 {
241 Editops ed_slice = detail::vector_slice(*this, start, stop, step);
242 ed_slice.src_len = src_len;
243 ed_slice.dest_len = dest_len;
244 return ed_slice;
245 }
246
247 void remove_slice(int start, int stop, int step = 1)
248 {
249 detail::vector_remove_slice(*this, start, stop, step);
250 }
251
252 Editops reverse() const
253 {
254 Editops reversed = *this;
255 std::reverse(reversed.begin(), reversed.end());
256 return reversed;
257 }
258
259 size_t get_src_len() const noexcept
260 {
261 return src_len;
262 }
263 void set_src_len(size_t len) noexcept
264 {
265 src_len = len;
266 }
267 size_t get_dest_len() const noexcept
268 {
269 return dest_len;
270 }
271 void set_dest_len(size_t len) noexcept
272 {
273 dest_len = len;
274 }
275
276 Editops inverse() const
277 {
278 Editops inv_ops = *this;
279 std::swap(inv_ops.src_len, inv_ops.dest_len);
280 for (auto& op : inv_ops) {
281 std::swap(op.src_pos, op.dest_pos);
282 if (op.type == EditType::Delete)
283 op.type = EditType::Insert;
284 else if (op.type == EditType::Insert)
285 op.type = EditType::Delete;
286 }
287 return inv_ops;
288 }
289
290 Editops remove_subsequence(const Editops& subsequence) const
291 {
292 Editops result;
293 result.set_src_len(src_len);
294 result.set_dest_len(dest_len);
295
296 if (subsequence.size() > size()) throw std::invalid_argument("subsequence is not a subsequence");
297
298 result.resize(size() - subsequence.size());
299
300 /* offset to correct removed edit operations */
301 int offset = 0;
302 auto op_iter = begin();
303 auto op_end = end();
304 size_t result_pos = 0;
305 for (const auto& sop : subsequence) {
306 for (; op_iter != op_end && sop != *op_iter; op_iter++) {
307 if (result_pos >= result.size()) throw std::invalid_argument("subsequence is not a subsequence");
308
309 result[result_pos] = *op_iter;
310 result[result_pos].src_pos =
311 static_cast<size_t>(static_cast<ptrdiff_t>(result[result_pos].src_pos) + offset);
312 result_pos++;
313 }
314 /* element of subsequence not part of the sequence */
315 if (op_iter == op_end) throw std::invalid_argument("subsequence is not a subsequence");
316
317 if (sop.type == EditType::Insert)
318 offset++;
319 else if (sop.type == EditType::Delete)
320 offset--;
321 op_iter++;
322 }
323
324 /* add remaining elements */
325 for (; op_iter != op_end; op_iter++) {
326 result[result_pos] = *op_iter;
327 result[result_pos].src_pos =
328 static_cast<size_t>(static_cast<ptrdiff_t>(result[result_pos].src_pos) + offset);
329 result_pos++;
330 }
331
332 return result;
333 }
334
335private:
336 size_t src_len;
337 size_t dest_len;
338};
339
340inline bool operator==(const Editops& lhs, const Editops& rhs)
341{
342 if (lhs.get_src_len() != rhs.get_src_len() || lhs.get_dest_len() != rhs.get_dest_len()) return false;
343
344 if (lhs.size() != rhs.size()) return false;
345
346 return std::equal(lhs.begin(), lhs.end(), rhs.begin());
347}
348
349inline bool operator!=(const Editops& lhs, const Editops& rhs)
350{
351 return !(lhs == rhs);
352}
353
354inline void swap(Editops& lhs, Editops& rhs) noexcept(noexcept(lhs.swap(rhs)))
355{
356 lhs.swap(rhs);
357}
358
359class Opcodes : private std::vector<Opcode> {
360public:
361 using std::vector<Opcode>::size_type;
362
363 Opcodes() noexcept : src_len(0), dest_len(0)
364 {}
365
366 Opcodes(size_type count, const Opcode& value) : std::vector<Opcode>(count, value), src_len(0), dest_len(0)
367 {}
368
369 explicit Opcodes(size_type count) : std::vector<Opcode>(count), src_len(0), dest_len(0)
370 {}
371
372 Opcodes(const Opcodes& other)
373 : std::vector<Opcode>(other), src_len(other.src_len), dest_len(other.dest_len)
374 {}
375
376 Opcodes(const Editops& other);
377
378 Opcodes(Opcodes&& other) noexcept
379 {
380 swap(other);
381 }
382
383 Opcodes& operator=(Opcodes other) noexcept
384 {
385 swap(other);
386 return *this;
387 }
388
389 /* Element access */
390 using std::vector<Opcode>::at;
391 using std::vector<Opcode>::operator[];
392 using std::vector<Opcode>::front;
393 using std::vector<Opcode>::back;
394 using std::vector<Opcode>::data;
395
396 /* Iterators */
397 using std::vector<Opcode>::begin;
398 using std::vector<Opcode>::cbegin;
399 using std::vector<Opcode>::end;
400 using std::vector<Opcode>::cend;
401 using std::vector<Opcode>::rbegin;
402 using std::vector<Opcode>::crbegin;
403 using std::vector<Opcode>::rend;
404 using std::vector<Opcode>::crend;
405
406 /* Capacity */
407 using std::vector<Opcode>::empty;
408 using std::vector<Opcode>::size;
409 using std::vector<Opcode>::max_size;
410 using std::vector<Opcode>::reserve;
411 using std::vector<Opcode>::capacity;
412 using std::vector<Opcode>::shrink_to_fit;
413
414 /* Modifiers */
415 using std::vector<Opcode>::clear;
416 using std::vector<Opcode>::insert;
417 using std::vector<Opcode>::emplace;
418 using std::vector<Opcode>::erase;
419 using std::vector<Opcode>::push_back;
420 using std::vector<Opcode>::emplace_back;
421 using std::vector<Opcode>::pop_back;
422 using std::vector<Opcode>::resize;
423
424 void swap(Opcodes& rhs) noexcept
425 {
426 std::swap(src_len, rhs.src_len);
427 std::swap(dest_len, rhs.dest_len);
428 std::vector<Opcode>::swap(rhs);
429 }
430
431 Opcodes slice(int start, int stop, int step = 1) const
432 {
433 Opcodes ed_slice = detail::vector_slice(*this, start, stop, step);
434 ed_slice.src_len = src_len;
435 ed_slice.dest_len = dest_len;
436 return ed_slice;
437 }
438
439 Opcodes reverse() const
440 {
441 Opcodes reversed = *this;
442 std::reverse(reversed.begin(), reversed.end());
443 return reversed;
444 }
445
446 size_t get_src_len() const noexcept
447 {
448 return src_len;
449 }
450 void set_src_len(size_t len) noexcept
451 {
452 src_len = len;
453 }
454 size_t get_dest_len() const noexcept
455 {
456 return dest_len;
457 }
458 void set_dest_len(size_t len) noexcept
459 {
460 dest_len = len;
461 }
462
463 Opcodes inverse() const
464 {
465 Opcodes inv_ops = *this;
466 std::swap(inv_ops.src_len, inv_ops.dest_len);
467 for (auto& op : inv_ops) {
468 std::swap(op.src_begin, op.dest_begin);
469 std::swap(op.src_end, op.dest_end);
470 if (op.type == EditType::Delete)
471 op.type = EditType::Insert;
472 else if (op.type == EditType::Insert)
473 op.type = EditType::Delete;
474 }
475 return inv_ops;
476 }
477
478private:
479 size_t src_len;
480 size_t dest_len;
481};
482
483inline bool operator==(const Opcodes& lhs, const Opcodes& rhs)
484{
485 if (lhs.get_src_len() != rhs.get_src_len() || lhs.get_dest_len() != rhs.get_dest_len()) return false;
486
487 if (lhs.size() != rhs.size()) return false;
488
489 return std::equal(lhs.begin(), lhs.end(), rhs.begin());
490}
491
492inline bool operator!=(const Opcodes& lhs, const Opcodes& rhs)
493{
494 return !(lhs == rhs);
495}
496
497inline void swap(Opcodes& lhs, Opcodes& rhs) noexcept(noexcept(lhs.swap(rhs)))
498{
499 lhs.swap(rhs);
500}
501
502inline Editops::Editops(const Opcodes& other)
503{
504 src_len = other.get_src_len();
505 dest_len = other.get_dest_len();
506 for (const auto& op : other) {
507 switch (op.type) {
508 case EditType::None: break;
509
510 case EditType::Replace:
511 for (size_t j = 0; j < op.src_end - op.src_begin; j++)
512 push_back({EditType::Replace, op.src_begin + j, op.dest_begin + j});
513 break;
514
515 case EditType::Insert:
516 for (size_t j = 0; j < op.dest_end - op.dest_begin; j++)
517 push_back({EditType::Insert, op.src_begin, op.dest_begin + j});
518 break;
519
520 case EditType::Delete:
521 for (size_t j = 0; j < op.src_end - op.src_begin; j++)
522 push_back({EditType::Delete, op.src_begin + j, op.dest_begin});
523 break;
524 }
525 }
526}
527
528inline Opcodes::Opcodes(const Editops& other)
529{
530 src_len = other.get_src_len();
531 dest_len = other.get_dest_len();
532 size_t src_pos = 0;
533 size_t dest_pos = 0;
534 for (size_t i = 0; i < other.size();) {
535 if (src_pos < other[i].src_pos || dest_pos < other[i].dest_pos) {
536 push_back({EditType::None, src_pos, other[i].src_pos, dest_pos, other[i].dest_pos});
537 src_pos = other[i].src_pos;
538 dest_pos = other[i].dest_pos;
539 }
540
541 size_t src_begin = src_pos;
542 size_t dest_begin = dest_pos;
543 EditType type = other[i].type;
544 do {
545 switch (type) {
546 case EditType::None: break;
547
548 case EditType::Replace:
549 src_pos++;
550 dest_pos++;
551 break;
552
553 case EditType::Insert: dest_pos++; break;
554
555 case EditType::Delete: src_pos++; break;
556 }
557 i++;
558 } while (i < other.size() && other[i].type == type && src_pos == other[i].src_pos &&
559 dest_pos == other[i].dest_pos);
560
561 push_back({type, src_begin, src_pos, dest_begin, dest_pos});
562 }
563
564 if (src_pos < other.get_src_len() || dest_pos < other.get_dest_len()) {
565 push_back({EditType::None, src_pos, other.get_src_len(), dest_pos, other.get_dest_len()});
566 }
567}
568
569template <typename T>
570struct ScoreAlignment {
571 T score;
572 size_t src_start;
573 size_t src_end;
574 size_t dest_start;
575 size_t dest_end;
577 ScoreAlignment() : score(T()), src_start(0), src_end(0), dest_start(0), dest_end(0)
578 {}
579
580 ScoreAlignment(T score_, size_t src_start_, size_t src_end_, size_t dest_start_, size_t dest_end_)
581 : score(score_),
582 src_start(src_start_),
583 src_end(src_end_),
584 dest_start(dest_start_),
585 dest_end(dest_end_)
586 {}
587};
588
589template <typename T>
590inline bool operator==(const ScoreAlignment<T>& a, const ScoreAlignment<T>& b)
591{
592 return (a.score == b.score) && (a.src_start == b.src_start) && (a.src_end == b.src_end) &&
593 (a.dest_start == b.dest_start) && (a.dest_end == b.dest_end);
594}
595
596} // namespace rapidfuzz
Edit operations used by the Levenshtein distance.
Definition types.hpp:45
EditType type
Definition types.hpp:46
size_t dest_pos
Definition types.hpp:48
size_t src_pos
Definition types.hpp:47
Edit operations used by the Levenshtein distance.
Definition types.hpp:81
EditType type
Definition types.hpp:82
size_t dest_end
Definition types.hpp:86
size_t src_end
Definition types.hpp:84
size_t dest_begin
Definition types.hpp:85
size_t src_begin
Definition types.hpp:83