一個有些意思的項目--文件夾對比工具(一)

前言

爲什麼會寫這個,因爲遇到了有意思的事情,簡而言之就是,面試某意向公司,沒過;其中一位面試官非常nice,還仔細看了我博客,覺得是不是面試時沒展現出來,因此第二天專程打電話過來,給了我一個額外機會,就是花幾天時間做一個小項目,過幾天提交給他。

這是背景,項目是關於做一個工具,可以指定兩個目錄進行對比,如果某個文件如a.txt在兩個目錄都存在,就對比其內容並呈現,呈現效果可以參考beyond compare或者git diff。

花了三天多時間編碼,兩天時間寫文檔,最終做了一個滿足需求的簡陋工具出來,幸不辱命吧;項目麻雀雖小,五臟俱全,也寫了需求分析文檔、概要設計、詳細設計等,項目本身也非常有意思,所以給大家分享一下。
其中一個核心點就是文本差異對比算法,因此先來篇文章講解一下,鋪墊鋪墊。

差異對比,很多人會想到beyond compare、git、svn等。這裏以git來說吧,git作爲版本管理工具,真的也太方便了,很多時候想推薦給非互聯網行業的朋友們。

版本管理工具,最重要的一點就是不同版本的差異對比,看下圖:

image-20220801211626345

從圖上來說,右邊那一行比左邊,就是多了幾個字符”xxxww“;但是對於軟件來說,怎麼才知道,只是多了幾個字符呢,畢竟,按理來說,插入了幾個字符後,原來在右側的字符被迫右移,其實在程序看來,是從插入的地方開始,兩個字符串已經開始是不同的了。

但是軟件並沒有從插入的地方開始的右側字符,全標記爲差異,所以,軟件是怎麼識別出來的呢?

字符串轉換

其實以上的問題可以用下面的例子來簡單闡述:

假設有一個原始字符串是:ABCABBA,現在我們想把它變成:CBABAC,是不是有很多種方式呢?

比如,最簡單的方式,ABCABBA依次刪掉:ABCABBA,現在,是不是變成一個空字符串了,刪7個字符,一共操作7次;接下來,再在空字符串基礎上,依次加上:CBABAC,加了6次。

最終,操作了13次,就把ABCABBA變成了CBABAC。

但是,這太暴力了,也不是很優雅,直觀的感覺就是,太傻了,好像不需要這麼多次吧?

所以,尋找一個算法,以保證:轉換成功的同時,保證儘可能少的編輯次數。

這就是最短diff算法,diff就是把原始字符串變成目標字符串,要進行的各種增刪操作;或者也可以和數學裏的delta對比,我查了下,delta就有變動的意思。

image-20220801214421370

"最短diff"這個算法有多種實現,在git diff的代碼中,就有4種實現供我們選擇,分別是:

myers, minimal, patience, histogram

在git help diff的文檔中,有簡要介紹:

image-20220801212830510

默認的是myers算法,什麼時候用其他的呢,這邊有篇文檔:https://luppeng.wordpress.com/2020/10/10/when-to-use-each-of-the-git-diff-algorithms/,有需要的同學可以看看。

這篇就簡單說下我對myers的理解,算法比較複雜,我也沒怎麼弄懂,大家也跟着有個概念就行了(狗頭)

diff的另一種直觀展示

大佬們想了一個辦法來表示diff。還是上面的例子,ABCABBA轉換爲CBABAC

根據這兩個字符串構造下面一張圖,橫軸是原始內容,縱軸是目標內容。

image-20220801221137659

這個圖要這麼看,左上角是零座標,水平是x軸,上下是y軸,x軸從(0,0)點到(1,0)點,爲A字符;(1,0)到(2,0)是B字符,依次類推,橫軸上的8個點,有7個空,依次爲ABCABBA,即原始字符串的內容。y軸也是同理。

現在規定了,(0,0)處,你擁有原始字符串ABCABBA,當前指向第一個字符A。此時,向右表示刪除對應的字符,向下表示新增對應字符,對角線則表示原內容保持不動(或者說先刪再加,即不變)

現在舉個例子:

  • 從(0,0)移動到(1,0),需要刪掉A,此時,ABCABBA從當前光標所在處,刪掉A,變成了BCABBA,此時光標指向BCABBA的第一個字符B;

    image-20220801222140579

  • 從(1,0)再向右移動一格到(2,0),此時要刪去B,變成了CABBA

  • 沿着x軸,繼續移動到(3,0),刪除C,變成ABBA;繼續移動到(4,0),刪除A,變成BBA;繼續到(5,0),刪除B,變成BA;繼續到(6,0),刪除B,變成A;繼續到(7,0),刪除A,變成空。

    image-20220801222800789

  • 到目前爲止,我們來到了7,0,字符串變成空的了;開始往下走,往下走的過程,是增加對應字符,比如,從(7,0),走到(7,1),增加字符C,變成“C”;再走到(7,2),變成CB;再到(7,3),變成CBA;

  • 最終走到(7,6),變成了CBABAC.

大家可能發現了,只要沿着那個圖一路走,從(0,0)到達右下角(7,6),原始字符串就能變成目標字符串。

當然,這條路是比較暴力的,先刪除原始字符串(一路往右),再新增目標字符串(一路向下)。

diff的圖形表示之路線演示

我們來隨便畫個其他路線試試。

image-20220801223706228

現在(0,0)處,我們爲原始字符串ABCABBA,

  • 接下來是向右一步-A,變成BCABBA。
  • 向下,+C,變成CBCABBA
  • 遇到對角線,對角線對應字符B,此時可以理解爲刪掉B,再加上B,相當於光標前移,依然是CB|CABBA,我們用|表示光標位置
  • 向下,在當前光標處+A,變成CBA|CABBA;加B,變成CBAB|CABBA;再-C,變成CBAB|ABBA
  • 又遇到對角線,對角線對應字符A,此時光標移動,變成CBABA| BBA
  • 從(4,5)移動到(4,6),加C,變成CBABAC|BBA
  • 從(4,6)移動到(7,6),依次刪除BBA,即變成CBABAC

所以,我們再一次成功到達了右下角,此時字符串也變成了CBABAC。我們也可以算一下,移動了多少次

-A,+C,+A,+B,-C,+C,-B,-B,-A,合計是9次(走對角線的話,沒有實際修改字符,不算在內)。

Myers 算法大概理解

如果前面都理解了的話,我們大概知道,從圖中的左上角,到達右下角的每一條路線,都是有效的diff。

而Myers的目標,應該就是從衆多的路線中,選出一條距離最短(向右的次數 + 向下的次數之和;走對角線不算)的路線。

而這條最短的路線,就是最短diff算法的答案。

Myers的算法我還不是很理解,但是如果按照我們暴力的思路,就是每條路線的距離都計算一下(向右的次數 + 向下的次數之和;走對角線不算),然後取最短的路線即可。

網上有不少資料,有興趣可以閱讀:

https://chenshinan.github.io/2019/05/02/git生成diff原理:Myers差分算法/

原始論文:

https://neil.fraser.name/writing/diff/myers.pdf

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章