补充
This commit is contained in:
166
lib/core/DifferenceCalculation.py
Normal file
166
lib/core/DifferenceCalculation.py
Normal file
@@ -0,0 +1,166 @@
|
||||
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']}")
|
||||
Reference in New Issue
Block a user