Оптимизация алгоритма Яро-Винклера
У меня есть этот код для алгоритма Яро-Винклера, взятый из этого веб-сайта. Мне нужно бежать 150 000 раз, чтобы получить расстояние между различиями. Это займет много времени, поскольку я запускаю мобильное устройство Android.
Можно ли его оптимизировать больше?
public class Jaro {
/**
* gets the similarity of the two strings using Jaro distance.
*
* @param string1 the first input string
* @param string2 the second input string
* @return a value between 0-1 of the similarity
*/
public float getSimilarity(final String string1, final String string2) {
//get half the length of the string rounded up - (this is the distance used for acceptable transpositions)
final int halflen = ((Math.min(string1.length(), string2.length())) / 2) + ((Math.min(string1.length(), string2.length())) % 2);
//get common characters
final StringBuffer common1 = getCommonCharacters(string1, string2, halflen);
final StringBuffer common2 = getCommonCharacters(string2, string1, halflen);
//check for zero in common
if (common1.length() == 0 || common2.length() == 0) {
return 0.0f;
}
//check for same length common strings returning 0.0f is not the same
if (common1.length() != common2.length()) {
return 0.0f;
}
//get the number of transpositions
int transpositions = 0;
int n=common1.length();
for (int i = 0; i < n; i++) {
if (common1.charAt(i) != common2.charAt(i))
transpositions++;
}
transpositions /= 2.0f;
//calculate jaro metric
return (common1.length() / ((float) string1.length()) +
common2.length() / ((float) string2.length()) +
(common1.length() - transpositions) / ((float) common1.length())) / 3.0f;
}
/**
* returns a string buffer of characters from string1 within string2 if they are of a given
* distance seperation from the position in string1.
*
* @param string1
* @param string2
* @param distanceSep
* @return a string buffer of characters from string1 within string2 if they are of a given
* distance seperation from the position in string1
*/
private static StringBuffer getCommonCharacters(final String string1, final String string2, final int distanceSep) {
//create a return buffer of characters
final StringBuffer returnCommons = new StringBuffer();
//create a copy of string2 for processing
final StringBuffer copy = new StringBuffer(string2);
//iterate over string1
int n=string1.length();
int m=string2.length();
for (int i = 0; i < n; i++) {
final char ch = string1.charAt(i);
//set boolean for quick loop exit if found
boolean foundIt = false;
//compare char with range of characters to either side
for (int j = Math.max(0, i - distanceSep); !foundIt && j < Math.min(i + distanceSep, m - 1); j++) {
//check if found
if (copy.charAt(j) == ch) {
foundIt = true;
//append character found
returnCommons.append(ch);
//alter copied string2 for processing
copy.setCharAt(j, (char)0);
}
}
}
return returnCommons;
}
}
Я упоминаю, что во всем процессе я делаю только экземпляр script, поэтому только один раз
jaro= new Jaro();
Если вы собираетесь тестировать и нуждаться в примерах, чтобы не сломать script, вы найдете здесь здесь, в другом потоке для оптимизации python
Ответы
Ответ 1
Да, но вам это не понравится. Замените все те new
ed StringBuffers массивами char, которые выделяются в конструкторе и никогда больше не используются, используя целые индексы, чтобы отслеживать, что в них.
Этот незавершенный патч Commons-Lang даст вам немного вкуса.
Ответ 2
Я знаю, что этот вопрос, вероятно, был решен в течение некоторого времени, но я хотел бы прокомментировать сам алгоритм. При сравнении строки с самим собой ответ оказывается равным 1/| string | выкл. При сравнении немного разных значений значения также оказываются ниже.
Решением этого является настройка "m-1" на "m" во внутреннем for-statement в методе getCommonCharacters. Затем код работает как шарм:)
См. http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance также для некоторых примеров.
Ответ 3
- Попробуйте избежать двух вложенных циклов в цикле getCommonCharacters.
Предложение о том, как: хранить все символы в меньшей строке на какой-либо карте (у java есть несколько), где ключ - это символ, а значение - позиция, так что вы все равно можете рассчитать расстояние, являются общими. Я не совсем понимаю алгоритм, но я думаю, что это выполнимо.
- За исключением того, что и ответы bmargulies, я действительно не вижу дальнейших оптимизаций за пределами таких вещей, как биты и т.д. Если это действительно важно, подумайте о переписывании этой части в C?
Ответ 4
Я мало что знаю о Android и как он работает с базами данных. WP7 имеет (будет:):) SQL CE. Следующим шагом будет, как правило, работа с вашими данными. Добавьте длины строк и ограничьте свои сравнения. Добавьте индексы в оба столбца и отсортируйте по длине, а затем по значению. Индекс по длине также должен быть отсортирован. Я запускал его на старом сервере с 150 000 медицинских терминов, давая мне советы и проверку орфографии менее чем за 0,5 секунды, пользователи почти не замечали этого, особенно если они работают в отдельной теме.
Я хотел долго об этом писать (например, 2 года:)), потому что есть необходимость. Но мне, наконец, удается написать несколько слов об этом и дать несколько советов. Пожалуйста, проверьте здесь:
ISolvable.blogspot.com
Хотя для платформы Microsoft все еще общие принципы одинаковы.
Ответ 5
Да, это можно сделать намного быстрее. Во-первых, вам вообще не нужны StringBuffers. Для другого вам не нужен отдельный цикл для подсчета транспозиций.
Вы можете найти мою реализацию здесь, и это должно быть намного быстрее. Это под лицензией Apache 2.0.
Ответ 6
Вместо того, чтобы возвращать общие символы с помощью метода GetCommonCharacters, используйте пару массивов для совпадения, аналогично версии C здесь https://github.com/miguelvps/c/blob/master/jarowinkler.c
/*Calculate matching characters*/
for (i = 0; i < al; i++) {
for (j = max(i - range, 0), l = min(i + range + 1, sl); j < l; j++) {
if (a[i] == s[j] && !sflags[j]) {
sflags[j] = 1;
aflags[i] = 1;
m++;
break;
}
}
}
Другая оптимизация - предварительно вычислить битмаску для каждой строки.
Используя это, проверьте, присутствует ли текущий символ в первой строке во второй. Это можно сделать, используя эффективные побитовые операции.
Это пропустит вычисление max/min и цикл для отсутствующих символов.