Я использую алгоритм расстояния Левенштейна в C ++, чтобы сравнить две строки, чтобы измерить, насколько они близки друг к другу. Однако простой алгоритм Левенштейновского расстояния не различает границы слов, разделенные пробелами. Это приводит к меньшим вычислениям расстояния, чем я хочу. Я сравниваю названия, чтобы увидеть, насколько они близки друг к другу, и хочу, чтобы алгоритм не считал символы совпадающими, если они встречаются в нескольких словах.
Например, если я сравниваю эти две строки, я получаю следующий результат с +
обозначение матча и -
обозначение несоответствия:
Al Chertoff Et
Al Church Department of finance Et
+++++------+--++-----++-+------+++
Al Ch e rt of f Et
Я получаю расстояние 20 со словом "Chertoff"
соответствие через четыре слова "Church Department of finance"
в то время как я действительно хочу, чтобы они рассматривались дальше друг от друга, не позволяя символам совпадать более чем из одного слова и получая расстояние 25 со словом "Chertoff"
наиболее соответствует одному слову "Department"
, с совпадением трех символов:
Al Chertoff Et
Al Church Department of finance Et
+++--------+--++---------------+++
Al e rt Et
Ch off
Как я мог бы адаптировать расстояние Левенштейна для достижения этого или есть другой алгоритм расстояния, который был бы более подходящим для этого? Возможно, используя расстояние Левенштейна для каждого слова в отдельности, слово «работа» и выбирая слово с наименьшим расстоянием? Тем не менее, что если сопоставление одного слова в глубине строки приведет к тому, что последующие слова будут плохо совпадать, потому что их совпадения были лучшими в начале строки? Можно ли это как-то сделать с помощью расстояния Левенштейна, адаптированного к уровню слова?
Например, кратчайшее расстояние по этой идее для следующего более сложного примера составляет 20:
Al Chertoff Deport Et
Al Church Department of finance Et
+++++----++++-++---------------+++
Al Ch Dep rt Et
ertoff o
Вместо максимизации "Chertoff"
Подходим и получаем большее расстояние 24:
Al Chertoff Deport Et
Al Church Department of finance Et
+++--------+--++-----+---------+++
Al e rt o Et
Ch off
Dep rt
Моя текущая реализация расстояния Левенштейна заключается в следующем:
size_t
levenshtein_distance(const std::string& a_compare1,
const std::string& a_compare2) {
const size_t length1 = a_compare1.size();
const size_t length2 = a_compare2.size();
std::vector<size_t> curr_col(length2 + 1);
std::vector<size_t> prev_col(length2 + 1);
// Prime the previous column for use in the following loop:
for (size_t idx2 = 0; idx2 < length2 + 1; ++idx2) {
prev_col[idx2] = idx2;
}
for (size_t idx1 = 0; idx1 < length1; ++idx1) {
curr_col[0] = idx1 + 1;
for (size_t idx2 = 0; idx2 < length2; ++idx2) {
const size_t compare = a_compare1[idx1] == a_compare2[idx2] ? 0 : 1;
curr_col[idx2 + 1] = std::min(std::min(curr_col[idx2] + 1,
prev_col[idx2 + 1] + 1),
prev_col[idx2] + compare);
}
curr_col.swap(prev_col);
}
return prev_col[length2];
}
Я могу получить довольно близко к тому, что вы хотите, сделав levenshtein_distance
универсальный алгоритм для контейнера последовательности, включающий функцию стоимости, которая вычисляет расстояние между двумя элементами:
template<typename T, typename C>
size_t
seq_distance(const T& seq1, const T& seq2, const C& cost,
const typename T::value_type& empty = typename T::value_type()) {
const size_t size1 = seq1.size();
const size_t size2 = seq2.size();
std::vector<size_t> curr_col(size2 + 1);
std::vector<size_t> prev_col(size2 + 1);
// Prime the previous column for use in the following loop:
prev_col[0] = 0;
for (size_t idx2 = 0; idx2 < size2; ++idx2) {
prev_col[idx2 + 1] = prev_col[idx2] + cost(empty, seq2[idx2]);
}
for (size_t idx1 = 0; idx1 < size1; ++idx1) {
curr_col[0] = curr_col[0] + cost(seq1[idx1], empty);
for (size_t idx2 = 0; idx2 < size2; ++idx2) {
curr_col[idx2 + 1] = std::min(std::min(
curr_col[idx2] + cost(empty, seq2[idx2]),
prev_col[idx2 + 1] + cost(seq1[idx1], empty)),
prev_col[idx2] + cost(seq1[idx1], seq2[idx2]));
}
curr_col.swap(prev_col);
curr_col[0] = prev_col[0];
}
return prev_col[size2];
}
Учитывая вышеизложенное seq_distance
расстояние редактирования между двумя предложениями, так что редактирование не может быть сделано между границами слова, может быть определено с помощью следующего:
size_t
letter_distance(char letter1, char letter2) {
return letter1 != letter2 ? 1 : 0;
}
size_t
word_distance(const std::string& word1, const std::string& word2) {
return seq_distance(word1, word2, &letter_distance);
}
size_t
sentence_distance(const std::string& sentence1, const std::string& sentence2) {
std::vector<std::string> words1;
std::vector<std::string> words2;
std::istringstream iss1(sentence1);
std::istringstream iss2(sentence2);
std::copy(std::istream_iterator<std::string>(iss1),
std::istream_iterator<std::string>(),
std::back_inserter(words1));
std::copy(std::istream_iterator<std::string>(iss2),
std::istream_iterator<std::string>(),
std::back_inserter(words2));
return seq_distance(words1, words2, &word_distance);
}
Вот код, работающий над ideone. Я проверил несколько случаев, и я почти уверен, что это правильно, но вы должны попробовать это больше, чтобы убедиться, что результаты разумны.
Обратите внимание, что это не совсем то, что вы просили, поскольку в нем не учитываются все пробелы в измерении расстояния редактирования: я думаю, что не должно быть слишком сложно изменить его, чтобы не делать этого, но я не продумал это полностью. В любом случае, это может быть так же хорошо (или даже лучше), в зависимости от ваших потребностей, поэтому я позволю вам решить, хотите ли вы попытаться настроить его.
Небольшое замечание, ваш исходный код был слегка ошибочным в следующих двух строках:
curr_col.reserve(length2 + 1);
prev_col.reserve(length2 + 1);
резервируйте емкость в векторах, но на самом деле не изменяйте их размеры, поэтому доступ к массиву после этого был неопределенным поведением. Вы должны на самом деле resize
вектор, если вы собираетесь получить доступ к элементам в диапазоне: reserve
обычно для ситуаций, когда вы собираетесь push_back
определенное количество элементов по одному (что увеличивает размер по мере того, как вы идете, а не все сразу), и вы хотите избежать затрат на несколько внутренних перераспределений (поскольку внутренняя емкость увеличивается только на определенный коэффициент каждый раз, когда емкость превышен).
РЕДАКТИРОВАТЬ:
Эта версия учитывает пробелы между словами как часть расстояния редактирования, но результаты все равно не совпадают с вашими примерами из-за необходимости добавления нескольких пробелов в некоторых случаях.
Границы слов будут пересекаться, если отдельные слова не имеют одинаковую длину. Если вы хотите, чтобы индексы сравнивались в соответствующих словах, вам нужно сделать слова одинаковой длины. Например, вот Javascript (да, я знаю, что вы спрашивали, или C ++, но это для иллюстрации — код, взятый из Википедии): процедура вычисления расстояния:
var memo = {};
function d(str1, i, len1, str2, j, len2){
var key = [i,len1,j,len2].join(',');
if(memo[key] != undefined) return memo[key];
if(len1 == 0) return len2;
if(len2 == 0) return len1;
var cost = 0;
if(str1[i] != str2[j]) cost = 1;
var dist = Math.min(
d(str1, i+1,len1-1, str2,j,len2)+1,
d(str1,i,len1,str2,j+1,len2-1)+1,
d(str1,i+1,len1-1,str2,j+1,len2-1)+cost);
memo[key] = dist;
return dist;
}
var str1 = "Al Chertoff Deport$$$$ $$ $$$$$$$ Et";
var str2 = "Al Church$$ Department of finance Et";
console.log(d(str1, 0, str1.length, str2, 0, str2.length));
Обратите внимание, как я изменил две входные строки, чтобы они соответствовали на уровне отдельных слов. Запустив это, я получил расстояние 19. Аналогично, если я изменяю строки на:
var str1 = "Al Chertoff $$$$$$$$$$ $$ $$$$$$$ Et";
var str2 = "Al Church$$ Department of finance Et";
Я получаю расстояние 24.