在 Python 中编写检查是否存在几乎相同字符串对的程序
随着互联网及大数据的发展,海量文本数据的存储和分析变得越来越重要,字符串的查找和比较也成为了一个重要的算法问题。在实际应用中,我们通常不需要比较完全相同的字符串,而是关注那些仅仅有细微差异的字符串,比如错别字、省略或添加了某些字符等,这些差异带来的信息也许比完全相同的字符串更有价值。
本文将介绍如何在 Python 中使用常见的字符串处理方法来检查是否存在几乎相同的字符串对。
Jaccard 相似度
Jaccard 相似度是一种衡量集合相似度的指标,常用于测量两个文本或者两个物品之间的相似度。在 NLP(自然语言处理)中,我们通常将文本表示为词集,而一篇文本就对应着一个词集合。这个文本的 Jaccard 相似度就可以通过计算这个词集合之间的交集和并集得出。
公式如下:
J(A, B) = \frac{|A \cap B|}{|A \cup B|}
其中,A 和 B 是两个集合,|A| 表示集合 A 中元素的个数。
我们可以将字符串看作一个字符集合,计算两个字符串之间的 Jaccard 相似度,进而得出它们之间的相似程度。
下面是一个计算 Jaccard 相似度的 Python 代码示例:
def jaccard_sim(str_1, str_2):
set_1 = set(str_1)
set_2 = set(str_2)
common = len(set_1 & set_2)
total = len(set_1 | set_2)
return common / total
这个函数接受两个字符串作为参数,并返回它们之间的 Jaccard 相似度。
例如,我们可以计算两个字符串 “apple” 和 “orange”:
>>> jaccard_sim("apple", "orange")
0.0
由于它们之间没有任何相同字符,因此它们的 Jaccard 相似度为 0。
而对于 “apple” 和 “apples”:
>>> jaccard_sim("apple", "apples")
0.8333333333333334
它们之间有三个相同字符,因此它们的 Jaccard 相似度为 0.83。
汉明距离
汉明距离是一种衡量两个串之间的编辑距离的指标,它表示将一个字符串变成另一个字符串所需的最小替换操作次数。例如,将 “apple” 替换为 “orange” 需要进行 5 次替换操作,它们之间的汉明距离为 5。
公式如下:
d(s, t) = \sum_{i=0}^{n} 1_{s_i \neq t_i}
其中,s 和 t 是两个字符串,n 表示两字符串长度的最大值,1_{s_i \neq t_i} 在 s 和 t 在当前位置的字符不同的时候等于 1,否则等于 0。
在 Python 中,我们可以通过简单的循环来计算两个字符串之间的汉明距离:
def hamming_distance(str_1, str_2):
distance = 0
for ch_1, ch_2 in zip(str_1, str_2):
distance += ch_1 != ch_2
distance += abs(len(str_1) - len(str_2))
return distance
这个函数接受两个字符串作为参数,并返回它们之间的汉明距离。
例如,我们可以计算两个字符串 “apple” 和 “orange” 之间的汉明距离:
>>> hamming_distance("apple", "orange")
5
它们之间需要进行 5 次替换操作才能相互转换,因此它们的汉明距离为 5。
而对于 “apple” 和 “apples”:
>>> hamming_distance("apple", "apples")
1
它们之间只需要进行一次插入操作,因此它们的汉明距离为 1。
编辑距离
编辑距离是一种衡量两个字符串之间相似度的指标,它代表了将一个字符串转化成另一个字符串所需的最小操作次数。它主要包括三种操作:插入、删除、替换。例如,将 “apple” 转化为 “orange” 需要进行 3 次操作(删除 “a”,替换 “p” 和 “l”,插入 “n”),它们之间的编辑距离为 3。
在 Python 中,我们可以使用动态规划算法来计算两个字符串之间的编辑距离。具体来说,我们可以构建一个二维数组 d_{i,j} 来记录字符串 str_1 的前 i 个字符和字符串 str_2 的前 j 个字符之间的编辑距离,其中 i 和 j 都从 0 开始,d_{0,j}=j,d_{i,0}=i。
对于 d_{i,j},有以下三种情况:
- 如果 str_1[i]=str_2[j],则 d_{i,j}=d_{i-1,j-1};
- 否则,可以通过三种操作将 str_1 转化为 str_2:
- 插入操作:d_{i,j}=d_{i,j-1}+1;
- 删除操作:d_{i,j}=d_{i-1,j}+1;
- 替换操作:d_{i,j}=d_{i-1,j-1}+1;
- 以上三种操作中选择操作次数最少的进行。
最终,d_{m,n} 就是字符串 str_1 和 str_2 之间的编辑距离,其中 m 和 n 分别是字符串 str_1 和 str_2 的长度。
下面是一个计算编辑距离的 Python 代码示例:
def edit_distance(str_1, str_2):
m, n = len(str_1), len(str_2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1):
dp[i][0] = i
for j in range(n + 1):
dp[0][j] = j
for i in range(1, m + 1):
for j in range(1, n + 1):
if str_1[i - 1] == str_2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = 1 + min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1])
return dp[m][n]
这个函数接受两个字符串作为参数,并返回它们之间的编辑距离。
例如,我们可以计算两个字符串 “apple” 和 “orange” 之间的编辑距离:
>>> edit_distance("apple", "orange")
3
它们之间需要进行 3 次操作才能相互转换,因此它们的编辑距离为 3。
而对于 “apple” 和 “apples”:
>>> edit_distance("apple", "apples")
1
它们之间只需要进行一次插入操作,因此它们的编辑距离为 1。
应用实例
我们可以将上述三种方法结合起来,在 Python 中编写一个程序来检查是否存在几乎相同的字符串对。具体来说,对于一组字符串序列,我们首先计算它们之间的 Jaccard 相似度,对相似度高于一定阈值的字符串对(比如 0.8),进一步计算它们之间的汉明距离和编辑距离。最终,输出相似度高的字符串对及其之间的距离。
下面是一个具体的 Python 代码示例:
def check_similar_strs(strs, threshold):
similar_pairs = []
for i in range(len(strs)):
for j in range(i + 1, len(strs)):
sim = jaccard_sim(strs[i], strs[j])
if sim >= threshold:
hamming_dist = hamming_distance(strs[i], strs[j])
edit_dist = edit_distance(strs[i], strs[j])
similar_pairs.append((strs[i], strs[j], sim, hamming_dist, edit_dist))
return similar_pairs
这个函数接受一个字符串序列和阈值作为参数,并返回一个包含相似度高的字符串对以及它们之间的汉明距离和编辑距离的列表。
例如,对于一个字符串序列 [“apple”, “apples”, “banana”, “oranges”, “peaches”],可以调用这个函数来获取相似度高于 0.8 的字符串对:
>>> check_similar_strs(["apple", "apples", "banana", "oranges", "peaches"], 0.8)
[('apple', 'apples', 0.8333333333333334, 1, 1)]
可以看到,”apple” 和 “apples” 之间的 Jaccard 相似度高于 0.8,它们之间的汉明距离和编辑距离都比较小。
结论
本文介绍了在 Python 中使用 Jaccard 相似度、汉明距离和编辑距离来检查是否存在几乎相同的字符串对。这些方法不仅可以应用于文本相似度计算,还可以用于重名检测、数据清洗等应用场景。在实际应用中,我们需要根据具体的需求来选择合适的方法,并进行必要的参数调整,以获得准确且有效的结果。