DIFFの真髄	
fuku68	
1
はじめに	
¨  diff
¤  diff(ディフ)とはファイルの比較を行うためのコマンド
で2つのファイル間の違いを出力できるプログラム。
diffプログラムは行単位でテキストファイル間の差異
を表示する。	
2
diffの要素	
¨  編集距離
¤  2つの要素列の差異を数値化したもの
¨  LCS(Longest Common Subsequence)
¤  2つの要素列の最長共通部分列
¨  SES(Shortest Edit Script)
¤  ある要素列を別の要素列に変換するための最短手
順	
参考: http://gihyo.jp/dev/column/01/prog/2011/diff_sd200906	
3
LCS(Longest Common Subsequence)	
¨  最長共通部分列問題
¤  与えられた 2 つの列 X = ⟨x1,x2,...,xm⟩と
Y =⟨y1,y2,...,yn⟩の最長共通部分列を求める問題 	
¨  例)
¤  X = <A, B, C, B, D, A, B>
¤  Y = <B, D, C, A, B, A>
¤  LCS = <B, C, B, A>
¤  LCSは1つとは限らない!
n  <B, C A, B>もLCSの解
n  LCSが変わるとSESも変わる	
4
LCSの例	
5
¨  ABCBDABとBDCABA	
A	 B	 C	 B	 D	 A	 B	
B	 D	 C	 A	 B	 A	
LCS	 B	 C	 B	 A
SES(Shortest Edit Script)	
6
¨  要素列に対しての操作を行い、別の要素列にす
るための一連の操作	
状態遷移	 操作	
<A, B, C, B, D, A, B> Xの状態
<B, C, B, D, A, B> Aを削除
<B, D, C, B, D, A, B> Dを追加
<B, D, C, A, B, D, A, B> Aを追加
<B, D, C, A, B, A, B> Dを削除
<B, D, C, A, B, A> Bを削除(Yと一致)
編集距離	
7
¨  SESにおける要素の「追加」と「削除」の合計	
状態遷移	 操作	 編集距離	
<A, B, C, B, D, A, B> Xの状態
<B, C, B, D, A, B> Aを削除 +1
<B, D, C, B, D, A, B> Dを追加 +1
<B, D, C, A, B, D, A, B> Aを追加 +1
<B, D, C, A, B, A, B> Dを削除 +1
<B, D, C, A, B, A> Bを削除(Yと一致) +1
diffを求めるには…	
8
¨  LCSがわかればSES,編集距離は導い出される
¤  問題としては等価
¨  単純に考えると…
¤  Xから共通部分列を選んで、その共通部分列がYの
共通部分列になり得るのかを確認する。
¤  上記の方法であると計算量が多い
¤  効率の良いアルゴリズムは…
動的計画法(Dynamic Programming)	
9
¨  対象となる問題を複数の部分問題に分割し、部分
問題の計算結果を記録しながら解いていく手法
¨  DPの名称で呼ばれ、プログラムコンテストにおい
ても必須の分野
¤  ナップサック問題
¤  最短経路問題
LCSをDPで解く	
10
¨  LCS の部分構造最適性
1.  X[m] =Y[n] ならばZ[k] =X[m] =Y[n] であり,Zk−1 はXm−1 とYn−1 のLCSであ
る.
2.  X[m] != Y[n]のとき,Z[k] !=X[m] ならばZはXm−1 とY のLCSである.
3.  X[m] != Y[n]のとき,Z[k] !=Y[n]ならばZはXとYn−1 のLCSである. 	
¨  再帰的な解
¤  c[i,j]=
n  0 (i = 0 ま た は j = 0 の と き )
n  c[i−1,j−1]+1 (i,j>0かつxi =yj のとき)
n  max(c[i,j − 1],c[i − 1,j]) (i,j > 0 かつ xi = yj のとき)
DPでのプログラム1(python)	
11
def calc_lcs(a, b):
    n = len(a)
    m = len(b)
   
    matrix = []
    matrix.append ([0] * (m + 1))
    for i in range(n):
        matrix.append([0] * (m + 1))
        for j in range(m):
            if a[i] == b[j]:
                matrix[i + 1][j + 1] = matrix[i][j] + 1
            else:
                matrix[i + 1][j + 1]  = max(matrix[i][j + 1], matrix[i + 1][j] )
               
    # LCSの長さを算出	
    print("LCS length = " +  str(matrix[n][m]))
図解	
12
0	 0	 0	 0	 0	 0	 0	
0	 0	 0	 0	 1	 1	 1	
0	 1	 1	 1	 1	 2	 2	
0	 1	 1	 2	 2	 2	 2	
0	 1	 1	 2	 2	 3	 3	
0	 1	 2	 2	 2	 3	 3	
0	 1	 2	 2	 3	 3	 4	
0	 1	 2	 2	 3	 4	 4	
A
B
C
B
D
A
B
B D C A B A
i	
j	
¨  X = <A,B,C,B,D,A,B>
¨  Y = <B,D,C,A,B,A>
DPでのプログラム2(python)	
13
## LCSをDP法で求める。 	
def lcs(a, b): 	
	
# . . . 先ほどのコード	
# LCSの長さを算出	
print("LCS length = " + str(matrix[n][m]))	
	
