I wrote a short library function, based on an example from Rosetta, to compare two strings and determine similarity, using Jaro-Winkler.
A short copy-paste ready example:
main.cpp
#include <string>
#include <iostream>
#include "jaro_winkler.hpp"
int main() {
std::string a { "DWAYNE" };
std::string b { "DUANE" };
std::cout << "Similarity for '" << a
<< "' and '" << b
<< "': " << edit_distance::jaro_winkler(a, b)
<< std::endl;
std::cout << "Similarity for 'MARTHA' and 'MARHTA': "
<< edit_distance::jaro_winkler("MARTHA", "MARHTA")
<< std::endl;
std::cout << "Similarity for 'DIXON' and 'DICKSONX': "
<< edit_distance::jaro_winkler("DIXON", "DICKSONX")
<< std::endl;
std::cout << "Similarity for 'JELLYFISH' and 'SMELLYFISH': "
<< edit_distance::jaro_winkler("JELLYFISH", "SMELLYFISH")
<< std::endl;
}
jaro_winkler.hpp
#pragma once
#include <cstddef>
#include <cstdint>
#include <algorithm>
#include <string_view>
namespace edit_distance {
template <typename T = float>
inline T jaro(const std::string_view source,
const std::string_view target)
{
const unsigned sl = source.length();
const unsigned tl = target.length();
if (sl == 0 || tl == 0) return 0;
const auto match_distance = (sl == 1 && tl == 1)
? 0
: (std::max(sl, tl) / 2 - 1);
auto source_matches = new bool[sl] {0};
auto target_matches = new bool[tl] {0};
unsigned matches = 0;
for (unsigned i = 0; i < sl; ++i) {
const auto end = std::min(i + match_distance + 1, tl);
for (auto k = i > match_distance ? (i - match_distance) : 0u; k < end; ++k)
{
if (!target_matches[k] && source[i] == target[k]) {
source_matches[i] = true;
target_matches[k] = true;
++matches;
break;
}
}
}
if (matches == 0) {
delete[] source_matches;
delete[] target_matches;
return 0;
}
T t = 0.0;
unsigned k = 0;
for (unsigned i = 0; i < sl; ++i) {
if (source_matches[i]) {
while (!target_matches[k]) ++k;
if (source[i] != target[k]) t += 0.5;
++k;
}
}
const T m = matches;
delete[] source_matches;
delete[] target_matches;
return (m / sl + m / tl + (m - t) / m) / 3.0;
}
template <typename T = float>
inline T jaro_winkler(const std::string_view source,
const std::string_view target,
const unsigned prefix = 2,
const T boost_treshold = 0.7,
const T scaling_factor = 0.1)
{
const auto similarity = jaro<T>(source, target);
if (similarity > boost_treshold) {
int common_prefix = 0;
const int l = std::min((unsigned)std::min(source.length(), target.length()), prefix);
for (; common_prefix < l; ++common_prefix) {
if (source[common_prefix] != target[common_prefix]) break;
}
return similarity + scaling_factor * common_prefix * (1 - similarity);
} else {
return similarity;
}
}
} // namespace edit_distance
I'd be happy to hear any comments or critiques.