6#include <rapidfuzz/details/PatternMatchVector.hpp>
7#include <rapidfuzz/details/common.hpp>
8#include <rapidfuzz/details/distance.hpp>
9#include <rapidfuzz/details/intrinsics.hpp>
15struct FlaggedCharsWord {
20struct FlaggedCharsMultiword {
21 std::vector<uint64_t> P_flag;
22 std::vector<uint64_t> T_flag;
25struct SearchBoundMask {
27 size_t empty_words = 0;
28 uint64_t last_mask = 0;
29 uint64_t first_mask = 0;
32static inline double jaro_calculate_similarity(
size_t P_len,
size_t T_len,
size_t CommonChars,
33 size_t Transpositions)
37 Sim +=
static_cast<double>(CommonChars) /
static_cast<double>(P_len);
38 Sim +=
static_cast<double>(CommonChars) /
static_cast<double>(T_len);
39 Sim += (
static_cast<double>(CommonChars) -
static_cast<double>(Transpositions)) /
40 static_cast<double>(CommonChars);
47static inline bool jaro_length_filter(
size_t P_len,
size_t T_len,
double score_cutoff)
49 if (!T_len || !P_len)
return false;
51 double min_len =
static_cast<double>(std::min(P_len, T_len));
52 double Sim = min_len /
static_cast<double>(P_len) + min_len /
static_cast<double>(T_len) + 1.0;
54 return Sim >= score_cutoff;
60static inline bool jaro_common_char_filter(
size_t P_len,
size_t T_len,
size_t CommonChars,
63 if (!CommonChars)
return false;
66 Sim +=
static_cast<double>(CommonChars) /
static_cast<double>(P_len);
67 Sim +=
static_cast<double>(CommonChars) /
static_cast<double>(T_len);
70 return Sim >= score_cutoff;
73static inline size_t count_common_chars(
const FlaggedCharsWord& flagged)
75 return popcount(flagged.P_flag);
78static inline size_t count_common_chars(
const FlaggedCharsMultiword& flagged)
80 size_t CommonChars = 0;
81 if (flagged.P_flag.size() < flagged.T_flag.size()) {
82 for (uint64_t flag : flagged.P_flag) {
83 CommonChars += popcount(flag);
87 for (uint64_t flag : flagged.T_flag) {
88 CommonChars += popcount(flag);
94template <
typename PM_Vec,
typename InputIt1,
typename InputIt2>
95static inline FlaggedCharsWord flag_similar_characters_word(
const PM_Vec& PM,
97 const Range<InputIt1>&,
99 const Range<InputIt1>& P,
101 const Range<InputIt2>& T,
size_t Bound)
103 assert(P.size() <= 64);
104 assert(T.size() <= 64);
105 assert(Bound > P.size() || P.size() - Bound <= T.size());
107 FlaggedCharsWord flagged = {0, 0};
109 uint64_t BoundMask = bit_mask_lsb<uint64_t>(Bound + 1);
112 auto T_iter = T.begin();
113 for (; j < std::min(Bound, T.size()); ++j, ++T_iter) {
114 uint64_t PM_j = PM.get(0, *T_iter) & BoundMask & (~flagged.P_flag);
116 flagged.P_flag |= blsi(PM_j);
117 flagged.T_flag |=
static_cast<uint64_t
>(PM_j != 0) << j;
119 BoundMask = (BoundMask << 1) | 1;
122 for (; j < T.size(); ++j, ++T_iter) {
123 uint64_t PM_j = PM.get(0, *T_iter) & BoundMask & (~flagged.P_flag);
125 flagged.P_flag |= blsi(PM_j);
126 flagged.T_flag |=
static_cast<uint64_t
>(PM_j != 0) << j;
134template <
typename CharT>
135static inline void flag_similar_characters_step(
const BlockPatternMatchVector& PM, CharT T_j,
136 FlaggedCharsMultiword& flagged,
size_t j,
137 SearchBoundMask BoundMask)
139 size_t j_word = j / 64;
140 size_t j_pos = j % 64;
141 size_t word = BoundMask.empty_words;
142 size_t last_word = word + BoundMask.words;
144 if (BoundMask.words == 1) {
146 PM.get(word, T_j) & BoundMask.last_mask & BoundMask.first_mask & (~flagged.P_flag[word]);
148 flagged.P_flag[word] |= blsi(PM_j);
149 flagged.T_flag[j_word] |=
static_cast<uint64_t
>(PM_j != 0) << j_pos;
153 if (BoundMask.first_mask) {
154 uint64_t PM_j = PM.get(word, T_j) & BoundMask.first_mask & (~flagged.P_flag[word]);
157 flagged.P_flag[word] |= blsi(PM_j);
158 flagged.T_flag[j_word] |= 1ull << j_pos;
165 if (T_j >= 0 && T_j < 256) {
166 for (; word + 3 < last_word - 1; word += 4) {
168 unroll<size_t, 4>([&](
size_t i) {
169 PM_j[i] = PM.get(word + i,
static_cast<uint8_t
>(T_j)) & (~flagged.P_flag[word + i]);
173 flagged.P_flag[word] |= blsi(PM_j[0]);
174 flagged.T_flag[j_word] |= 1ull << j_pos;
178 flagged.P_flag[word + 1] |= blsi(PM_j[1]);
179 flagged.T_flag[j_word] |= 1ull << j_pos;
183 flagged.P_flag[word + 2] |= blsi(PM_j[2]);
184 flagged.T_flag[j_word] |= 1ull << j_pos;
188 flagged.P_flag[word + 3] |= blsi(PM_j[3]);
189 flagged.T_flag[j_word] |= 1ull << j_pos;
195 for (; word < last_word - 1; ++word) {
196 uint64_t PM_j = PM.get(word, T_j) & (~flagged.P_flag[word]);
199 flagged.P_flag[word] |= blsi(PM_j);
200 flagged.T_flag[j_word] |= 1ull << j_pos;
205 if (BoundMask.last_mask) {
206 uint64_t PM_j = PM.get(word, T_j) & BoundMask.last_mask & (~flagged.P_flag[word]);
208 flagged.P_flag[word] |= blsi(PM_j);
209 flagged.T_flag[j_word] |=
static_cast<uint64_t
>(PM_j != 0) << j_pos;
213template <
typename InputIt1,
typename InputIt2>
214static inline FlaggedCharsMultiword flag_similar_characters_block(
const BlockPatternMatchVector& PM,
215 const Range<InputIt1>& P,
216 const Range<InputIt2>& T,
size_t Bound)
218 assert(P.size() > 64 || T.size() > 64);
219 assert(Bound > P.size() || P.size() - Bound <= T.size());
222 FlaggedCharsMultiword flagged;
223 flagged.T_flag.resize(ceil_div(T.size(), 64));
224 flagged.P_flag.resize(ceil_div(P.size(), 64));
226 SearchBoundMask BoundMask;
227 size_t start_range = std::min(Bound + 1, P.size());
228 BoundMask.words = 1 + start_range / 64;
229 BoundMask.empty_words = 0;
230 BoundMask.last_mask = (1ull << (start_range % 64)) - 1;
231 BoundMask.first_mask = ~UINT64_C(0);
233 auto T_iter = T.begin();
234 for (
size_t j = 0; j < T.size(); ++j, ++T_iter) {
235 flag_similar_characters_step(PM, *T_iter, flagged, j, BoundMask);
237 if (j + Bound + 1 < P.size()) {
238 BoundMask.last_mask = (BoundMask.last_mask << 1) | 1;
239 if (j + Bound + 2 < P.size() && BoundMask.last_mask == ~UINT64_C(0)) {
240 BoundMask.last_mask = 0;
246 BoundMask.first_mask <<= 1;
247 if (BoundMask.first_mask == 0) {
248 BoundMask.first_mask = ~UINT64_C(0);
250 BoundMask.empty_words++;
258template <
typename PM_Vec,
typename InputIt1>
259static inline size_t count_transpositions_word(
const PM_Vec& PM,
const Range<InputIt1>& T,
260 const FlaggedCharsWord& flagged)
262 uint64_t P_flag = flagged.P_flag;
263 uint64_t T_flag = flagged.T_flag;
265 size_t Transpositions = 0;
267 uint64_t PatternFlagMask = blsi(P_flag);
269 Transpositions += !(PM.get(0, T[countr_zero(T_flag)]) & PatternFlagMask);
271 T_flag = blsr(T_flag);
272 P_flag ^= PatternFlagMask;
275 return Transpositions;
278template <
typename InputIt1>
279static inline size_t count_transpositions_block(
const BlockPatternMatchVector& PM,
const Range<InputIt1>& T,
280 const FlaggedCharsMultiword& flagged,
size_t FlaggedChars)
283 size_t PatternWord = 0;
284 uint64_t T_flag = flagged.T_flag[TextWord];
285 uint64_t P_flag = flagged.P_flag[PatternWord];
287 auto T_first = T.begin();
288 size_t Transpositions = 0;
289 while (FlaggedChars) {
293 T_flag = flagged.T_flag[TextWord];
299 P_flag = flagged.P_flag[PatternWord];
302 uint64_t PatternFlagMask = blsi(P_flag);
304 Transpositions += !(PM.get(PatternWord, T_first[
static_cast<ptrdiff_t
>(countr_zero(T_flag))]) &
307 T_flag = blsr(T_flag);
308 P_flag ^= PatternFlagMask;
314 return Transpositions;
321static inline size_t jaro_bounds(
size_t P_len,
size_t T_len)
326 size_t Bound = (T_len > P_len) ? T_len : P_len;
328 if (Bound > 0) Bound--;
336template <
typename InputIt1,
typename InputIt2>
337static inline size_t jaro_bounds(Range<InputIt1>& P, Range<InputIt2>& T)
339 size_t P_len = P.size();
340 size_t T_len = T.size();
344 assert(P_len != 0 || T_len != 0);
351 Bound = T_len / 2 - 1;
352 if (T_len > P_len + Bound) T.remove_suffix(T_len - (P_len + Bound));
355 Bound = P_len / 2 - 1;
356 if (P_len > T_len + Bound) P.remove_suffix(P_len - (T_len + Bound));
361template <
typename InputIt1,
typename InputIt2>
362static inline double jaro_similarity(Range<InputIt1> P, Range<InputIt2> T,
double score_cutoff)
364 size_t P_len = P.size();
365 size_t T_len = T.size();
367 if (score_cutoff > 1.0)
return 0.0;
369 if (!P_len && !T_len)
return 1.0;
372 if (!jaro_length_filter(P_len, T_len, score_cutoff))
return 0.0;
374 if (P_len == 1 && T_len == 1)
return static_cast<double>(P.front() == T.front());
376 size_t Bound = jaro_bounds(P, T);
379 size_t CommonChars = remove_common_prefix(P, T);
380 size_t Transpositions = 0;
382 if (P.empty() || T.empty()) {
385 else if (P.size() <= 64 && T.size() <= 64) {
386 PatternMatchVector PM(P);
387 auto flagged = flag_similar_characters_word(PM, P, T, Bound);
388 CommonChars += count_common_chars(flagged);
390 if (!jaro_common_char_filter(P_len, T_len, CommonChars, score_cutoff))
return 0.0;
392 Transpositions = count_transpositions_word(PM, T, flagged);
395 BlockPatternMatchVector PM(P);
396 auto flagged = flag_similar_characters_block(PM, P, T, Bound);
397 size_t FlaggedChars = count_common_chars(flagged);
398 CommonChars += FlaggedChars;
400 if (!jaro_common_char_filter(P_len, T_len, CommonChars, score_cutoff))
return 0.0;
402 Transpositions = count_transpositions_block(PM, T, flagged, FlaggedChars);
405 double Sim = jaro_calculate_similarity(P_len, T_len, CommonChars, Transpositions);
406 return (Sim >= score_cutoff) ? Sim : 0;
409template <
typename InputIt1,
typename InputIt2>
410static inline double jaro_similarity(
const BlockPatternMatchVector& PM, Range<InputIt1> P, Range<InputIt2> T,
413 size_t P_len = P.size();
414 size_t T_len = T.size();
416 if (score_cutoff > 1.0)
return 0.0;
418 if (!P_len && !T_len)
return 1.0;
421 if (!jaro_length_filter(P_len, T_len, score_cutoff))
return 0.0;
423 if (P_len == 1 && T_len == 1)
return static_cast<double>(P[0] == T[0]);
425 size_t Bound = jaro_bounds(P, T);
428 size_t CommonChars = 0;
429 size_t Transpositions = 0;
431 if (P.empty() || T.empty()) {
434 else if (P.size() <= 64 && T.size() <= 64) {
435 auto flagged = flag_similar_characters_word(PM, P, T, Bound);
436 CommonChars += count_common_chars(flagged);
438 if (!jaro_common_char_filter(P_len, T_len, CommonChars, score_cutoff))
return 0.0;
440 Transpositions = count_transpositions_word(PM, T, flagged);
443 auto flagged = flag_similar_characters_block(PM, P, T, Bound);
444 size_t FlaggedChars = count_common_chars(flagged);
445 CommonChars += FlaggedChars;
447 if (!jaro_common_char_filter(P_len, T_len, CommonChars, score_cutoff))
return 0.0;
449 Transpositions = count_transpositions_block(PM, T, flagged, FlaggedChars);
452 double Sim = jaro_calculate_similarity(P_len, T_len, CommonChars, Transpositions);
453 return (Sim >= score_cutoff) ? Sim : 0;
458template <
typename VecType>
459struct JaroSimilaritySimdBounds {
461 VecType boundMaskSize;
465template <
typename VecType,
typename InputIt,
int _lto_hack = RAPIDFUZZ_LTO_HACK>
466static inline auto jaro_similarity_prepare_bound_short_s2(
const VecType* s1_lengths, Range<InputIt>& s2)
467# ifdef RAPIDFUZZ_AVX2
468 -> JaroSimilaritySimdBounds<simd_avx2::native_simd<VecType>>
470 -> JaroSimilaritySimdBounds<simd_sse2::native_simd<VecType>>
473# ifdef RAPIDFUZZ_AVX2
474 using namespace simd_avx2;
476 using namespace simd_sse2;
479# ifndef RAPIDFUZZ_AVX2
480 static constexpr size_t alignment = native_simd<VecType>::alignment;
482 static constexpr size_t vec_width = native_simd<VecType>::size;
483 assert(s2.size() <=
sizeof(VecType) * 8);
485 JaroSimilaritySimdBounds<native_simd<VecType>> bounds;
490 for (
size_t i = 0; i < vec_width; ++i)
491 if (s1_lengths[i] > maxLen) maxLen = s1_lengths[i];
493# ifdef RAPIDFUZZ_AVX2
494 native_simd<VecType> zero(VecType(0));
495 native_simd<VecType> one(1);
497 native_simd<VecType> s1_lengths_simd(
reinterpret_cast<const uint64_t*
>(s1_lengths));
498 native_simd<VecType> s2_length_simd(
static_cast<VecType
>(s2.size()));
502 native_simd<VecType> boundSizes = max8(s1_lengths_simd, s2_length_simd) >> 1;
504 boundSizes -= (boundSizes > zero) & one;
509 bounds.boundMaskSize = sllv(one, boundSizes << 1) - one;
510 bounds.boundMask = sllv(one, boundSizes + one) - one;
512 bounds.maxBound = (s2.size() > maxLen) ? s2.size() : maxLen;
513 bounds.maxBound /= 2;
514 if (bounds.maxBound > 0) bounds.maxBound--;
516 alignas(alignment) std::array<VecType, vec_width> boundMaskSize_;
517 alignas(alignment) std::array<VecType, vec_width> boundMask_;
520 for (
size_t i = 0; i < vec_width; ++i) {
521 size_t Bound = jaro_bounds(
static_cast<size_t>(s1_lengths[i]), s2.size());
523 if (Bound > bounds.maxBound) bounds.maxBound = Bound;
525 boundMaskSize_[i] = bit_mask_lsb<VecType>(2 * Bound);
526 boundMask_[i] = bit_mask_lsb<VecType>(Bound + 1);
529 bounds.boundMaskSize = native_simd<VecType>(
reinterpret_cast<uint64_t*
>(boundMaskSize_.data()));
530 bounds.boundMask = native_simd<VecType>(
reinterpret_cast<uint64_t*
>(boundMask_.data()));
533 size_t lastRelevantChar =
static_cast<size_t>(maxLen) + bounds.maxBound;
534 if (s2.size() > lastRelevantChar) s2.remove_suffix(s2.size() - lastRelevantChar);
539template <
typename VecType,
typename InputIt,
int _lto_hack = RAPIDFUZZ_LTO_HACK>
540static inline auto jaro_similarity_prepare_bound_long_s2(
const VecType* s1_lengths, Range<InputIt>& s2)
541# ifdef RAPIDFUZZ_AVX2
542 -> JaroSimilaritySimdBounds<simd_avx2::native_simd<VecType>>
544 -> JaroSimilaritySimdBounds<simd_sse2::native_simd<VecType>>
547# ifdef RAPIDFUZZ_AVX2
548 using namespace simd_avx2;
550 using namespace simd_sse2;
553 static constexpr size_t vec_width = native_simd<VecType>::size;
554 assert(s2.size() >
sizeof(VecType) * 8);
556 JaroSimilaritySimdBounds<native_simd<VecType>> bounds;
561 for (
size_t i = 0; i < vec_width; ++i)
562 if (s1_lengths[i] > maxLen) maxLen = s1_lengths[i];
564 bounds.maxBound = s2.size() / 2 - 1;
565 bounds.boundMaskSize = native_simd<VecType>(bit_mask_lsb<VecType>(2 * bounds.maxBound));
566 bounds.boundMask = native_simd<VecType>(bit_mask_lsb<VecType>(bounds.maxBound + 1));
568 size_t lastRelevantChar =
static_cast<size_t>(maxLen) + bounds.maxBound;
569 if (s2.size() > lastRelevantChar) s2.remove_suffix(s2.size() - lastRelevantChar);
574template <
typename VecType,
typename InputIt,
int _lto_hack = RAPIDFUZZ_LTO_HACK>
576jaro_similarity_simd_long_s2(Range<double*> scores,
const detail::BlockPatternMatchVector& block,
577 VecType* s1_lengths, Range<InputIt> s2,
double score_cutoff)
noexcept
579# ifdef RAPIDFUZZ_AVX2
580 using namespace simd_avx2;
582 using namespace simd_sse2;
585 static constexpr size_t alignment = native_simd<VecType>::alignment;
586 static constexpr size_t vec_width = native_simd<VecType>::size;
587 static constexpr size_t vecs = native_simd<uint64_t>::size;
588 assert(block.size() % vecs == 0);
589 assert(s2.size() >
sizeof(VecType) * 8);
591 struct AlignedAlloc {
592 AlignedAlloc(
size_t size) : memory(rf_aligned_alloc(native_simd<VecType>::alignment, size))
597 rf_aligned_free(memory);
600 void* memory =
nullptr;
603 native_simd<VecType> zero(VecType(0));
604 native_simd<VecType> one(1);
605 size_t result_index = 0;
607 size_t s2_block_count = detail::ceil_div(s2.size(),
sizeof(VecType) * 8);
608 AlignedAlloc memory(2 * s2_block_count *
sizeof(native_simd<VecType>));
610 native_simd<VecType>* T_flag =
static_cast<native_simd<VecType>*
>(memory.memory);
613 native_simd<VecType>* counter =
static_cast<native_simd<VecType>*
>(memory.memory) + s2_block_count;
614 VecType* T_flags =
static_cast<VecType*
>(memory.memory) + s2_block_count * vec_width;
616 for (
size_t cur_vec = 0; cur_vec < block.size(); cur_vec += vecs) {
618 auto bounds = jaro_similarity_prepare_bound_long_s2(s1_lengths + result_index, s2_cur);
620 native_simd<VecType> P_flag(VecType(0));
622 std::fill(T_flag, T_flag + detail::ceil_div(s2_cur.size(),
sizeof(VecType) * 8),
623 native_simd<VecType>(VecType(0)));
624 std::fill(counter, counter + detail::ceil_div(s2_cur.size(),
sizeof(VecType) * 8),
625 native_simd<VecType>(VecType(1)));
633 for (; j < std::min(bounds.maxBound, s2_cur.size()); ++j) {
634 alignas(alignment) std::array<uint64_t, vecs> stored;
635 unroll<size_t, vecs>([&](
size_t i) { stored[i] = block.get(cur_vec + i, s2_cur[j]); });
636 native_simd<VecType> X(stored.data());
637 native_simd<VecType> PM_j = andnot(X & bounds.boundMask, P_flag);
639 P_flag |= blsi(PM_j);
640 size_t T_word_index = j / (
sizeof(VecType) * 8);
641 T_flag[T_word_index] |= andnot(counter[T_word_index], (PM_j == zero));
643 counter[T_word_index] = counter[T_word_index] << 1;
644 bounds.boundMask = (bounds.boundMask << 1) | ((bounds.boundMask <= bounds.boundMaskSize) & one);
647 for (; j < s2_cur.size(); ++j) {
648 alignas(alignment) std::array<uint64_t, vecs> stored;
649 unroll<size_t, vecs>([&](
size_t i) { stored[i] = block.get(cur_vec + i, s2_cur[j]); });
650 native_simd<VecType> X(stored.data());
651 native_simd<VecType> PM_j = andnot(X & bounds.boundMask, P_flag);
653 P_flag |= blsi(PM_j);
654 size_t T_word_index = j / (
sizeof(VecType) * 8);
655 T_flag[T_word_index] |= andnot(counter[T_word_index], (PM_j == zero));
657 counter[T_word_index] = counter[T_word_index] << 1;
658 bounds.boundMask = bounds.boundMask << 1;
661 auto counts = popcount(P_flag);
662 alignas(alignment) std::array<VecType, vec_width> P_flags;
663 P_flag.store(P_flags.data());
665 for (
size_t i = 0; i < detail::ceil_div(s2_cur.size(),
sizeof(VecType) * 8); ++i)
666 T_flag[i].store(T_flags + i * vec_width);
668 for (
size_t i = 0; i < vec_width; ++i) {
669 size_t CommonChars =
static_cast<size_t>(counts[i]);
670 if (!jaro_common_char_filter(
static_cast<size_t>(s1_lengths[result_index]), s2.size(),
671 CommonChars, score_cutoff))
673 scores[result_index] = 0.0;
678 VecType P_flag_cur = P_flags[i];
679 size_t Transpositions = 0;
681 static constexpr size_t vecs_per_word = vec_width / vecs;
682 size_t cur_block = i / vecs_per_word;
683 size_t offset =
sizeof(VecType) * 8 * (i % vecs_per_word);
686 size_t T_word_index = 0;
687 VecType T_flag_cur = T_flags[T_word_index * vec_width + i];
689 while (!T_flag_cur) {
691 T_flag_cur = T_flags[T_word_index * vec_width + i];
694 VecType PatternFlagMask = blsi(P_flag_cur);
697 block.get(cur_vec + cur_block,
698 s2[countr_zero(T_flag_cur) + T_word_index *
sizeof(VecType) * 8]);
699 Transpositions += !(PM_j & (
static_cast<uint64_t
>(PatternFlagMask) << offset));
701 T_flag_cur = blsr(T_flag_cur);
702 P_flag_cur ^= PatternFlagMask;
706 double Sim = jaro_calculate_similarity(
static_cast<size_t>(s1_lengths[result_index]), s2.size(),
707 CommonChars, Transpositions);
709 scores[result_index] = (Sim >= score_cutoff) ? Sim : 0;
715template <
typename VecType,
typename InputIt,
int _lto_hack = RAPIDFUZZ_LTO_HACK>
717jaro_similarity_simd_short_s2(Range<double*> scores,
const detail::BlockPatternMatchVector& block,
718 VecType* s1_lengths, Range<InputIt> s2,
double score_cutoff)
noexcept
720# ifdef RAPIDFUZZ_AVX2
721 using namespace simd_avx2;
723 using namespace simd_sse2;
726 static constexpr size_t alignment = native_simd<VecType>::alignment;
727 static constexpr size_t vec_width = native_simd<VecType>::size;
728 static constexpr size_t vecs = native_simd<uint64_t>::size;
729 assert(block.size() % vecs == 0);
730 assert(s2.size() <=
sizeof(VecType) * 8);
732 native_simd<VecType> zero(VecType(0));
733 native_simd<VecType> one(1);
734 size_t result_index = 0;
736 for (
size_t cur_vec = 0; cur_vec < block.size(); cur_vec += vecs) {
738 auto bounds = jaro_similarity_prepare_bound_short_s2(s1_lengths + result_index, s2_cur);
740 native_simd<VecType> P_flag(VecType(0));
741 native_simd<VecType> T_flag(VecType(0));
742 native_simd<VecType> counter(VecType(1));
750 for (; j < std::min(bounds.maxBound, s2_cur.size()); ++j) {
751 alignas(alignment) std::array<uint64_t, vecs> stored;
752 unroll<size_t, vecs>([&](
size_t i) { stored[i] = block.get(cur_vec + i, s2_cur[j]); });
753 native_simd<VecType> X(stored.data());
754 native_simd<VecType> PM_j = andnot(X & bounds.boundMask, P_flag);
756 P_flag |= blsi(PM_j);
757 T_flag |= andnot(counter, (PM_j == zero));
759 counter = counter << 1;
760 bounds.boundMask = (bounds.boundMask << 1) | ((bounds.boundMask <= bounds.boundMaskSize) & one);
763 for (; j < s2_cur.size(); ++j) {
764 alignas(alignment) std::array<uint64_t, vecs> stored;
765 unroll<size_t, vecs>([&](
size_t i) { stored[i] = block.get(cur_vec + i, s2_cur[j]); });
766 native_simd<VecType> X(stored.data());
767 native_simd<VecType> PM_j = andnot(X & bounds.boundMask, P_flag);
769 P_flag |= blsi(PM_j);
770 T_flag |= andnot(counter, (PM_j == zero));
772 counter = counter << 1;
773 bounds.boundMask = bounds.boundMask << 1;
776 auto counts = popcount(P_flag);
777 alignas(alignment) std::array<VecType, vec_width> P_flags;
778 P_flag.store(P_flags.data());
779 alignas(alignment) std::array<VecType, vec_width> T_flags;
780 T_flag.store(T_flags.data());
781 for (
size_t i = 0; i < vec_width; ++i) {
782 size_t CommonChars =
static_cast<size_t>(counts[i]);
783 if (!jaro_common_char_filter(
static_cast<size_t>(s1_lengths[result_index]), s2.size(),
784 CommonChars, score_cutoff))
786 scores[result_index] = 0.0;
791 VecType P_flag_cur = P_flags[i];
792 VecType T_flag_cur = T_flags[i];
793 size_t Transpositions = 0;
795 static constexpr size_t vecs_per_word = vec_width / vecs;
796 size_t cur_block = i / vecs_per_word;
797 size_t offset =
sizeof(VecType) * 8 * (i % vecs_per_word);
799 VecType PatternFlagMask = blsi(P_flag_cur);
801 uint64_t PM_j = block.get(cur_vec + cur_block, s2[countr_zero(T_flag_cur)]);
802 Transpositions += !(PM_j & (
static_cast<uint64_t
>(PatternFlagMask) << offset));
804 T_flag_cur = blsr(T_flag_cur);
805 P_flag_cur ^= PatternFlagMask;
808 double Sim = jaro_calculate_similarity(
static_cast<size_t>(s1_lengths[result_index]), s2.size(),
809 CommonChars, Transpositions);
811 scores[result_index] = (Sim >= score_cutoff) ? Sim : 0;
817template <
typename VecType,
typename InputIt,
int _lto_hack = RAPIDFUZZ_LTO_HACK>
818static inline void jaro_similarity_simd(Range<double*> scores,
const detail::BlockPatternMatchVector& block,
819 VecType* s1_lengths,
size_t s1_lengths_size,
const Range<InputIt>& s2,
820 double score_cutoff)
noexcept
822 if (score_cutoff > 1.0) {
823 for (
size_t i = 0; i < s1_lengths_size; i++)
830 for (
size_t i = 0; i < s1_lengths_size; i++)
831 scores[i] = s1_lengths[i] ? 0.0 : 1.0;
836 if (s2.size() >
sizeof(VecType) * 8)
837 return jaro_similarity_simd_long_s2(scores, block, s1_lengths, s2, score_cutoff);
839 return jaro_similarity_simd_short_s2(scores, block, s1_lengths, s2, score_cutoff);
844class Jaro :
public SimilarityBase<Jaro, double, 0, 1> {
845 friend SimilarityBase<Jaro, double, 0, 1>;
846 friend NormalizedMetricBase<Jaro>;
848 template <
typename InputIt1,
typename InputIt2>
849 static double maximum(
const Range<InputIt1>&,
const Range<InputIt2>&)
noexcept
854 template <
typename InputIt1,
typename InputIt2>
855 static double _similarity(
const Range<InputIt1>& s1,
const Range<InputIt2>& s2,
double score_cutoff,
858 return jaro_similarity(s1, s2, score_cutoff);