簡體   English   中英

git的耐心差異算法的實現是否正確?

[英]Is git's implementation of the patience diff algorithm correct?

Stackoverflow上的這個問題似乎是耐心差異算法應用的一個很好的候選者。 然而,在測試我的潛在答案時,我發現git diff --patience不能達到我的預期(在這種情況下,與默認的diff算法沒有區別):

$ cat a
/**
 * Function foo description.
 */
function foo() {}

/**
 * Function bar description.
 */
function bar() {}

$ cat b
/**
 * Function bar description.
 */
function bar() {}

$ git diff --no-index --patience a b
diff --git a/a b/b
index 3064e15..a93bad0 100644
--- a/a
+++ b/b
@@ -1,9 +1,4 @@
 /**
- * Function foo description.
- */
-function foo() {}
-
-/**
  * Function bar description.
  */
 function bar() {}

我希望差異是:

diff --git a/a b/b
index 3064e15..a93bad0 100644
--- a/a
+++ b/b
@@ -1,8 +1,3 @@
-/**
- * Function foo description.
- */
-function foo() {}
-
 /**
  * Function bar description.
  */

根據我的理解,在這種情況下,唯一的共同行是提到bar的兩行,並且這些行周圍最長的常見上下文應該是函數bar()及其docblock,這意味着diff應該歸結為已刪除的函數foo()以及它自己的docblock和以下空白行。

沒有人在一段時間內解決這個問題,所以我會對它進行一次嘗試。 這完全是純粹的高級理論,因為我還沒有閱讀關於原始耐心算法的論文。

LCS(最長公共子序列)算法都是為了減少尋找最小編輯距離解決方案所花費的時間。 標准(動態編程)解決方案是O( MN ),其中M是原始字符串中的符號數, N是目標字符串中的符號數。 在我們的例子中,“符號”是行,“字符串”是行的集合,而不是帶有字符的字符串(符號將是,例如,ASCII代碼)。 我們只需填寫“編輯成本”的M × N矩陣; 當我們完成時,我們通過在結果矩陣中向后追蹤最小路徑來產生實際編輯。 有關示例,請參閱https://jlordiales.me/2014/03/01/dynamic-programming-edit-distance/ (通過谷歌搜索找到的網頁:這不是我有什么關系,除了現在高速掃描它是正確的。這似乎是正確的。:-))

實際上計算這個矩陣對於大文件是相當昂貴的,因為MN是源行的數量(通常大致相等):~4k行文件在矩陣中產生~16M條目,必須在我們能夠完全填充之前跟蹤最小路徑。 而且,比較“符號”不再像比較字符那樣微不足道,因為每個“符號”是一個完整的行。 (通常的技巧是在矩陣生成期間散列每一行並比較散列,然后在追溯過程中重新檢查,如果散列誤導了我們,則將“保持不變符號”替換為“刪除原始並插入新”。這甚至可以正常工作在存在哈希沖突的情況下:我們可能會得到一個非常次優的編輯序列,但它實際上永遠不會太糟糕 。)

LCS通過觀察保持長公共子序列(“保留所有這些線”)幾乎總是導致大贏,來修改矩陣計算。 找到一些好的LCS-es之后,我們將問題分解為“編輯非公共前綴,保持公共序列,並編輯非公共后綴”:現在我們計算兩個動態編程矩陣,但對於較小的問題,所以它走得更快 (當然,我們可以對前綴和后綴進行說明。如果我們有一個~4k行文件,我們發現~2k完全沒有變化,中間靠近共線,在頂部留下~0.5k行在〜1.5k的底部,我們可以檢查長的常見子序列在~0.5k“頂部有差異”線,然后再在~1.5k“底部有差異”線。)

LCS表現不佳,因此當“常見的子序列”像瑣碎的行(如} ,會產生可怕的差異,這些行有很多匹配,但並不真正相關。 耐心差異變體簡單地從初始LCS計算中丟棄這些行,因此它們不是“公共子序列”的一部分。 這使得剩余的矩陣更大 ,這就是你必須耐心的原因。 :-)

結果是耐心差異在這里沒有幫助,因為我們的問題與常見的子序列無關。 事實上,即使我們完全拋棄LCS並只做了一個大矩陣,我們仍然會得到一個不理想的結果。 我們的問題是刪除費用:

- * Function foo description.
- */
-function foo() {}
-
-/**

(並且不插入任何內容)與刪除費用相同

-/**
- * Function foo description.
- */
-function foo() {}
-

任何一個的成本只是“刪除5個符號”。 即使我們對每個符號進行加權 - 使非空行“刪除”比空行“更昂貴” - 成本保持不變:我們最后刪除相同的五行。

相反,我們需要的是基於“視覺聚類”對線進行加權的一些方法: 邊緣處的短線比中間的短線更便宜。 添加到Git 2.9的壓縮啟發式嘗試在事后執行此操作。 它顯然至少有一點點缺陷(只有空行計數,它們必須實際存在,而不僅僅是通過達到邊緣來暗示)。 在矩陣填充期間進行加權可能更好(假設在執行LCS消除之后剩下的內容實際上是通過完整的動態編程矩陣)。 不過,這是非常重要的。

我找到了Bram Cohen的一篇新帖子, 他描述了耐心差異算法 ,它支持觀察到的git diff輸出:

...... Patience Diff的工作原理 -

  1. 如果它們相同則匹配兩者的第一行,然后匹配第二行,第三行等,直到一對不匹配。
  2. 如果它們相同則匹配兩者的最后幾行,然后匹配倒數第二行,倒數第二行等,直到一對不匹配。
  3. 查找兩側恰好出現一次的所有行,然后在這些行上執行最長的公共子序列,將它們匹配起來。
  4. 在匹配的行之間的每個部分上執行步驟1-2

因此,算法對唯一線的強調受到步驟1和2的破壞,即使它們是由嘈雜的線組成,它們也檢測公共前綴和后綴。

這樣的表述與我之前看到的略有不同,而Bram承認他稍微改變了它:

我以前用順序描述它有點不同......

我的問題實際上重復了這篇評論中對Bram的帖子所表達的擔憂。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM