对大长度列表中的字符串进行模糊比较(考虑性能)

Fuzzy comparison of strings in lists of huge length (taking into account performance)

提问人:Paul 提问时间:8/24/2023 更新时间:8/26/2023 访问量:114

问:

我有两个清单:

我从数据库中得到的第一个列表是各种公司的名称(可以用大写、小写或组合书写)

list_from_DB = [“锐步”, “马自达”, “巡逻”, “AsbEngland-bank”, “马自达INCC”, “HIGHWAY lcc”, “马自达”, “015 Amazon”, ......]

此列表中大约有 400,000 个项目。


第二个列表我通过解析用户的文本来获得(绝对可以有任何单词、数字、符号等)

list_from_user = ['ts', '$243', '马自达', '军官', '签名', 'Date:07/20/2022', 'Wilson', 'Bank', .......]

此列表中大约有 1000 个项目。


我需要做的是找到list_from_user中的哪些项目处于list_from_DB状态,并按最相似的顺序显示它们。如下图所示,两个列表中的项目可能相同,或者拼写不同。

输出

[“马自达”, “马自达”, “马自达INCC”, “AsbEngland-bank”]


我做什么:是的,我知道模糊字符匹配库,我使用 rapidfuzz。

res = []
for e in list_from_user:
    r = rapidfuzz.process.extract_iter(e, list_from_DB, processor=str.lower, scorer=rapidfuzz.fuzz.ratio, score_cutoff=95)
    res += r

是的,结果是有效的,但很长,大约 30 秒,因为循环必须执行 1000 * 400.000 = 400.000.000 次操作。

因此,问题如下:是否有可能在不枚举所有选项的情况下以其他方式解决这个问题?(我不反对枚举所有选项的方法,但如果它适合时间)

我的时间目标是最多 3 秒。

python 性能 搜索模糊 比较

评论

0赞 user2390182 8/24/2023
你的数据库后端是什么?例如,postgres 具有三元组相似性函数。然后,您可以将模糊搜索交给数据库,数据库提供按相似性排序的结果。
1赞 MisterMiyagi 8/24/2023
你对类似字符串的标准到底是什么?该问题显示使用具有特定设置的特定库,但既然您不想使用它 - 替代方案的标准是什么?
0赞 user2390182 8/24/2023
另一件值得探索的事情是伯克哈德-凯勒树。您可以从数据库数据创建一个,并针对用户的每个查询词重复使用它。
0赞 Paul 8/24/2023
@user2390182 是的,我们正在使用 postgres。我知道卦象。但我无法理解你的思路。如果我将每个项目从list_from_user传输到数据库以确定卦,时间会更长。也许你的意思是别的,请解释一下
0赞 user2390182 8/24/2023
是的,1000 dB 的命中也可能很慢......请注意 BK 树建议: geeksforgeeks.org/bk-tree-introduction-implementation 此数据结构只需构建一次,旨在有效地查找相似项目。

答:

1赞 ken 8/26/2023 #1

如果您当前所做的事情的结果符合您的需求,那么 cdist 是更好的选择。

下面的代码是如何保存我们可以从 cdist 获得的所有内容。

import numpy as np
import rapidfuzz


def find_fuzzy(list_from_user, list_from_DB, score_cutoff: int):
    score_matrix = rapidfuzz.process.cdist(
        list_from_user,
        list_from_DB,
        processor=str.lower,
        scorer=rapidfuzz.fuzz.ratio,
        dtype=np.uint8,  # Output the score as uint8, which is faster.
        workers=-1,  # Use multithreading. -1 means use all cores.
        score_cutoff=score_cutoff,
    )

    results = []
    user_indices, db_indices = np.nonzero(score_matrix)
    for user_index_of_match, db_index_of_match in zip(user_indices, db_indices):
        results.append(
            {
                "user_index_of_match": user_index_of_match,
                "db_index_of_match": db_index_of_match,
                "user_item_of_match": list_from_user[user_index_of_match],
                "db_item_of_match": list_from_DB[db_index_of_match],
                "score_of_match": score_matrix[user_index_of_match, db_index_of_match],
            }
        )
    return results

评论

0赞 Paul 8/27/2023
你的回答令人难以置信。这是解决这个问题的一种很酷的方法,非常感谢
0赞 Paul 8/27/2023
在研究代码和各种可能性时,我有以下想法:是否有可能立即获得没有零的score_matrix?这是为了避免使用 np.nonzero(score_matrix)。这再次导致代码的最佳性能。只是我在文档中看到,有一个参数scorer_kwargs .也许它可以以某种方式使用?
0赞 ken 8/27/2023
@Paul 不幸的是,这是不可能的。被传递给记分器(在上面的代码中,函数),并且不会改变其本身的行为。更深入地看,这是 cdist 实现。矩阵是在函数的第一步分配的,因此没有办法绕过它。scorer_kwargsrapidfuzz.fuzz.ratiocdist