用 PHP Levenshtein 比较 5000 个字符串
Posted
技术标签:
【中文标题】用 PHP Levenshtein 比较 5000 个字符串【英文标题】:Compare 5000 strings with PHP Levenshtein 【发布时间】: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 可能毫无价值。 【参考方案1】:我认为对相似地址进行分组的更好方法是:
创建一个包含两张表的数据库 - 一张用于地址(和一个 id),一张用于地址中单词或文字数字的音译(使用地址表的外键)
将地址大写,将 [A-Z] 或 [0-9] 以外的任何内容替换为空格
按空格分割地址,计算每个“单词”的 soundex,保留任何只有数字的内容,并将其与您开始使用的地址的外键一起存储在 soundexes 表中
为每个地址(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;
计算您的源地址与查询返回的前几个值之间的 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 />";
【讨论】:
以上是关于用 PHP Levenshtein 比较 5000 个字符串的主要内容,如果未能解决你的问题,请参考以下文章
如何调整 Levenshtein 距离算法以将匹配限制为单个单词?
Spark Java API 计算 Levenshtein 距离