Files
TransPyC/lib/core/DifferenceCalculation.py
2026-06-16 16:09:42 +08:00

167 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
def LevenshteinDistance(s1: str, s2: str) -> int:
"""
计算两个字符串的 Levenshtein 距离(编辑距离)
:param s1: 原始字符串
:param s2: 目标字符串
:return: 最少编辑操作次数(差异程度)
"""
# 创建二维动态规划表dp[i][j] 表示 s1[:i] 到 s2[:j] 的编辑距离
m, n = len(s1), len(s2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 初始化:空字符串到 s1[:i] 需要 i 次删除操作
for i in range(m + 1):
dp[i][0] = i
# 初始化:空字符串到 s2[:j] 需要 j 次插入操作
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 s1[i-1] == s2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
# 字符不同,取「删除、插入、替换」中最小操作数 +1
dp[i][j] = min(
dp[i-1][j], # 删除 s1[i-1]
dp[i][j-1], # 插入 s2[j-1] 到 s1
dp[i-1][j-1] # 替换 s1[i-1] 为 s2[j-1]
) + 1
return dp[m][n]
def LongestCommonSubsequence(s1: str, s2: str) -> tuple[list[tuple[int, int]], str]:
"""
计算两个字符串的最长公共子序列LCS并返回 LCS 字符的位置映射
:param s1: 原始字符串
:param s2: 目标字符串
:return: (LCS 位置列表 [(S1Idx, S2Idx)], LCS 字符串)
"""
m, n = len(s1), len(s2)
# dp[i][j] 表示 s1[:i] 和 s2[:j] 的 LCS 长度
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 填充 LCS 长度表
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i-1] == s2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
# 回溯找 LCS 的具体字符和位置
LcsChars = []
LcsPositions = []
i, j = m, n
while i > 0 and j > 0:
if s1[i-1] == s2[j-1]:
LcsChars.append(s1[i-1])
LcsPositions.append((i-1, j-1)) # 存储原始索引从0开始
i -= 1
j -= 1
elif dp[i-1][j] > dp[i][j-1]:
i -= 1
else:
j -= 1
# 回溯得到的是逆序,需要反转
LcsChars.reverse()
LcsPositions.reverse()
return LcsPositions, ''.join(LcsChars)
def GetStringDiff(s1: str, s2: str) -> dict:
"""
完整对比两个字符串,返回差异详情
:param s1: 原始字符串
:param s2: 目标字符串
:return: 差异字典包含编辑距离、LCS、差异位置/内容)
"""
# 1. 计算编辑距离(差异程度)
EditDist = LevenshteinDistance(s1, s2)
# 2. 计算 LCS公共部分
LcsPos, LcsStr = LongestCommonSubsequence(s1, s2)
# 3. 定位差异位置和内容
DiffDetails = {
"delete": [], # s1 中需要删除的字符 (索引, 字符)
"insert": [], # s2 中需要插入的字符 (索引, 字符)
"replace": [] # s1 中需要替换的字符 (s1索引, 原字符, 目标字符)
}
# 生成 s1 和 s2 的差异标记
S1Idx = 0
S2Idx = 0
for (LcsS1Idx, LcsS2Idx) in LcsPos:
# 处理 s1 中需要删除的字符LCS 前的非公共部分)
while S1Idx < LcsS1Idx:
DiffDetails["delete"].append((S1Idx, s1[S1Idx]))
S1Idx += 1
# 处理 s2 中需要插入的字符LCS 前的非公共部分)
while S2Idx < LcsS2Idx:
DiffDetails["insert"].append((S2Idx, s2[S2Idx]))
S2Idx += 1
# 公共字符,跳过
S1Idx += 1
S2Idx += 1
# 处理末尾剩余的非公共部分
while S1Idx < len(s1):
DiffDetails["delete"].append((S1Idx, s1[S1Idx]))
S1Idx += 1
while S2Idx < len(s2):
DiffDetails["insert"].append((S2Idx, s2[S2Idx]))
S2Idx += 1
# 优化:将连续的删除+插入合并为替换(更符合直观)
ReplaceCandidates = list(zip(DiffDetails["delete"], DiffDetails["insert"]))
for (DelItem, InsItem) in ReplaceCandidates:
DelIdx, DelChar = DelItem
InsIdx, InsChar = InsItem
DiffDetails["replace"].append((DelIdx, DelChar, InsChar))
DiffDetails["delete"].remove(DelItem)
DiffDetails["insert"].remove(InsItem)
return {
"EditDistance": EditDist, # 差异程度(数值越小越相似)
"similarity": 1 - EditDist / max(len(s1), len(s2), 1), # 相似度0-1
"LcsString": LcsStr, # 最长公共子序列
"LcsLength": len(LcsStr), # LCS 长度
"DiffDetails": DiffDetails # 具体差异(删/插/改)
}
# 测试用例
if __name__ == "__main__":
# 示例1轻微差异替换+插入)
s1 = "Hello World!"
s2 = "Hello Python!"
DiffResult = GetStringDiff(s1, s2)
print("=== 示例1轻微差异 ===")
print(f"编辑距离:{DiffResult['EditDistance']}")
print(f"相似度:{DiffResult['similarity']:.2f}")
print(f"最长公共子序列:{DiffResult['LcsString']}")
print(f"差异详情:{DiffResult['DiffDetails']}")
# 示例2完全不同
s3 = "abc123"
s4 = "xyz789"
DiffResult2 = GetStringDiff(s3, s4)
print("\n=== 示例2完全不同 ===")
print(f"编辑距离:{DiffResult2['EditDistance']}")
print(f"相似度:{DiffResult2['similarity']:.2f}")
print(f"最长公共子序列:{DiffResult2['LcsString']}")
print(f"差异详情:{DiffResult2['DiffDetails']}")
# 示例3内容一致
s5 = "TestString"
s6 = "TestString"
DiffResult3 = GetStringDiff(s5, s6)
print("\n=== 示例3内容一致 ===")
print(f"编辑距离:{DiffResult3['EditDistance']}")
print(f"相似度:{DiffResult3['similarity']:.2f}")
print(f"最长公共子序列:{DiffResult3['LcsString']}")
print(f"差异详情:{DiffResult3['DiffDetails']}")