Оптимизация алгоритма Яро-Винклера

У меня есть этот код для алгоритма Джаро-Винклера, взятый из этого сайт. Мне нужно пробежать 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;
    }
}

Я упоминаю, что во всем процессе я делаю только экземпляр скрипта, поэтому только один раз

jaro= new Jaro();

Если вы собираетесь тестировать и вам нужны примеры, чтобы не сломать скрипт, вы найдете его здесь , в другом потоке для оптимизации Python


person Pentium10    schedule 17.05.2010    source источник


Ответы (6)


Да, но тебе это не понравится. Замените все эти newed StringBuffers массивами символов, которые выделены в конструкторе, и никогда больше, используя целочисленные индексы для отслеживания того, что в них находится.

Это ожидающее исправления Commons-Lang добавит вам колорита.

person bmargulies    schedule 17.05.2010
comment
Я был настроен скептически, но провел несколько тестов, и оказалось, что массив символов действительно будет примерно в десять раз быстрее, чем StringBuffer. Если вы хотите избежать использования фактического массива символов, StringBuilder всего в два раза медленнее, чем массив символов. - person Rubys; 18.05.2010

Я знаю, что этот вопрос, вероятно, был решен в течение некоторого времени, но я хотел бы прокомментировать сам алгоритм. При сравнении строки с самой собой ответ оказывается 1/|string| выключенный. При сравнении немного отличающихся значений значения также оказываются ниже.

Решение этой проблемы состоит в том, чтобы настроить «m-1» на «m» во внутреннем операторе for в методе getCommonCharacters. Затем код работает как шарм :)

См. http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance, а также несколько примеров.

person mvantol1    schedule 27.10.2010

  1. Старайтесь избегать двух вложенных циклов в цикле getCommonCharacters.
    Предложение о том, как: хранить все символы в меньшей строке в какой-либо карте (в java их несколько), где ключом является символ, а значение это позиция, таким образом вы все равно можете рассчитать расстояние, если они общие. Я не совсем понимаю алгоритм, но я думаю, что это выполнимо.
  2. За исключением этого и ответа bmargulies, я действительно не вижу дальнейшей оптимизации, кроме таких вещей, как биты и т. Д. Если это действительно важно, рассмотрите возможность перезаписи этой части на C?
person Rubys    schedule 17.05.2010

Я мало знаю об Android и о том, как он работает с базами данных. WP7 имеет (будет иметь :) ) SQL CE. Следующим шагом, как правило, будет работа с вашими данными. Добавьте длины строк и ограничьте количество сравнений. Добавьте индексы для обоих столбцов и отсортируйте по длине, а затем по значению. Индекс по длине также должен быть отсортирован. Я запускал его на старом сервере с 150 000 медицинских терминов, которые давали мне предложения и проверяли орфографию менее чем за 0,5 секунды, пользователи почти не замечали этого, особенно если запускались в отдельном потоке.

Я собирался вести об этом блог в течение длительного времени (например, 2 года :) ), потому что есть необходимость. Но мне, наконец, удалось написать несколько слов об этом и дать несколько советов. Пожалуйста, проверьте это здесь:

ISolvable.blogspot.com

Хотя это для платформы Microsoft, общие принципы остаются теми же.

person Ivan    schedule 31.05.2011

Да, это можно сделать намного быстрее. Во-первых, вам вообще не нужны StringBuffers. Во-вторых, вам не нужен отдельный цикл для подсчета транспозиций.

Вы можете найти моя реализация здесь, и это должно быть намного быстрее. Это под лицензией Apache 2.0.

person larsga    schedule 15.09.2011

Вместо того, чтобы возвращать общие символы с помощью метода 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;
        }
    }
}

Другая оптимизация заключается в предварительном вычислении битовой маски для каждой строки. Используя это, проверьте, присутствует ли текущий символ в первой строке во второй. Это можно сделать с помощью эффективных побитовых операций.

Это пропустит вычисление максимума/минимума и цикл для отсутствующих символов.

person dvidben    schedule 03.03.2017