lcs = []	
i = n; j = m	
while matrix[i][j] > 0:	
while i != 0 and matrix[i][j] == matrix[i - 1][j]:	
i = i – 1	
while j != 0 and matrix[i][j] == matrix[i][j - 1]:	
j = j – 1	
lcs.insert(0, a[i -1])	
i = i -1	
j = j -1	
# LCS	
print('LCS = ' + ''.join(lcs))
図解	
14
¨  X = <A,B,C,B,D,A,B>
¨  Y = <B,D,C,A,B,A>
¨  LCSの最大長 = 4
¨  表を辿れば
LCSが算出できる
¤  LCS = <B,C,B,A>	
0	 0	 0	 0	 0	 0	 0	
0	 0	 0	 0	 1	 1	 1	
0	 1	 1	 1	 1	 2	 2	
0	 1	 1	 2	 2	 2	 2	
0	 1	 1	 2	 2	 3	 3	
0	 1	 2	 2	 2	 3	 3	
0	 1	 2	 2	 3	 3	 4	
0	 1	 2	 2	 3	 4	 4	
A
B
C
B
D
A
B
B D C A B A
i	
j
DPでのプログラム2(python)	
15
def calc_lcs2(a, b):
n = len(a)
m = len(b)
row = [0] * (m + 1)
for i in range(n):
prev_len = 0
tmp = 0
for j in range(m):
tmp = row[j + 1]
if a[i] == b[j]:
row[j + 1] = prev_len + 1
else:
row[j + 1] = max(row[j], row[j + 1] )
prev_len = tmp
# LCSの長さを算出	
print("LCS length = " + str(row[m]))
LCSの長さだけ
であればメモリを節約
Hunt-Mcllroyのアルゴリズム	16
Hunt-Mcllroyのアルゴリズム	
17
¨  k-candidatesを考える
¤  X = <A, B, C, B, D, A, B>
¤  Y = <B, D, C, A, B, A>
A	 B	 C	 B	 D	 A	 B	
A	
B	
A	
C	
D	
B
k-candidates	
18
0	 1	 2	 2	 3	 3	 4	 4	
0	 1	 2	 2	 3	 3	 3	 4	
0	 1	 1	 2	 2	 2	 3	 3	
0	 0	 1	 2	 2	 2	 2	 2	
0	 0	 1	 1	 1	 2	 2	 2	
0	 0	 1	 1	 1	 1	 1	 1	
0	 0	 0	 0	 0	 0	 0	 0	
B
B
C
D
A
BDBCBA A
A
A	 B	 C	 B	 D	 A	 B	
A	
B	
A	
C	
D	
B	
¨  Ai = Bj
¨  Pij > max(Pi−1, j , Pi, j−1 )
k-candidatesを繋げる	
19
0	 1	 2	 2	 3	 3	 4	 4	
0	 1	 2	 2	 3	 3	 3	 4	
0	 1	 1	 2	 2	 2	 3	 3	
0	 0	 1	 2	 2	 2	 2	 2	
0	 0	 1	 1	 1	 2	 2	 2	
0	 0	 1	 1	 1	 1	 1	 1	
0	 0	 0	 0	 0	 0	 0	 0	
B
B
C
D
A
BDBCBA A
A
A	 B	 C	 B	 D	 A	 B	
A	
B	
A	
C	
D	
B	
¨  k-candidatesの左下にあるCandidateから最長のも
のを接続する。
¨  実装によって実際に接続されるものが異なる
実装コード	
20
¨  diff-lcs
¤  Rubyのdiffを求めるためのGem
¤  Hunt-Mcllroyのアルゴリズムを用いて実装
¤  片方をまずハッシュ化する
n  例) ABCBDAB
n  {' A' : [0, 5],' B' : [1, 3, 6],' C' : [2],' D' : [4]}
¨  https://github.com/halostatue/diff-lcs/blob/
master/lib/diff/lcs/internals.rb#L41
実践	
21
diff-lcsに触れる	
22
require 'diff/lcs'
seq1 = %w(a b c e h j l m n p)
seq2 = %w(b c d e f j k l m r s t)
lcs = Diff::LCS.LCS(seq1, seq2)
diffs = Diff::LCS.diff(seq1, seq2)
sdiff = Diff::LCS.sdiff(seq1, seq2)
seq = Diff::LCS.traverse_sequences(seq1, seq2, callback_obj)
bal = Diff::LCS.traverse_balanced(seq1, seq2, callback_obj)
seq2 == Diff::LCS.patch!(seq1, diffs)
seq1 == Diff::LCS.unpatch!(seq2, diffs)
seq2 == Diff::LCS.patch!(seq1, sdiff)
seq1 == Diff::LCS.unpatch!(seq2, sdiff)
その他のアルゴリズム	23
O(ND)アルゴリズム	
24
¨  E.W.Myersの手法
¨  edit graphを用いた最短経路問題として捉える
¨  すべての経路を探索せずにコスト=nの時の条件下の制約で検索
範囲を狭くする
A
B
C
B
D
A
B
B D C A B A(0, 0)	
各経路のコスト	
0	
1	
1
O(NP) アルゴリズム	
25
¨  Wuの手法
¨  Myersの手法nに対して文字数の差(delta)を考慮して、
さらに検索の効率をあげる
参考文献	
¨  wikipedia
¤  https://ja.wikipedia.org/wiki/Diff
¨  diffの動作原理を知る~どのようにして差分を導き出すのか
¤  http://gihyo.jp/dev/column/01/prog/2011/diff_sd200906
¨  アルゴリズムイントロダクション 第3版 総合版 (世界標準MIT教科書)
¤  https://www.amazon.co.jp/アルゴリズムイントロダクション-第3版-総合版-世界標
準MIT教科書-コルメン/dp/476490408X
¨  Hunt, James W.; McIlroy, M. Douglas (June 1976). "An Algorithm for
Differential File Comparison”
¤  http://www.cs.dartmouth.edu/~doug/diff.pdf
¨  Diff Algorithm
¤  http://constellation.hatenablog.com/entry/20091021/1256112978	
26

diffの真髄