ぱんだツールズぱんだツールズ

技術背景

テキスト差分比較の仕組み — Myersアルゴリズムとdiffの読み方

約6分

契約書の旧版と新版を付き合わせて「どこが変わったのか」を数時間かけて確認したことはないでしょうか。 あるいは GitHub の PR 画面で赤と緑に塗り分けられた差分を見たことがあるはずです。 どちらも同じ「diff(差分)」という仕組みの応用ですが、差分は開発者だけのものではなく、 仕様書・設定ファイル・契約書など、あらゆる文書比較で役立ちます。 この記事では、diff の仕組み・Myersアルゴリズムの考え方・unified diff の読み方・実務での使い分けを整理します。

diff とは — 2つのテキストを最小操作で表現する

diff(ディフ)とは、2つのテキストの違いを「追加・削除」という最小限の編集操作で表現する仕組みです。 1976年頃に UNIX 系 OS で登場した diff コマンドがルーツで、現代ではバージョン管理・コードレビュー・設定ファイル比較など、 テキストを扱うあらゆる場面で使われています。

単純に「違う・違わない」を判定するだけなら、2つの文字列を1文字ずつ突き合わせるだけで済みます。 しかし diff のゴールはそこではなく、「どう編集すれば A が B になるか」をできるだけ少ない操作で説明することです。 これを「最小編集スクリプト(Shortest Edit Script)」と呼び、差分アルゴリズムの中心的な課題になっています。

最長共通部分列(LCS)問題との関係

diff の計算は、数学的には最長共通部分列(Longest Common Subsequence、LCS)問題の裏返しです。 2つの文字列に共通して現れる最長の部分列が分かれば、それ以外の部分が「追加」か「削除」になる、という考え方です。

たとえば「ABCBDAB」と「BDCAB」の LCS は「BCAB」または「BDAB」(長さ4)です。 LCS が長いほど、2つの文字列は似ていることになります。

LCS は動的計画法(DP)で解くと、2つの文字列の長さを N と M として O(NM) の時間・空間計算量がかかります。 小さなテキストなら問題ありませんが、1万行を超えるファイル同士の比較では億オーダーの計算が必要になり、実用的ではありません。 ここで Myers アルゴリズムが登場します。

Myersアルゴリズム(1986年)— diffの事実上の標準

1986年に Eugene W. Myers が発表した論文「An O(ND) Difference Algorithm and Its Variations」で提案されたアルゴリズムが、 現代の diff の事実上の標準になっています。Git・Subversion・多くの diff ツールの内部で採用されています。

このアルゴリズムの計算量は O((N+M)D) で、D は「必要な編集操作の数(編集距離)」です。 LCS の DP と違い、2つのテキストが似ている(D が小さい)ほど高速に動作する点が大きな利点です。 実際のコード差分では、ファイル全体の数%しか変更されていないことが多く、Myers アルゴリズムはこの性質に非常にフィットします。

豆知識

Myers アルゴリズムはグラフ探索として定式化されます。 「A を行方向、B を列方向に並べた格子」を考え、対角線を進めば「共通行」、右か下に進めば「追加・削除」になります。 このグラフ上で最短経路を求めるのが Myers の考え方です。

差分の表示形式

同じ差分データでも、表示形式によって読みやすさが変わります。代表的な3形式を比較します。

形式特徴主な用途
unified diff前後数行の文脈と変更行を 1 つのハンクにまとめる。コンパクトGit・パッチファイル・コードレビュー
context diff変更前・変更後を別ブロックで並べる。unified より冗長古いパッチ形式(現在は少数派)
side-by-side左右に並べて視覚的に比較。差分量が多いときに見やすいIDE の diff ビュー・GitHub の Split View

unified diff の読み方

--- a/config.yml

+++ b/config.yml

@@ -1,5 +1,6 @@

name: sakutto-tools

version: 1.0.0

-timeout: 30

+timeout: 60

+retry: 3

debug: false

  • --- a/...+++ b/... が変更前・変更後のファイルパス
  • @@ -1,5 +1,6 @@ はハンクヘッダー。「前5行・後6行」の対応範囲を示す
  • 先頭が空白の行は「文脈行」(共通行)、- は削除、+ は追加

行単位 vs 単語単位 vs 文字単位の比較

diff は比較する「単位」を変えるだけで、読みやすさが大きく変わります。用途に応じて使い分けるのがコツです。

粒度向いている用途注意点
行単位コード差分・ログ比較・設定ファイル行内の小さな変更が全行差分になる
単語単位文章の校正・英文の推敲・GitHub の word-level diff日本語は単語区切りが曖昧で精度が落ちやすい
文字単位日本語文章の校正・一文字だけの修正確認差分量が多いと視覚的に読みにくい

GitHub の PR レビューで「word-level diff」が読みやすく感じるのは、行単位の赤緑に重ねて、 その行の中のどの単語が変わったかを強調表示しているからです。 行単位だけでは「1行全部が赤と緑で塗り分けられて、どこが変わったのか分からない」ことがありますが、 単語単位の強調を加えることで視認性が格段に上がります。

実務で diff を使うシーン

diff は開発現場だけでなく、次のような実務シーンで役立ちます。

  • 仕様書・契約書の改定確認 — 法務レビュー後の修正版と原版を突き合わせて、変更箇所だけを短時間で確認する
  • 設定ファイルのサーバー間差異 — 本番と検証環境の設定が揃っているか、どの行が違うかを即座に把握する
  • APIレスポンスの回帰テスト — リリース前後で同じリクエストのレスポンスがどう変わったかを比較する
  • ドキュメントのバージョン管理 — マニュアルの前版と新版を比較し、読者への変更通知を作成する
  • 翻訳メモリの更新 — 原文の変更点を抽出して、翻訳すべき箇所だけを特定する

プレーンテキストの比較なら、テキスト差分比較ツールに2つの文章を貼り付けるだけで、行単位・単語単位・文字単位の差分が色分けで表示されます。 ファイルはサーバーに送信されず、ブラウザ内だけで処理されるので、社外秘の契約書や仕様書でも安心して使えます。

JSON差分の特殊性 — テキストdiffだけでは不十分

JSON を含む構造化データを比較する場合、単純なテキスト diff では困ることがあります。

第一に、キーの並び順に意味がないこと。 JSON の仕様では「オブジェクトのキー並び順は未定義」とされており、同じデータでもサーバーやライブラリの都合で順序が入れ替わります。 テキスト diff で見るとすべての行が違って見えてしまいますが、意味的には全く同一なケースが多々あります。

第二に、整形・インデントの揺れ。 1行で出力された JSON と、インデント付きに整形された JSON は、テキストとしては全く違いますが、データとしては同じです。

こうした問題を避けるには、JSON を構造として理解したうえで比較するツールが必要です。 ぱんだツールズのJSON差分比較ツールは、キー・値のレベルで差分を抽出するので、並び順やインデントの違いに惑わされません。 API のバージョン間レスポンス比較や、設定ファイルの変更確認に適しています。

注意

JSON 配列の並び順には意味があります(先頭要素・2番目・3番目…という順序が仕様的に保証される)。 一方オブジェクトのキー並び順は未定義です。 JSON差分ツールは通常、「配列は順序あり・オブジェクトは順序なし」として比較しますが、 用途によっては配列も集合として比較したいことがあります。ツールのオプションを確認しましょう。

まとめ

  • diff は2つのテキストを「追加・削除」の最小操作で表現する仕組み。1976年の UNIX diff が原点
  • 差分計算は数学的には最長共通部分列(LCS)問題の裏返し。単純な DP は O(NM) で大きなファイルに不向き
  • Myersアルゴリズム(1986年)が事実上の標準。Git・多くの diff ツールに採用されている
  • unified diff のハンクヘッダー @@ -1,3 +1,4 @@ は「変更前1行目から3行・変更後1行目から4行」の意味
  • 行単位・単語単位・文字単位を用途に応じて使い分ける。GitHub の読みやすさは word-level 強調の功績
  • 行の入れ替えは diff では大量差分として出る。意味的な移動検出は別ツール(--color-moved・difftastic など)を使う
  • JSON はキー並び順やインデントの揺れがあるため、JSON差分比較ツールで構造的に比較するのが確実
  • プレーンテキストの比較はテキスト差分比較ツールで。どちらもブラウザ内処理で機密データも安心

よくある質問

diffコマンドと git diff は同じですか?

根っこの考え方は同じですが、対象と表示が異なります。GNUの diff コマンドは「ファイルAとファイルB」を直接比較する汎用ツールで、unified diff や context diff 形式で差分を出力します。一方 git diff は Git のリポジトリ状態(作業ツリー・ステージングエリア・コミット)を比較するコマンドで、内部的には Myers アルゴリズムをベースにした差分エンジン(libxdiff)を使っています。git diff の出力は unified diff を拡張したもので、ファイルの名前変更検出や、色付け、空白の扱いを制御するオプションなどが追加されています。

unified diff の @@ -1,3 +1,4 @@ はどう読みますか?

