Paul Heckel's Algorithm

367 阅读3分钟

《A Technique for Isolating Differences Between Files》

一种计算两个文件差异的算法。

以行作为基础单元。

Two observations

  1. 每个文件中出现一次且仅出现一次的行必须是同一行(未更改但可能已移动)。
  2. 如果在与“找到”行对紧邻的每个文件中存在彼此相同的行,则这些行必须是同一行。可找到未更改行的序列。

名词解释

  • O: old file
  • N: new file
  • OC: O 中有某一行出现多少次 (0/1/many)
  • NC: N 中有某一行出现多少次 (0/1/many)
  • OLNO: 某一行在 O 中的行号,仅 OC = 1 时有效

还涉及一个符号表和两个数组 OA 和 NA,文件中每一行对应符号表中一项,新旧文件中每一行分别对应数组 OA 和 NA 中的一个元素。符号表中的一项和数组中的元素结构如下: image.png OA 和 NA 中的一个元素要只包含符号表项和行号其中之一。

@dataclass
class HeckelSymbolTableEntry: # 符号表条目
    value: Any
    oc: int = 0
    nc: int = 0
    olno: int = 0
    
HeckelSymbolTableEntryType = Union[int, HeckelSymbolTableEntry] # OA/NA 元素类型

# 符号表
st: Dict[Any: HeckelSymbolTableEntryType] = dict()

算法过程

算法一共包含6个PASS

PASS 1

  1. 按行读取 N 到一个序列中
  2. 如果符号表中不存在该行对应的一项,则创建
  3. 递增符号表中的 NC
  4. 在 NA 中新增行 i 对应的一个元素,内容为该行对应的符号表项。
# PASS 1 
# a -> N
for idx, i in enumerate(self.a):
    ste = st.setdefault(i, HeckelSymbolTableEntry(i))
    ste.nc += 1
    na.append(ste)

PASS 2

与 PASS 1 相同,只不过是处理文件 O,同时记录 OLNO。

# PASS 2
# b -> O
for idx, i in enumerate(self.b):
    ste = st.setdefault(i, HeckelSymbolTableEntry(i))
    ste.oc += 1
    oa.append(ste)
    ste.olno = idx

PASS 3

基于 observation 1 处理 NC = OC = 1 的那些行因为每一行都是相同的、未修改的,因此我们将NA 和 OA 中的符号指针替换为另一个文件中的行数。

例如,如果 NA[i] 对应这样一行,我们在符号表中查找 NA[i] 并将 NA[i] 设置为 OLNO 并将 OA[OLNO] 设置为 i

# PASS 3
for i in range(len(na)):
    if na[i].nc == na[i].oc == 1:
        olno = na[i].olno
        na[i] = olno
        oa[olno] = i

PASS 4

应用 observation 2 并按升序处理 NA 中的每一行:如果 NA[i] 指向 O 中 第 j 行,并且 NA[i+1]OA[j+1] 包含相同的符号表条目指针,则 OA[j+1] 设置为 i+1NA[i+1] 设置为 j+1

# PASS 4
for i in range(len(na)):
    try:
        if isinstance(na[i], int):
            j = na[i]
            if isinstance(na[i + 1], HeckelSymbolTableEntry) and na[i + 1] == oa[j + 1]:
                oa[j + 1] = i + 1
                na[i + 1] = j + 1
    except IndexError:
        pass

PASS 5

应用 observation 2 并按降序处理每个条目:如果 NA[i] 指向 O 中 第 j 行, 并且 NA[i-1]OA[j-1] 包含相同的符号表条目指针,则 NA[i- 1]j-1 替换,OA[j-1]i-1 替换。

# PASS 5
for i in reversed(range(1, len(na))):
    try:
        if isinstance(na[i], int):
            j = na[i]
            if isinstance(na[i - 1], HeckelSymbolTableEntry) and na[i - 1] == oa[j - 1] and i >= 1 and j >= 1:
                oa[j - 1] = i - 1
                na[i - 1] = j - 1
    except IndexError:
        pass

PASS 6

数组 NA 现在包含编码差异所需的信息:如果 NA[i] 指向符号表条目,我们假设行 i 是插入,我们可以将其标记为新文本。 如果它指向 OA[j],但 NA[i+1] 不指向 OA[j+1],则行 i 处于删除或块移动的边界,可以这样标记。

Refrences

  1. A technique for isolating differences between files dl.acm.org/doi/10.1145…
  2. github.com/m-matelski/…