diffが計算しているもの: 最長共通部分列(LCS)
2つのテキストの違いを探すとは、「元の文章を修正後の文章にするには、どの行を削除し、どの行を挿入すれば最も少ない編集で済むか」 に答えることです。この問題は、2つのリストから順序を保ったまま共通に取り出せる最長の列、つまりLCS(Longest Common Subsequence、最長共通部分列)を求める問題と同じです。LCSに含まれる項目は「変わっていない部分」で、残りは元にだけあれば 削除、修正後にだけあれば追加になります。したがって編集の回数(削除+追加)は、元の長さ + 修正後の長さ − 2 × LCSの長さで決まります。
このツールは、動的計画法でLCSの表を埋め、そこから逆にたどって編集を読み取る古典的な方法を使います。時間とメモリは 2つの長さの積に比例するため、まず両方の先頭と末尾で同じ部分を切り落として比較範囲を狭め、それでも積が大きすぎる場合は 画面を固まらせずに案内メッセージを出します(上限は後述)。Gitの標準のdiffは、1986年にEugene Myersが発表した別のアルゴリズムが 基になっていて、2つの版が似ているほど速く動きます。どちらも最小の編集を求める点は同じですが、同じ長さの解が複数あるときに 選ぶ解が異なることがあり、削除行・追加行の並びが少し違って見える場合があります。
手で追える例
アルゴリズムの解説で定番の文字列 ABCABBA(元)と CBABAC(修正後)の表です。各マスは「その位置から最後までのLCSの長さ」で、 左上のマスが全体のLCSの長さです。数値はこのページを作るときに、ツールと同じ漸化式で計算した結果です。
| 元\修正後 | C | B | A | B | A | C | 末尾 |
|---|---|---|---|---|---|---|---|
| A | 4 | 4 | 3 | 2 | 2 | 1 | 0 |
| B | 4 | 4 | 3 | 2 | 1 | 1 | 0 |
| C | 4 | 3 | 3 | 2 | 1 | 1 | 0 |
| A | 3 | 3 | 3 | 2 | 1 | 0 | 0 |
| B | 3 | 3 | 2 | 2 | 1 | 0 | 0 |
| B | 2 | 2 | 2 | 2 | 1 | 0 | 0 |
| A | 1 | 1 | 1 | 1 | 1 | 0 | 0 |
| 末尾 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
結果: LCSの長さは4です。元が7個、修正後が6個なので、最小の編集数は 7 + 6 − 2×4 = 5 です。 行単位に置き換えるとイメージしやすくなります。元が「りんご / バナナ / ぶどう」、修正後が「りんご / いちご / ぶどう / みかん」のとき、 LCSは「りんご、ぶどう」で長さ2、編集数は 3 + 4 − 2×2 = 3 です。つまり「バナナ」の削除が1行、 「いちご」と「みかん」の追加が2行になります。上の入力欄にそのまま入れると、同じ結果を確かめられます。
行・単語・文字単位の違い
行単位は、ソースコード、設定ファイル、箇条書き、契約書の条項のように、行が意味の単位になっている文書に向いています。 速く、結果も簡潔ですが、行の中の単語が1つ変わっただけでも行全体が削除と追加として表示されます。そこでこのツールは、 削除行と追加行が隣り合っているとき、その2行を単語単位でもう一度比べ、実際に変わった単語に色をつけます。単語単位は、 長い1行で書かれた報告書や記事の下書きなどで、どのフレーズが直されたかを見るのに適しています。文字単位は、誤字や 数字1桁の違い、口座番号やURLのように1文字の誤りが致命的な値の照合に向いています。ただし長い文章では処理が急に重くなり、 たまたま一致した文字のせいで読みにくい細切れの結果になることがあるため、短い箇所に使ってください。なお日本語は単語の間に 空白がないため、単語単位では空白で区切られた塊が1つの単語として扱われ、空白のない日本語の文は全体で1つの単語になります。 日本語の文の中の違いを細かく見たいときは文字単位を使います。
| 単位 | 向いている文書 | 長所 | 注意点 |
|---|---|---|---|
| 行 | コード、設定、一覧、条項 | 速く結果が短い | 1語の修正でも行全体が変わったように見える(行内の強調で補う) |
| 単語 | 英文の長い文、報告書 | 直したフレーズが見える | 空白で区切るため、空白のない日本語は文全体が1語になる |
| 文字 | 数字・名前・URLの照合 | 1文字の違いも捉える | 長い文章では遅く、結果が細切れになりやすい |
空白・大文字小文字のオプションを使う場面
内容は同じなのにインデントだけが変わったり、行末に空白が付いたりして、すべての行が「違う」と出るときは、 「前後の空白を無視」を選ぶと実際の変更だけが残ります。ワープロからコピーした文章のように空白が二重に入っている場合は 「連続する空白を1つに」が適しています。「大文字・小文字を無視」は、英文で大文字小文字の表記だけが変わった場合に便利ですが、userId と userID が別の名前になるコードの比較ではオフにしてください。これらのオプションは比較にだけ 働き、画面には元の文字がそのまま表示されます。改行コードがWindows(CRLF)とUnix(LF)で違うために全行が違って見える問題は、 入力時に改行を統一して防いでいます。
結果の読み方と活用例
緑は修正後にだけある部分(追加)、赤は元にだけある部分(削除)です。契約書や利用規約の改定前後を比べるときは、まず行単位で 全体を見渡し、変わった行だけを単語単位で見直すと効率的です。コードや設定ファイルは、行単位で空白を無視にして意味のある 変更だけを見ます。JSONを比べるなら、先にJSON整形で両方のインデントとキーの並びを そろえると、不要な差が消えます。志望動機などの文章を書き直したあとは、文字数カウントで 分量の変化も確かめられます。見た目は同じなのに違うと出る場合は、文字単位にして、全角・半角スペースやゼロ幅文字といった 目に見えない文字、同じ文字が別のコードで保存されている場合などを探してください。名前をまとめて置換したいときは、正規表現テスターで先に式を試せます。
プライバシー、上限、向かない場合
貼り付けた内容はすべてブラウザ内で処理され、サーバーへの送信も保存もされません。実際の制限はメモリです。LCSの表は 1マスにつき32ビットの数値を持ち、先頭と末尾の同じ部分を除いた比較範囲のマス数が25,000,000を超えると、ツールは表を作らず メッセージを表示します。目安は約100MB、片側で約5,000項目です。行単位なら数千行ですが、文字単位では数千文字なので、 長い文書は行単位にするか、テキストを分けて比較してください。
diffは「移動」を理解しません。段落を切り取って別の場所に貼ると、一方では削除、もう一方では追加として表示されます。 比較するのはプレーンテキストのみで、WordやPDFの書式、表、画像の変更は検出できません。行内の強調は、近くにある 削除行と追加行を順番に組み合わせて比べるため、行の並びが大きく入れ替わっていると組み合わせが合わないことがあります。 3者間のマージやバージョン管理には対応していないので、チームでのコード管理には Git のようなツールを使ってください。 法的な効力のある文書の最終確認は、必ず人が元の文書と照らして行ってください。
よくある質問
1行の中の単語が1つ変わっただけなのに、行全体が赤・緑になります。
行単位の比較の基本的な動作です。削除行と追加行が組み合わされると、その中で実際に変わった単語がより濃く強調されるので、濃い部分を見てください。フレーズだけを見たいときは、比較の単位を単語に変えます。
「前後の空白を無視」と「連続する空白を1つに」の違いは何ですか?
前者は比較する各項目の両端の空白だけを取り除きます。後者は項目の途中にある複数の空白やタブも1つの空白として扱うので、スペースを二重にした違いも無視されます。
同じ内容に見えるのに違うと表示されるのはなぜですか?
目に見えない空白(全角スペース、ゼロ幅文字)、行末の空白、ユニコードの正規化形式の違い、大文字小文字の違いなどが主な原因です。オプションを変えてみて、文字単位で比べると、どの文字が違うのか確認できます。
行が移動したことは検出されますか?
いいえ。位置が変わった行は、一方で削除、他方で追加として表示されます。これはLCSに基づくdiffに共通の性質です。
Gitのdiffと結果が違うことはありますか?
あります。最小の編集が複数あるとき、アルゴリズムによって選ぶ経路が異なるため、編集数は同じでも削除行・追加行の位置が少し違うことがあります。GitはMyers系のアルゴリズムを使い、ほかの方式も選べます。
「差分をコピー」はどんな形式でコピーされますか?
行単位では、変更のない行の先頭に空白2つ、削除行に「- 」、追加行に「+ 」を付けたテキストになります。単語・文字単位では、削除を [-…-]、追加を [+…+] で表します。