【问题标题】:Compare 5000 strings with PHP Levenshtein用 PHP Levenshtein 比较 5000 个字符串
【发布时间】:2009-12-24 11:30:57
【问题描述】:

我在一个数组中有 5000 个,有时甚至更多的街道地址字符串。我想将它们与 levenshtein 进行比较以找到相似的匹配项。如果不遍历所有 5000 并直接将它们与其他所有 4999 进行比较,我该如何做到这一点?

编辑:如果有人有建议,我也对替代方法感兴趣。总体目标是根据用户提交的街道地址找到相似的条目(并消除重复项)。

【问题讨论】:

  • 关于您的更新,您可能需要应用一些输入清理以使您的生活更轻松。 (例如:如果您在存储之前将“Ave”转换为“Avenue”,“Rd”转换为“Road”等,则使用 soundex 将成为更现实的选择。)
  • 如何定义相似地址?您是否有一些 Lehvenstein 距离的最大值是相似性的限制等?
  • 类似的将是“12 Bird Road, Apt 6”和“12 Bird Rd. #6”
  • “923 Bird Road, Apt 6”和“23 Bird Road, Apt 6”怎么样?它们相似吗? Lehvenstein 距离会说它们是,毕竟只有一个字符差异,但在地理上人们可以认为它们不是很相似。
  • @Juha:这是一个很好的观点。另外,我的地址集中在方圆一百英里内。所以使用原始的 Levenshtein 可能毫无价值。

标签: php database similarity street-address levenshtein-distance


【解决方案1】:

我认为对相似地址进行分组的更好方法是:

  1. 创建一个包含两张表的数据库 - 一张用于地址(和一个 id),一张用于地址中单词或文字数字的音译(使用地址表的外键)

  2. 将地址大写,将 [A-Z] 或 [0-9] 以外的任何内容替换为空格

  3. 按空格分割地址,计算每个“单词”的 soundex,保留任何只有数字的内容,并将其与您开始使用的地址的外键一起存储在 soundexes 表中

  4. 为每个地址(id $target)找到最相似的地址:

    SELECT similar.id, similar.address, count(*) 
    FROM adress similar, word cmp, word src
    WHERE src.address_id=$target
    AND src.soundex=cmp.soundex
    AND cmp.address_id=similar.id
    ORDER BY count(*)
    LIMIT $some_value;
    
  5. 计算您的源地址与查询返回的前几个值之间的 levenstein 差异。

(对大型数组进行任何类型的操作通常在数据库中更快)

【讨论】:

  • 我会在上述算法中使用标准化地址。也就是说,已删除缩写的地址等(“Ave.”更改为“Avenue”等)
【解决方案2】:

我认为您无法避免遍历数组,因为 levenstein() 函数只接受字符串而不是数组作为输入。

你可以这样做:

for($i=0;$i<count($array)-1;$i++)
{
    for($j=$i+1;$j<count($array);$j++)
    {
        $lev = levenshtein($array[$i],$array[$j]);
        if($lev == 0)
        {
            // exact match
        }
        else if($lev <= THRESHOLD)
        {
            // similar
        }
    }
}

【讨论】:

  • 我害怕这个,因为它会进行 2500 万次比较。
  • @phirschybar:实际上是1250万,我们只比较不同的对,如果比较pair(X,Y),我们跳过比较pair(Y,X)。
【解决方案3】:

您可以使用bk-tree 来加快搜索/比较速度。

http://blog.notdot.net/2007/4/Damn-Cool-Algorithms-Part-1-BK-Trees 说:

现在我们可以对 Levenshtein 距离进行一个特别有用的观察:它形成了一个度量空间。
[...]
假设我们有两个参数,查询,我们在搜索中使用的字符串,以及字符串可以与查询的最大距离并且仍然被返回。假设我们采用任意字符串,对其进行测试并将其与查询进行比较。调用合成距离 d。因为我们知道三角不等式成立,所以我们所有的结果与测试的距离必须最多为 d+n,并且距离测试的距离至少为 d-n。
[...]
测试表明,搜索距离为 1 的查询不超过树的 5-8%,搜索两个错误的查询不超过树的 17-25% - 与检查每个节点相比有了很大的改进!

