难以理解 Paul Heckel 的 Diff 算法
Difficulty understanding Paul Heckel's Diff Algorithm
一直在看Paul Heckel's Diff Algorithm,好像没完全看懂
我复制了 Python 代码中所示的步骤 1-5,但我无法使用算法的最后一步来显示差异。如果有人解释了最后一步以及 Python 代码,我将不胜感激。
此外,我不完全理解为什么您需要参考第 4 步和第 5 步中的 table 行,所以对此的解释也很棒!
非常感谢
这是我当前的代码:
def find_diff(current_file_as_list, different_file_as_list):
N = current_file_as_list
O = different_file_as_list
table = {}
OA = []
NA = []
for i in O:
OA.append(i)
for i in N:
NA.append(i)
# First pass
i = 0
for line in N:
if not line in table:
table[line] = {}
table[line]["NC"] = 1
else:
if table[line]["NC"] == 1:
table[line]["NC"] = 2
else:
table[line]["NC"] = "many"
NA[i] = table[line]
i += 1
# second pass
j = 0
for line in O:
if not line in table:
table[line] = {}
table[line]["OC"] = 1
else:
if not "OC" in table[line]:
table[line]["OC"] = 1
elif table[line]["OC"] == 1:
table[line]["OC"] = 2
else:
table[line]["OC"] = "many"
table[line]["OLNO"] = j # Gets overwritten with multiple occurrences.
# Check to see if this is the intended implementation.
# Maybe only relevant for "OC" == "NC" == 1
OA[j] = table[line]
j += 1
# third pass
i = 0
for i in range(0, len(NA)):
# Check if they appear in both files
if "OC" in NA[i] and "NC" in NA[i]:
# Check if they appear exactly once
if NA[i]["OC"] == NA[i]["NC"] == 1:
olno = NA[i]["OLNO"]
NA[i], OA[olno] = olno, i
i += 1
# fourth pass
# ascending
for i in range(0, len(NA)):
for j in range(0 , len(OA)):
if NA[i] == OA[j] and i + 1 < len(NA) and j + 1 < len(OA) and NA[i + 1] == OA[j + 1]:
OA[j + 1] = table[O[i + 1]]
NA[i + 1] = table[N[j + 1]]
# fifth pass
# descending
for i in range(len(NA) - 1, 0, -1):
for j in range(len(OA) - 1, 0, -1):
if NA[i] == OA[j] and i - 1 > 0 and j - 1 > 0 and NA[i - 1] == OA[j - 1]:
OA[j - 1] = table[O[i - 1]]
NA[i - 1] = table[N[j - 1]]
# final step implementation should go here but I'm not sure how to approach it but this is my current attempt (which I am certain is wrong):
k = 0
array = []
for i in range(0, len(NA)):
if isinstance(NA[i], int):
array.append("= " + str(N[i]))
k = NA[i] + 1
elif isinstance(NA[i], dict):
array.append("+ " + N[i])
for j in range(k, len(OA)):
k = j + 1
print("j - " + str(j))
if not isinstance(OA[j], int):
array.append("- " + O[j])
else:
break
您可以将任意两个字符串或字符串列表作为输入传递给函数,例如。 find_diff("hello", "hell")
我不确定你在哪里找到这个解释和代码,但它有几个错误。用于数据比较参考的维基百科页面之一是 a reference to Paul's paper,事实证明这对理解算法最有帮助。
首先,据我所知,您对最后一步的实现是正确的(假设前面的步骤都正确完成)。
让我们从一个 syntax/language 问题开始:也许我遗漏了什么,但我不明白为什么你(和你链接到的代码)增加自增索引 i
在第三关。
关于 table 条目的计数器:在链接代码中有一个注释问题 - 为什么我们根本需要 2 值?答案是——我们没有!在论文本身中,Heckel 明确写道,计数器应该具有的唯一值是 0、1 和许多。您可以看到我们从不使用或查询计数器的 2 值。我猜测这个错误来自于用一种比 Heckel 在编写算法时所想到的语言更灵活的语言来实现算法,因为查询是否存在特定 table 条目的计数器是同义词查询计数器的值是否为 0.
最后也是最重要的,这个实现中的第四遍和第五遍是错误的。在这里,我认为论文中通行证的措辞可能令人困惑,而且编写链接代码的人都弄错了。你的第二个问题已经揭示了它。第四遍在 NA
上按升序排列,每个位置的值指向 OA
中的 position(这意味着它的类型为 int
在讨论的实现中)我们检查两个数组中下一个位置的值是否指向相同的 table 条目。如果他们这样做了,我们会用彼此的位置替换这些指针(用 int
s 覆盖指针。所以你的第二个问题很重要 - 我们 不 使用 table 入口指针在这里)。这样,我们就有了在第三遍中发现的唯一行,作为锚点,可以找到紧随其后的未更改行,并且是它们 "block" 的一部分,但在文件中不是唯一的。同样的情况发生在第五遍,但是是倒退的,因此未更改的唯一行之前的相同行也将被归类为未更改。
这是我描述的第四和第五遍:
# fourth pass
# ascending
for i in range(0, len(NA) - 1):
if isinstance(NA[i], int) and (NA[i] + 1) < len(OA) and NA[i + 1] == OA[NA[i] + 1]:
NA[i + 1] = NA[i] + 1
OA[NA[i] + 1] = i + 1
# fifth pass
# descending
for i in range(len(NA) - 1, 0, -1):
if isinstance(NA[i], int) and (NA[i] - 1) >= 0 and NA[i - 1] == OA[NA[i] - 1]:
NA[i - 1] = NA[i] - 1
OA[NA[i] - 1] = i - 1
我一直在寻找相同问题的解决方案。我最终从头开始在 Python 中实现了 Heckel 的算法。这里是 implementation of first 5 steps of Heckel's algorithm and implementation of 6th step (extracting diff reperesentation).
您还可以在您的程序中使用 mdiff 程序包,使用 Heckel 算法检测带有块移动的文本差异:
from mdiff import HeckelSequenceMatcher
a = ['line1', 'line2', 'line3', 'line4', 'line5']
b = ['line1', 'line3', 'line2', 'line4', 'line6']
sm = HeckelSequenceMatcher(a, b)
opcodes = sm.get_opcodes()
for tag, i1, i2, j1, j2 in opcodes:
print('{:7} a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2]))
equal a[0:1] --> b[0:1] ['line1'] --> ['line1']
move a[1:2] --> b[2:2] ['line2'] --> []
equal a[2:3] --> b[1:2] ['line3'] --> ['line3']
moved a[1:1] --> b[2:3] [] --> ['line2']
equal a[3:4] --> b[3:4] ['line4'] --> ['line4']
replace a[4:5] --> b[4:5] ['line5'] --> ['line6']
包中也有简单的 GUI app 允许可视化和测试算法。
一直在看Paul Heckel's Diff Algorithm,好像没完全看懂
我复制了 Python 代码中所示的步骤 1-5,但我无法使用算法的最后一步来显示差异。如果有人解释了最后一步以及 Python 代码,我将不胜感激。
此外,我不完全理解为什么您需要参考第 4 步和第 5 步中的 table 行,所以对此的解释也很棒!
非常感谢
这是我当前的代码:
def find_diff(current_file_as_list, different_file_as_list):
N = current_file_as_list
O = different_file_as_list
table = {}
OA = []
NA = []
for i in O:
OA.append(i)
for i in N:
NA.append(i)
# First pass
i = 0
for line in N:
if not line in table:
table[line] = {}
table[line]["NC"] = 1
else:
if table[line]["NC"] == 1:
table[line]["NC"] = 2
else:
table[line]["NC"] = "many"
NA[i] = table[line]
i += 1
# second pass
j = 0
for line in O:
if not line in table:
table[line] = {}
table[line]["OC"] = 1
else:
if not "OC" in table[line]:
table[line]["OC"] = 1
elif table[line]["OC"] == 1:
table[line]["OC"] = 2
else:
table[line]["OC"] = "many"
table[line]["OLNO"] = j # Gets overwritten with multiple occurrences.
# Check to see if this is the intended implementation.
# Maybe only relevant for "OC" == "NC" == 1
OA[j] = table[line]
j += 1
# third pass
i = 0
for i in range(0, len(NA)):
# Check if they appear in both files
if "OC" in NA[i] and "NC" in NA[i]:
# Check if they appear exactly once
if NA[i]["OC"] == NA[i]["NC"] == 1:
olno = NA[i]["OLNO"]
NA[i], OA[olno] = olno, i
i += 1
# fourth pass
# ascending
for i in range(0, len(NA)):
for j in range(0 , len(OA)):
if NA[i] == OA[j] and i + 1 < len(NA) and j + 1 < len(OA) and NA[i + 1] == OA[j + 1]:
OA[j + 1] = table[O[i + 1]]
NA[i + 1] = table[N[j + 1]]
# fifth pass
# descending
for i in range(len(NA) - 1, 0, -1):
for j in range(len(OA) - 1, 0, -1):
if NA[i] == OA[j] and i - 1 > 0 and j - 1 > 0 and NA[i - 1] == OA[j - 1]:
OA[j - 1] = table[O[i - 1]]
NA[i - 1] = table[N[j - 1]]
# final step implementation should go here but I'm not sure how to approach it but this is my current attempt (which I am certain is wrong):
k = 0
array = []
for i in range(0, len(NA)):
if isinstance(NA[i], int):
array.append("= " + str(N[i]))
k = NA[i] + 1
elif isinstance(NA[i], dict):
array.append("+ " + N[i])
for j in range(k, len(OA)):
k = j + 1
print("j - " + str(j))
if not isinstance(OA[j], int):
array.append("- " + O[j])
else:
break
您可以将任意两个字符串或字符串列表作为输入传递给函数,例如。 find_diff("hello", "hell")
我不确定你在哪里找到这个解释和代码,但它有几个错误。用于数据比较参考的维基百科页面之一是 a reference to Paul's paper,事实证明这对理解算法最有帮助。
首先,据我所知,您对最后一步的实现是正确的(假设前面的步骤都正确完成)。
让我们从一个 syntax/language 问题开始:也许我遗漏了什么,但我不明白为什么你(和你链接到的代码)增加自增索引 i
在第三关。
关于 table 条目的计数器:在链接代码中有一个注释问题 - 为什么我们根本需要 2 值?答案是——我们没有!在论文本身中,Heckel 明确写道,计数器应该具有的唯一值是 0、1 和许多。您可以看到我们从不使用或查询计数器的 2 值。我猜测这个错误来自于用一种比 Heckel 在编写算法时所想到的语言更灵活的语言来实现算法,因为查询是否存在特定 table 条目的计数器是同义词查询计数器的值是否为 0.
最后也是最重要的,这个实现中的第四遍和第五遍是错误的。在这里,我认为论文中通行证的措辞可能令人困惑,而且编写链接代码的人都弄错了。你的第二个问题已经揭示了它。第四遍在 NA
上按升序排列,每个位置的值指向 OA
中的 position(这意味着它的类型为 int
在讨论的实现中)我们检查两个数组中下一个位置的值是否指向相同的 table 条目。如果他们这样做了,我们会用彼此的位置替换这些指针(用 int
s 覆盖指针。所以你的第二个问题很重要 - 我们 不 使用 table 入口指针在这里)。这样,我们就有了在第三遍中发现的唯一行,作为锚点,可以找到紧随其后的未更改行,并且是它们 "block" 的一部分,但在文件中不是唯一的。同样的情况发生在第五遍,但是是倒退的,因此未更改的唯一行之前的相同行也将被归类为未更改。
这是我描述的第四和第五遍:
# fourth pass
# ascending
for i in range(0, len(NA) - 1):
if isinstance(NA[i], int) and (NA[i] + 1) < len(OA) and NA[i + 1] == OA[NA[i] + 1]:
NA[i + 1] = NA[i] + 1
OA[NA[i] + 1] = i + 1
# fifth pass
# descending
for i in range(len(NA) - 1, 0, -1):
if isinstance(NA[i], int) and (NA[i] - 1) >= 0 and NA[i - 1] == OA[NA[i] - 1]:
NA[i - 1] = NA[i] - 1
OA[NA[i] - 1] = i - 1
我一直在寻找相同问题的解决方案。我最终从头开始在 Python 中实现了 Heckel 的算法。这里是 implementation of first 5 steps of Heckel's algorithm and implementation of 6th step (extracting diff reperesentation).
您还可以在您的程序中使用 mdiff 程序包,使用 Heckel 算法检测带有块移动的文本差异:
from mdiff import HeckelSequenceMatcher
a = ['line1', 'line2', 'line3', 'line4', 'line5']
b = ['line1', 'line3', 'line2', 'line4', 'line6']
sm = HeckelSequenceMatcher(a, b)
opcodes = sm.get_opcodes()
for tag, i1, i2, j1, j2 in opcodes:
print('{:7} a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2]))
equal a[0:1] --> b[0:1] ['line1'] --> ['line1']
move a[1:2] --> b[2:2] ['line2'] --> []
equal a[2:3] --> b[1:2] ['line3'] --> ['line3']
moved a[1:1] --> b[2:3] [] --> ['line2']
equal a[3:4] --> b[3:4] ['line4'] --> ['line4']
replace a[4:5] --> b[4:5] ['line5'] --> ['line6']
包中也有简单的 GUI app 允许可视化和测试算法。