【问题标题】:Optimizing Jaro-Winkler algorithm优化 Jaro-Winkler 算法
【发布时间】:2011-02-20 09:15:24
【问题描述】:

我的 Jaro-Winkler 算法代码取自 this 网站。我需要运行 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 优化的线程中找到 here

【问题讨论】:

    标签: java algorithm optimization jaro-winkler


    【解决方案1】:

    是的,但你不会喜欢它。将所有newed StringBuffers 替换为在构造函数中分配的 char 数组,不再重复,使用整数索引来跟踪其中的内容。

    This pending Commons-Lang patch 会给你一些味道。

    【讨论】:

    • 我持怀疑态度,但我进行了一些测试,似乎 char 数组的速度确实是 StringBuffer 的十倍左右。如果您想避免使用实际的 char 数组,StringBuilder 的速度只有 char 数组的两倍左右。
    【解决方案2】:

    我知道这个问题可能已经解决了一段时间,但我想评论一下算法本身。将字符串与自身进行比较时,答案是 1/|string|离开。当比较稍微不同的值时,这些值也会变得更低。

    解决方案是在 getCommonCharacters 方法的内部 for 语句中将“m-1”调整为“m”。然后代码就像一个魅力:)

    请参阅http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance 了解一些示例。

    【讨论】:

      【解决方案3】:
      1. 尽量避免 getCommonCharacters 循环中的两个嵌套循环。
        关于如何的建议:将较小字符串中的所有字符存储在某种映射中(java有一些),其中键是字符,值是位置,这样你仍然可以计算距离,不管它们是共同的。算法我不是很懂,不过我觉得可行。
      2. 除了那个和 bmargulies 的回答之外,我真的没有看到除了位等之外的进一步优化。如果这真的很关键,考虑用 C 重写这部分吗?

      【讨论】:

        【解决方案4】:

        我不太了解 Android 以及它如何与数据库一起工作。 WP7 有(将有:))SQL CE。下一步通常是处理您的数据。添加字符串长度并限制您的比较。在两列上添加索引并按长度排序,然后按值排序。长度索引也应该排序。我让它运行在一个有 150 000 个医学术语的旧服务器上,在 0.5 秒内给我建议和拼写检查,用户几乎不会注意到它,尤其是在单独的线程上运行时。

        我打算在博客上写很长时间(比如 2 年 :)),因为有需要。但我终于设法写了几句话并提供一些提示。请在此处查看:

        ISolvable.blogspot.com

        虽然是针对微软平台的,但总体原理还是一样的。

        【讨论】:

          【解决方案5】:

          是的,这可以做得更快。一方面,您根本不需要 StringBuffers。另一方面,您不需要单独的循环来计算转置。

          你可以找到my implementation here,应该会快很多。它在 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;
                    }
                }
            }
            

            另一个优化是为每个字符串预先计算一个位掩码。 使用它,检查第一个字符串上的当前字符是否存在于第二个字符串上。这可以使用高效的按位运算来完成。

            这将跳过计算最大/最小和循环丢失字符。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2017-06-01
              • 2021-12-05
              • 2016-01-01
              • 1970-01-01
              • 2019-07-20
              • 2016-04-23
              • 1970-01-01
              相关资源
              最近更新 更多