「ハンクヘッダー」と呼ばれ、変更された箇所の位置情報を示しています。-1,3 は「変更前ファイルの1行目から3行分」、+1,4 は「変更後ファイルの1行目から4行分」を意味します。つまりこのハンクでは、元の3行を示しながら結果として4行に変わる、という範囲の変更が行われています。その下に続く行は、先頭が空白なら共通行(文脈行)、- なら削除行、+ なら追加行です。GitHub の PR 画面で見かける赤緑の差分も、内部的にはこの unified diff 形式を HTML に整形して表示しているものです。

行の順番が変わっただけなのに大量の差分が出るのはなぜですか?

diff は基本的に行単位で比較するため、行の移動は「削除+追加」として検出されます。たとえば関数の定義順を入れ替えただけでも、数十行の削除と追加として表示されます。これは Myers アルゴリズムが「最小の編集操作で A から B に変換する経路」を求めているだけで、「意味的に同じブロックが移動した」という理解はしないためです。対処としては、git diff の --color-moved オプション(移動検出)や、構文を理解する semantic diff ツール(difftastic など)を使う方法があります。ただし基本の diff ツールでは、このような変更は大量の差分として表示されるのが仕様だと理解しておくのが実用的です。

WordやExcelの「変更履歴」と diff の違いは何ですか?

Word の変更履歴は「編集操作そのもの」を記録しています。ユーザーが削除・挿入・書式変更を行った瞬間に、誰が・いつ・どの操作をしたかをドキュメント内部に記録する仕組みです。一方 diff はテキスト全体の「結果」を比較して、編集操作を逆算で推測するものです。つまり、WordやExcelは編集プロセス込みで履歴を持つのに対し、diff は前後のスナップショットだけから差分を計算します。その代わり diff は「どんなテキストファイルでも、どの時点のスナップショット同士でも」比較できる汎用性があります。

3-wayマージとは何ですか?

共通の祖先バージョンを基準に、2つの変更をマージする手法です。通常の diff(2-way)は「A から B への差分」だけを見ますが、3-way マージでは「共通祖先 O」「変更A」「変更B」の3つを比較し、A と B がそれぞれ O からどう変わったかを独立に把握したうえで統合します。Git の merge・rebase は 3-way マージが基本で、競合(コンフリクト)とは「A と B が O の同じ箇所をそれぞれ別の内容に変えた」状態のことです。2-way diff だけでは「どちらが元でどちらが変更か」が分からないため、共同開発では 3-way が必須になります。

JSON の差分比較は普通のテキスト diff で大丈夫ですか?

厳密にはおすすめしません。JSON は本質的に「順序を持たないキー・値の集合」なので、意味的に同一でも文字列としては異なることがよくあります。たとえばキーの並び順が違うだけの2つのJSONは、テキスト diff では全行差分として表示されてしまいます。また、インデントや空白の差、末尾カンマの有無、改行コードの違いなども「差分」として検出されます。JSON 構造を理解した比較をしたいときは、ぱんだツールズのJSON差分比較ツール(/tools/json-diff)を使うと、キー単位・値単位での差分だけを抽出できます。

文字単位で比較できるツールはありますか?

はい、あります。ぱんだツールズのテキスト差分比較ツール(/tools/text-diff)では、行単位に加えて単語単位・文字単位の比較モードを選べます。文章の一部だけを修正した場合や、1行に複数の変更がある場合、文字単位の比較の方が「実際に何が変わったか」を直感的に把握できます。たとえば「ユーザー登録画面」を「ユーザ登録画面」に変えた場合、行単位では1行全部が変更扱いですが、文字単位なら「ー」1文字だけが削除されたと分かります。

text-diff と json-diff はどう使い分けますか?

比較するデータの性質で使い分けます。プレーンなテキスト(仕様書・ログ・設定ファイル・コード片など)はテキスト差分比較ツール(/tools/text-diff)を使ってください。こちらは行単位・単語単位・文字単位で差分を視覚化します。一方、APIレスポンスや構造化された設定データなど、キー・値のJSON構造として比較したいものはJSON差分比較ツール(/tools/json-diff)が適しています。キーの並び替えや整形の違いを無視して、値そのものの変化だけを抽出できます。どちらもファイルはサーバーに送信されず、ブラウザ内だけで処理されます。

テキスト差分比較ツールに貼り付けたデータはサーバーに送信されますか?

送信されません。ぱんだツールズのテキスト差分比較ツール(/tools/text-diff)もJSON差分比較ツール(/tools/json-diff)も、すべての処理をブラウザ内の JavaScript で完結させています。比較したいテキストがサーバーに送られたり、ログに保存されたりすることはありません。社外秘の仕様書や本番環境の設定ファイルなど、機密性の高いデータの比較にも安心して使えます。

この記事で紹介したツール