优化Jaro-Winkler算法

14
我有这段 Jaro-Winkler 算法的代码,是从这个网站上获取的。我需要运行 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优化的线程中找到它
6个回答

7

是的,但你不会喜欢它。用在构造函数中分配且从未再次分配的字符数组替换所有那些new的StringBuffer,并使用整数索引来跟踪其中的内容。

这个即将发布的Commons-Lang补丁将为您提供一些味道。


我一开始持怀疑态度,但经过测试,似乎字符数组的速度真的比StringBuffer快大约十倍。如果你想避免使用实际的字符数组,那么StringBuilder的速度只比字符数组慢两倍左右。 - Rubys

4
我知道这个问题可能已经解决了一段时间,但我想评论一下算法本身。当将一个字符串与自己进行比较时,答案会偏离1/|string|。当比较稍有不同的值时,值也会变小。
解决方法是在getCommonCharacters方法内部的for语句中将'm-1'调整为'm'。代码就能完美地运行了 :)
另请参见http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance以获取一些示例。

0
  1. 尽量避免在getCommonCharacters循环中使用两个嵌套循环。
    建议:将较小字符串中的所有字符存储在某种映射(Java有几种)中,其中键是字符,值是位置,这样您仍然可以计算距离,无论它们是否相同。我不太理解算法,但我认为这是可行的。
  2. 除此之外,除了bmargulies的答案外,我真的看不到更多的优化,除了像位等东西。如果这真的很关键,请考虑用C重写此部分?

0

我对Android和它如何与数据库配合工作并不了解。WP7有(将会有:))SQL CE。下一步通常是处理数据。添加字符串长度并限制比较。在两个列上添加索引,按长度和值排序。长度索引也应该排序。我曾经在一个旧服务器上运行过150,000个医学术语的建议和拼写检查,用时不到0.5秒,用户几乎察觉不到,特别是如果在单独的线程上运行。

我打算很久以前(大约2年:))就写博客,因为有这个需求。但最终我还是设法写了几句话,并提供了一些技巧。请在这里查看:

ISolvable.blogspot.com

虽然它是为微软平台设计的,但通用原则仍然相同。


0

是的,这可以做得更快。首先,您根本不需要使用StringBuffers。另外,您也不需要单独循环来计算转位。

您可以在这里找到我的实现,它应该会更快。它遵循Apache 2.0许可证。


0

不要使用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;
        }
    }
}

另一个优化方法是为每个字符串预先计算一个位掩码。使用该位掩码,检查第一个字符串上的当前字符是否存在于第二个字符串中。这可以通过高效的位运算来完成。
这将跳过计算最大/最小值和循环缺失字符的步骤。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接