参考博客链接:(3条消息) 文本内容差异对比算法和程序_程序世界的王子的博客-CSDN博客_文本差异对比
应用背景:
比对两份合同的不同之处
代码逻辑(博主“程序世界的王子”原文):
1、首先我们的目光会将将新旧文本分别从第一行开始,逐行去看两行的内容是否一样。
2、当我们第一次看到两行的内容不一样时,我们的大脑就会想,已经开始进入内容有区别的区域了(下文称差异区域)。
3、随后我们会在两个文本里面继续往下看,直到再次看到两个内容相同的行为止,这时我们的大脑就会认为内容有区别的区域已经结束。但是,我们千万不要把问题想得这么简单,因为我们的大脑做了一个我们可能不容易发现的动作,那就是试探性的将新文本或者旧文本进行整体性的上移。因为如果在在旧文本中删除了若干行或者在新文本中增加了若干行,那么新旧文本之间的行与行的对应关系就会乱掉,无法通过逐行比对来找到差异区域的终点。如果是新文本中新增了若干行,则需要将新文本的所有内容整体性上移相同的行数;如果是旧文本中删除了若干行,也需要将旧文本整体性的上移相同的行数,这样才能对比出差异区域的终点位置。关于如何实现新旧文本的整体性上移,在下文的程序中将会详细讲解。
4、当我们的大脑分别在新旧文本里面捕捉到差异区域的开始点与结束点以后,我们的大脑的就会让我们不要再继续往下看了,要先分析一下差异区域里面是被做出的怎样的修改。
5、我们的大脑首先读取新旧文本在差异区域各自的起点和终点,来判断文本的变化类型。例如,如果旧文本中差异区域的起点和终点位置相同,属于同一行,但是在新文本中差异区域的起点和终点却隔了若干行,这时我们的思维就会直接判断出此处的变化内容是纯粹的新增了若干行。
差异区域的变化类型有5种,分别是纯粹新增若干行、纯粹删除若干行、纯粹修改若干行、新增并修改若干行、删除并修改若干行。当然还有删除并新增若干行这种情况,但是删除之后再新增,变化的结果等同于修改,所以此种类型可以直接合并到以上5种变化类型之内。
(文本变化类型的判断在下文会细讲,因为很重要,处理是否得当直接关系程序计算结果是否准确)
6、当我们已经知道有若干行被修改之后,我们的思维就会开始去比较那些修改前后的行的内容,观察它们重复字符的数量,将重复字符最多的两行认定为修改前后的两行。
7、当我们的大脑在已经得出第一处差异区域的变化类型和内容后,就会命令我们的眼睛从本次差异区域的终点开始(这点很重要,必须是从差异区域的终点开始),接着逐行去读后面的文本了。在找到下一个差异区域时,重复进行上面的过程,读取新旧文本在差异区域各自的起点和终点,判断变化类型。然后,继续看后面的文本。
8、直到把两个文本内容读完,我们的大脑就可以休息了。
————————————————
版权声明:本文为CSDN博主「程序世界的王子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43274704/article/details/102999851
博主使用的是Java语言,由于工作需要,以下是我翻译的python版本:
import logging
logging.basicConfig(level = logging.DEBUG)
logging.disable()
class CompareFile:
resultMap = dict()
def __init__(self, p1, p2):
logging.info('program start')
self.resultMap = dict()
path1, path2 = p1, p2
oldLines = self.readFile(path1)
newLines = self.readFile(path2)
total = 0
self.compare(oldLines, newLines, total)
print('对比结果展示:')
for changeType in self.resultMap.keys():
if 'add' in changeType:
logging.debug('get in add')
addLines = self.resultMap[changeType]
for linesNum in addLines.keys():
print('新文本中新增了第' + str(linesNum) + '行,内容为:' + addLines[linesNum])
if 'delete' in changeType:
logging.debug('get in delete')
delLines = self.resultMap[changeType]
for linesNum in delLines.keys():
print('旧文本中删除了第' + str(linesNum) + '行,内容为:' + delLines[linesNum])
if 'update' in changeType:
logging.debug('get in updates')
updateLines = self.resultMap[changeType]
logging.info('updateLines='+str(updateLines))
for linesNum in updateLines.keys():
print('旧文本中的第' + str(linesNum) + '行,内容为:' + oldLines[int(linesNum)] + '被修改为新文本中的第' + \
str(updateLines[linesNum]) + '行,内容为:' + newLines[int(updateLines[linesNum])])
def compare(self, oldLines, newLines, total):
breakPoint = self.getBreakPoint(oldLines, newLines)
if breakPoint:
oldStart = int(breakPoint['oldLinesBreakStart'])
newStart = int(breakPoint['newLinesBreakStart'])
oldLeftLines, newLeftLines = dict(), dict()
for oldLinesNum in oldLines.keys():
if oldLinesNum >= oldStart:
oldLeftLines[oldLinesNum] = oldLines[oldLinesNum]
for newLinesNum in newLines.keys():
if newLinesNum >= newStart:
newLeftLines[newLinesNum] = newLines[newLinesNum]
newLinesStart = 0
reConnPoint = dict()
reConnPoint = self.getConn(oldLeftLines, newLeftLines, newLinesStart, reConnPoint)
if 'oldLinesConnPoint' in reConnPoint.keys(): # point 1
oldEnd = int(reConnPoint['oldLinesConnPoint'])
newEnd = int(reConnPoint['newLinesConnPoint'])
self.analType(newStart, newEnd, oldStart, oldEnd, newLines, oldLines, total)
nextOldLines, nextNewLines = dict(), dict()
for oldLinseNum in oldLines.keys():
if oldLinseNum >= oldEnd:
nextOldLines[oldLinseNum] = oldLines[oldLinseNum]
for newLinesNum in newLines.keys():
if newLinesNum >= newEnd:
nextNewLines[newLinesNum] = newLines[newLinesNum]
total += 1
self.compare(nextOldLines, nextNewLines, total)
else:
oldLineNums = list(oldLines.keys())
oldLineNums.sort()
oldEnd = oldLineNums[-1] + 1
newLineNums = list(newLines.keys())
newLineNums.sort()
newEnd = newLineNums[-1] + 1
self.analType(newStart, newEnd, oldStart, oldEnd, newLines, oldLines, total)
def analType(self, newStart: int, newEnd: int, oldStart: int, oldEnd: int, newLines: dict, oldLines: dict,
total: int):
logging.debug('get in analyType')
if (oldEnd - oldStart) > (newEnd - newStart) and newEnd == newStart:
oldLine = dict()
for i in range(oldStart, oldEnd):
oldLine[i] = oldLines[i]
self.resultMap['delete' + str(total)] = oldLine
logging.debug('resultMap='+str(self.resultMap))
if oldEnd - oldStart == newEnd - newStart:
oldLine, newLine = dict(), dict()
for i in range(oldStart, oldEnd):
oldLine[i] = oldLines[i]
for i in range(newStart, newEnd):
newLine[i] = newLines[i]
number = oldEnd - oldStart
change = self.getUpdateLines(oldLine, newLine, number)
self.resultMap['update' + str(total)] = change
logging.debug('resultMap='+str(self.resultMap))
if oldEnd == oldStart and oldEnd - oldStart < newEnd - newStart:
newLine = dict()
for i in range(newStart, newEnd):
newLine[i] = newLines[i]
self.resultMap['add' + str(total)] = newLine
logging.debug('resultMap='+str(self.resultMap))
if oldEnd != oldStart and newEnd != newStart and oldEnd - oldStart < newEnd - newStart:
number = oldEnd - oldStart
oldLine, newLine, addLine = dict(), dict(), dict()
for i in range(oldStart, oldEnd):
oldLine[i] = oldLines[i]
for i in range(newStart, newEnd):
newLine[i] = newLines[i]
change = self.getUpdateLines(oldLine, newLine, number)
self.resultMap['update' + str(total)] = change
for lineNum1 in newLine.keys():
m = 0
for lineNum2 in change.keys():
if str(lineNum1) == str(change[lineNum2]):
m += 1
if m == 0:
logging.debug(
'm==0,addLine=' + str(addLine) + ',newLine=' + str(newLine) + ',lineNum1=' + str(lineNum1))
addLine[lineNum1] = newLine[lineNum1]
self.resultMap['add' + str(total)] = addLine
logging.debug('resultMap='+str(self.resultMap))
if oldEnd != oldStart and newEnd != newStart and oldEnd - oldStart > newEnd - newStart:
number = newEnd - newStart
oldLine, newLine, addLine = dict(), dict(), dict()
for i in range(oldStart, oldEnd):
oldLine[i] = oldLines[i]
for i in range(newStart, newEnd):
newLine[i] = newLines[i]
change = self.getUpdateLines(oldLine, newLine, number)
self.resultMap['update' + str(total)] = change
for lineNum1 in oldLine.keys():
m = 0
for lineNum2 in change.keys():
if str(lineNum1) == str(lineNum2):
m += 1
if m == 0:
addLine[lineNum1] = oldLine[lineNum1]
self.resultMap['delete' + str(total)] = addLine
logging.debug('resultMap='+str(self.resultMap))
def getBreakPoint(self, oldLines, newLines):
breakPoint = dict()
oldLineNums = list(oldLines.keys())
oldLineNums.sort()
for oldLinesNum in oldLineNums:
lineOld = oldLines[oldLinesNum]
newLinesNums = list(newLines.keys())
newLinesNums.sort()
for newLinesNum in newLinesNums:
lineNew = newLines[newLinesNum]
if lineNew == '':
continue
else:
if lineOld != lineNew:
breakPoint['oldLinesBreakStart'] = oldLinesNum
breakPoint['newLinesBreakStart'] = newLinesNum
return breakPoint
else:
newLines[newLinesNum] = ''
break
return 0
def getConn(self, oldLeftLines, newLeftLines, newLinesStart, reConnPoint):
oldLinesNums, newLinesNums = list(oldLeftLines.keys()), list(newLeftLines.keys())
oldLinesNums.sort();
newLinesNums.sort()
newNumMax = int(newLinesNums[-1])
for oldLinesNum in oldLinesNums:
lineOld = oldLeftLines[oldLinesNum]
oldNum = oldLinesNum
for newLinesNum in newLinesNums:
newNum = newLinesNum
if newLeftLines[newNum] == oldLeftLines[oldNum]:
reConnPoint['oldLinesConnPoint'] = oldNum
reConnPoint['newLinesConnPoint'] = newNum
return reConnPoint
return reConnPoint
def readFile(self, path: str) -> dict:
with open(path, 'r', encoding = 'utf-8') as f:
txt = f.readlines()
d = dict()
for i, e in enumerate(txt):
d[i] = e.replace('\n', '')
return d
def numJewelsInStones(self, J, S):
J = J.strip();
S = S.strip()
Ja = list(J);
Sa = list(S)
r = 0
for i in range(len(Ja)):
for j in range(len(Sa)):
if Ja[i] == Sa[j]:
r += 1
return r
def sortMapByValue(self, map):
map = sorted(map.items(), key = lambda x: x[1])
return [i for i, j in map][::-1]
def getUpdateLines(self, contentOld, contentNew, n):
resultMap, samChar = dict(), dict()
for oldNum in contentOld.keys():
for newNum in contentNew.keys():
count = self.numJewelsInStones(contentOld[oldNum], contentNew[newNum])
samChar[str(oldNum) + ':' + str(newNum)] = count
keys = self.sortMapByValue(samChar)
for i in range(n):
lineNumArr = keys[i]
lineNumA = lineNumArr.split(':')
resultMap[lineNumA[0]] = lineNumA[1]
return resultMap
if __name__ == '__main__':
path1 = r'D:\hk\hetong1.txt';
path2 = r'D:\hk\hetong2.txt'
cf = CompareFile(path1, path2)