编辑:但这对您的(“12 Bird Road, Apt 6”和“12 Bird Rd.#6”)问题没有帮助。仅通过您的蛮力 m*n 比较。

【讨论】:

    【解决方案4】:

    由于 Levenshtein 算法的性质(特别是它是两个字符串之间的比较这一事实),我看不出这是怎么可能的。

    您当然可以通过首先执行一些基本匹配要求来减少比较次数,但这超出了您所要求的范围。

    作为一个(很可能无关紧要的)选项,您始终可以使用soundex 之类的东西,它可以让您预先计算字符串值。 (我相信你也可以直接在 MySQL 中使用。)

    【讨论】:

      【解决方案5】:

      您可以根据 soundexes 对它们进行分组,然后将比较限制为最接近的 N 个案例...

       $mashed=array();
       foreach ($address as $key=>$val) {
            $mashed[$key]=soundex($val);
       }
       sort($mashed);
      

      然后遍历 $mashed 的键。

      C.

      【讨论】:

      • 我从未使用过 soundexes。知道它们与街道地址类型缩写(如“St”)的配合情况如何。 ?
      • Soundex (en.wikipedia.org/wiki/Soundex) 设计用于处理人名。如果将它们应用于地址,则将 soundex 算法应用于地址的每个部分是有意义的,例如 "23 Bird Road" -> SOUNDEX("Bird") 和 SOUNDEX("Road")
      • 此解决方案类似于O(2n)O(2nm)(均未简化),其中m 是地址中的每个单词。这是对O(n^2) 的巨大改进。
      • 它不能插入缩写 - 但 St 可以是 Street 或 Saint 或...?取决于上下文。 C.
      【解决方案6】:

      如果您想找到 所有 个相似的值,则必须将所有项目与所有其他项目进行比较。但是选择正确的数组函数会显着加快速度。这是一个简单的示例(结果数组可能会更好):

      $results = array();
      $count = count($entries);
      while ($count != 0) {
          # The entry to process
          $entry = array_shift($entries);
          # Get levenshtein distances to all others
          $result = array_map(
              'levenshtein',
              # array_map() needs two arrays, this one is an array consisting of
              # multiple entries of the value that we are processing
              array_fill($entry, 0, $count),
              $toCompare
          );
          $results[] = array($entry => $result);
          $count--;
      }
      

      【讨论】:

        【解决方案7】:

        鉴于您的问题,如果您想使用Lehvenstein distance,我认为除了将每个地址与其他地址进行比较之外别无他法。

        首先,你应该规范化地址,去掉缩写等等。

        • 大道 -> 大道
        • 路。 -> 道路

        对于类似的地址,您可以有一些固定的最大 Lehvenstein 距离 (N)。

        如果是这样,当您确定当前地址对的编辑距离大于 N 时,您可以中止 Lehvenstein 算法。为此,您需要编写 Lehvenstein 算法的自定义版本。这将使算法更快一点。

        还有一些相关的琐碎优化。例如:如果地址 A 的长度为 10 个字符,地址 B 的长度为 20 个字符,并且您认为 Lehvenstein 距离小于 8 的地址是相似的。您可以查看地址的长度并立即确定它们不相似。

        【讨论】:

          【解决方案8】:
          $stringA = "this is php programming language";
          $stringB = "this is complete programming script in which java php and  all other minor languages include";
          
          echo "string 1---->".$stringA."<br />";
          echo "string 2---->".$stringB."<br />";
          // changing string to arrays
          $array1 = explode(' ', $stringA);
          $array2 = explode(' ', $stringB);
          
          // getting same element from two strings
          $c = array_intersect($array1, $array2);
          // changing array to the string
          $d=implode(' ',$c);
          
          echo "string same elements---> ".$d."<br />";
          
          
          // getting difrent  element from two arrays
          $result = array_diff($array2, $array1);
          // changing array to the string
          $zem = implode(' ',$result);
          
          if (!empty($zem)) {
            echo "string diffrence---> ".$zem."<br />";
          } else {
            echo "string diffrence--->both strings are same <br />";
          }
          
          similar_text($stringA, $d, $p);
          echo " similarity between the string is ".$p."% <br />";
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-10-03
            • 1970-01-01
            • 2012-10-05
            • 2021-01-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多