"difflib" --- 差分の計算を助ける
********************************

**ソースコード:** Lib/difflib.py

======================================================================

このモジュールは、シーケンスを比較するためのクラスや関数を提供していま
す。例えば、ファイルの差分を計算して、それを HTML や context diff,
unified diff などいろいろなフォーマットで出力するために、このモジュー
ルを利用できます。ディレクトリやファイル群を比較するためには、
"filecmp" モジュールも参照してください。

class difflib.SequenceMatcher

   柔軟性のあるクラスで、二つのシーケンスの要素が *ハッシュ可能* な型
   であれば、どの型の要素を含むシーケンスも比較可能です。基本的なアル
   ゴリズムは、1980年代の後半に発表された Ratcliff と Obershelp による
   "ゲシュタルトパターンマッチング"と大げさに名づけられたアルゴリズム
   以前から知られている、やや凝ったアルゴリズムです。その考え方は、
   "junk" 要素を含まない最も長い互いに隣接したマッチ列を探すことです。
   ここで、  "junk" 要素とは、空行や空白などの、意味を持たない要素のこ
   とです。 (junk を処理するのは、Ratcliff と Obershelp のアルゴリズム
   に追加された拡張です。)この考え方は、マッチ列の左右に隣接するシーケ
   ンスの断片に対して再帰的にあてはめられます。この方法では編集を最小
   にするシーケンスは生まれませんが、人間の目からみて「正しい感じ」に
   マッチする傾向があります。

   **実行時間:** 基本的な Ratcliff-Obershelp アルゴリズムは、最悪の場
   合3乗、期待値で2乗となります。 "SequenceMatcher" オブジェクトでは、
   最悪のケースで2乗、期待値は比較されるシーケンス中に共通に現れる要素
   数に非常にややこしく依存しています。最良の場合は線形時間になります
   。

   **自動 junk ヒューリスティック:** "SequenceMatcher" は、シーケンス
   の特定の要素を自動的に junk として扱うヒューリスティックをサポート
   しています。このヒューリスティックは、各個要素がシーケンス内に何回
   現れるかを数えます。ある要素の重複数が (最初のものは除いて) 合計で
   シーケンスの 1% 以上になり、そのシーケンスが 200 要素以上なら、その
   要素は "popular" であるものとしてマークされ、シーケンスのマッチング
   の目的からは junk として扱われます。このヒューリスティックは、
   "SequenceMatcher" の作成時に "autojunk" パラメタを "False" に設定す
   ることで無効化できます。

   バージョン 3.2 で追加: *autojunk* パラメータ。

class difflib.Differ

   テキスト行からなるシーケンスを比較するクラスです。人が読むことので
   きる差分を作成します。 Differ クラスは "SequenceMatcher" クラスを利
   用して、行からなるシーケンスを比較したり、(ほぼ)同一の行内の文字を
   比較したりします。

   "Differ" クラスによる差分の各行は、2文字のコードで始まります:

   +------------+---------------------------------------------+
   | コード     | 意味                                        |
   |============|=============================================|
   | "'- '"     | 行はシーケンス1にのみ存在する               |
   +------------+---------------------------------------------+
   | "'+ '"     | 行はシーケンス2にのみ存在する               |
   +------------+---------------------------------------------+
   | "'  '"     | 行は両方のシーケンスで同一                  |
   +------------+---------------------------------------------+
   | "'? '"     | 行は入力シーケンスのどちらにも存在しない    |
   +------------+---------------------------------------------+

   '"?"' で始まる行は、行内のどこに差異が存在するかに注意を向けようと
   します。その行は、入力されたシーケンスのどちらにも存在しません。シ
   ーケンスがタブ文字を含むとき、これらの行は判別しづらいものになるこ
   とがあります。

class difflib.HtmlDiff

   このクラスは、二つのテキストを左右に並べて比較表示し、行間あるいは
   行内の変更点を強調表示するような HTML テーブル (またはテーブルの入
   った完全な HTML ファイル) を生成するために使います。テーブルは完全
   差分モード、コンテキスト差分モードのいずれでも生成できます。

   このクラスのコンストラクタは以下のようになっています:

   __init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK)

      "HtmlDiff" のインスタンスを初期化します。

      *tabsize* はオプションのキーワード引数で、タブストップ幅を指定し
      ます。デフォルトは "8" です。

      *wrapcolumn* はオプションのキーワード引数で、テキストを折り返す
      カラム幅を指定します。デフォルトは "None" で折り返しを行いません
      。

      *linejunk* および *charjunk* はオプションのキーワード引数で、
      "ndiff()"  ("HtmlDiff" はこの関数を使って左右のテキストの差分を
      HTML で生成します) に渡されます。それぞれの引数のデフォルト値お
      よび説明は "ndiff()" のドキュメントを参照してください。

   以下のメソッドが public になっています:

   make_file(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5, *, charset='utf-8')

      *fromlines* と *tolines* (いずれも文字列のリスト) を比較し、行間
      または行内の変更点が強調表示された行差分の入った表を持つ完全な
      HTML ファイルを文字列で返します。

      *fromdesc* および *todesc* はオプションのキーワード引数で、差分
      表示テーブルにおけるそれぞれ差分元、差分先ファイルのカラムのヘッ
      ダになる文字列を指定します (いずれもデフォルト値は空文字列です)
      。

      *context* および *numlines* はともにオプションのキーワード引数で
      す。*context* を "True" にするとコンテキスト差分を表示し、デフォ
      ルトの "False" にすると完全なファイル差分を表示します。
      *numlines* のデフォルト値は "5" で、*context* が "True" の場合、
      *numlines* は強調部分の前後にあるコンテキスト行の数を制御します
      。*context* が "False" の場合、*numlines* は "next" と書かれたハ
      イパーリンクをたどった時に到達する場所が次の変更部分より何行前に
      あるかを制御します (値をゼロにした場合、"next" ハイパーリンクを
      辿ると変更部分の強調表示がブラウザの最上部に表示されるようになり
      ます)。

      注釈:

        *fromdesc* と *todesc* はエスケープされていないHTMLとして解釈
        されます。信頼できないソースからの入力を受け取る際には適切にエ
        スケープされるべきです。

      バージョン 3.5 で変更: *charset* キーワード専用引数が追加されま
      した。HTML 文書のデフォルトの文字集合が "'ISO-8859-1'" から
      "'utf-8'" に変更されました。

   make_table(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5)

      *fromlines* と *tolines* (いずれも文字列のリスト) を比較し、行間
      または行内の変更点が強調表示された行差分の入った完全な HTML テー
      ブルを文字列で返します。

      このメソッドの引数は、 "make_file()" メソッドの引数と同じです。

   "Tools/scripts/diff.py" はこのクラスへのコマンドラインフロントエン
   ドで、使い方を学ぶ上で格好の例題が入っています。

difflib.context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')

   *a* と *b* (文字列のリスト) を比較し、差分 (差分形式の行を生成する
   *ジェネレータ*) を、 context diff のフォーマット(以下「コンテキスト
   形式」)で返します。

   コンテキスト形式は、変更があった行に前後数行を加えてある、コンパク
   トな表現方法です。変更箇所は、変更前/変更後に分けて表します。コンテ
   キスト (変更箇所前後の行) の行数は *n* で指定し、デフォルト値は 3
   です。

   デフォルトで、 diff 制御行 ("***" や "---" を含む行) は改行付きで生
   成されます。 "io.IOBase.readlines()" で作られた入力が
   "io.IOBase.writelines()" で扱うのに適した diff になるので (なぜなら
   入力と出力の両方が改行付きのため) 、これは有用です。

   行末に改行文字を持たない入力に対しては、出力でも改行文字を付加しな
   いように *lineterm* 引数に """" を渡してください。

   コンテキスト形式は、通常、ヘッダにファイル名と変更時刻を持っていま
   す。この情報は、文字列 *fromfile*, *tofile*, *fromfiledate*,
   *tofiledate* で指定できます。変更時刻の書式は、通常、ISO 8601 フォ
   ーマットで表されます。指定しなかった場合のデフォルト値は、空文字列
   です。

   >>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
   >>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
   >>> sys.stdout.writelines(context_diff(s1, s2, fromfile='before.py', tofile='after.py'))
   *** before.py
   --- after.py
   ***************
   *** 1,4 ****
   ! bacon
   ! eggs
   ! ham
     guido
   --- 1,4 ----
   ! python
   ! eggy
   ! hamster
     guido

   より詳細な例は、 difflib のコマンドラインインターフェース を参照し
   てください。

difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)

   「十分」なマッチの上位のリストを返します。*word* はマッチさせたいシ
   ーケンス (大概は文字列) です。*possibilities* は *word* にマッチさ
   せるシーケンスのリスト (大概は文字列のリスト) です。

   オプションの引数 *n* (デフォルトでは "3")はメソッドの返すマッチの最
   大数です。*n* は "0" より大きくなければなりません。

   オプションの引数 *cutoff* (デフォルトでは "0.6")は、区間 [0, 1] に
   入る小数の値です。*word* との一致率がそれ未満の *possibilities* の
   要素は無視されます。

   *possibilities* の要素でマッチした上位(多くても *n* 個)は、類似度の
   スコアに応じて(一番似たものを先頭に)ソートされたリストとして返され
   ます。

   >>> get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy'])
   ['apple', 'ape']
   >>> import keyword
   >>> get_close_matches('wheel', keyword.kwlist)
   ['while']
   >>> get_close_matches('pineapple', keyword.kwlist)
   []
   >>> get_close_matches('accept', keyword.kwlist)
   ['except']

difflib.ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK)

   *a* と *b* (文字列のリスト) を比較し、差分 (差分形式の行を生成する
   *ジェネレータ*) を、 "Differ" のスタイルで返します。

   オプションのキーワード引数 *linejunk* と *charjunk* には、フィルタ
   関数 (または "None") を渡します。

   *linejunk*: 文字列型の引数 1 つを受け取る関数です。文字列が junk の
   場合は真を、そうでない場合は偽を返します。デフォルトでは "None" で
   す。モジュールレべルの関数 "IS_LINE_JUNK()" は、高々 1 つのシャープ
   記号("'#'")を除いて可視の文字を含まない行をフィルタリングするもので
   す。しかし、下層にある "SequenceMatcher" クラスが、どの行が雑音とな
   るほど頻繁に登場するかを動的に分析します。このクラスによる分析は、
   この関数を使用するよりも通常うまく動作します。

   *charjunk*: 文字 (長さ1の文字列) を受け取る関数です。文字列が junk
   の場合は真を、そうでない場合は偽を返します。デフォルトでは、モジュ
   ールレべルの関数 "IS_CHARACTER_JUNK()" であり、これは空白文字類 (空
   白またはタブ、改行文字をこれに含めてはいけません) をフィルタして排
   除します。

   "Tools/scripts/ndiff.py" は、この関数のコマンドラインのフロントエン
   ド（インターフェイス）です。

   >>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
   ...              'ore\ntree\nemu\n'.splitlines(keepends=True))
   >>> print(''.join(diff), end="")
   - one
   ?  ^
   + ore
   ?  ^
   - two
   - three
   ?  -
   + tree
   + emu

difflib.restore(sequence, which)

   差分を生成した元の二つのシーケンスのうち一つを返します。

   "Differ.compare()" または "ndiff()" によって生成された *sequence*
   を与えられると、行頭のプレフィクスを取りのぞいてファイル 1 または 2
   (引数 *which* で指定される) に由来する行を復元します。

   例:

   >>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
   ...              'ore\ntree\nemu\n'.splitlines(keepends=True))
   >>> diff = list(diff) # materialize the generated delta into a list
   >>> print(''.join(restore(diff, 1)), end="")
   one
   two
   three
   >>> print(''.join(restore(diff, 2)), end="")
   ore
   tree
   emu

difflib.unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')

   *a* と *b* (文字列のリスト) を比較し、差分 (差分形式の行を生成する
   *ジェネレータ*) を、 unified diff フォーマット(以下「ユニファイド形
   式」)で返します。

   ユニファイド形式は変更があった行にコンテキストとなる前後数行を加え
   た、コンパクトな表現方法です。変更箇所は (変更前/変更後を分離したブ
   ロックではなく) インラインスタイルで表されます。コンテキストの行数
   は、*n* で指定し、デフォルト値は 3 です。

   デフォルトで、 diff 制御行 ("---", "+++", "@@" を含む行) は改行付き
   で生成されます。 "io.IOBase.readlines()" で作られた入力が
   "io.IOBase.writelines()" で扱うのに適した diff になるので (なぜなら
   入力と出力の両方が改行付きのため) 、これは有用です。

   行末に改行文字を持たない入力に対しては、出力でも改行文字を付加しな
   いように *lineterm* 引数に """" を渡してください。

   コンテキスト形式は、通常、ヘッダにファイル名と変更時刻を持っていま
   す。この情報は、文字列 *fromfile*, *tofile*, *fromfiledate*,
   *tofiledate* で指定できます。変更時刻の書式は、通常、ISO 8601 フォ
   ーマットで表されます。指定しなかった場合のデフォルト値は、空文字列
   です。

   >>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
   >>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
   >>> sys.stdout.writelines(unified_diff(s1, s2, fromfile='before.py', tofile='after.py'))
   --- before.py
   +++ after.py
   @@ -1,4 +1,4 @@
   -bacon
   -eggs
   -ham
   +python
   +eggy
   +hamster
    guido

   より詳細な例は、 difflib のコマンドラインインターフェース を参照し
   てください。

difflib.diff_bytes(dfunc, a, b, fromfile=b'', tofile=b'', fromfiledate=b'', tofiledate=b'', n=3, lineterm=b'\n')

   *dfunc* を使用して *a* と *b* (bytes オブジェクトのリスト) を比較し
   て、差分形式の行 (これも bytes オブジェクトです) を*dfunc* の戻り値
   の形式で返します。*dfunc* は、呼び出し可能である必要があります。一
   般に、これは "unified_diff()" または "context_diff()" です。

   未知のエンコーディングまたは一貫性のないエンコーディングのデータ同
   士を比較できます。*n* 以外のすべての入力は、bytes オブジェクトであ
   る必要があります。*n* 以外のすべての入力を損失なく str に変換して、
   "dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n,
   lineterm)" を呼び出すことにより動作します。*dfunc* の出力は、bytes
   型に変換されます。これにより、受け取る差分形式の行のエンコーディン
   グは、*a* と *b* の未知または一貫性のないエンコーディングと同一にな
   ります。

   バージョン 3.5 で追加.

difflib.IS_LINE_JUNK(line)

   無視できる行のとき "True" を返します。行 *line* は空白、または
   "'#'" ひとつのときに無視できます。それ以外のときには無視できません
   。古いバージョンでは "ndiff()" の引数 *linejunk* にデフォルトで使用
   されました。

difflib.IS_CHARACTER_JUNK(ch)

   無視できる文字のとき "True" を返します。文字 *ch* が空白、またはタ
   ブ文字のときには無視できます。それ以外の時には無視できません。
   "ndiff()" の引数 *charjunk* としてデフォルトで使用されます。

参考:

  Pattern Matching: The Gestalt Approach
     John W. Ratcliff と D. E. Metzener による類似のアルゴリズムに関す
     る議論。Dr. Dobb's Journal 1988年7月号掲載。


SequenceMatcherオブジェクト
===========================

"SequenceMatcher" クラスには、以下のようなコンストラクタがあります:

class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)

   オプションの引数 *isjunk* は、"None" (デフォルトの値です) にするか
   、単一の引数をとる関数でなければなりません。後者の場合、関数はシー
   ケンスの要素を受け取り、要素が junk であり、無視すべきである場合に
   限り真を返すようにしなければなりません。*isjunk* に "None" を渡すと
   、"lambda x: False" を渡したのと同じになります; すなわち、いかなる
   要素も無視しなくなります。例えば以下のような引数を渡すと:

      lambda x: x in " \t"

   空白とタブ文字を無視して文字のシーケンスを比較します。

   オプションの引数 *a* と *b* は、比較される文字列で、デフォルトでは
   空の文字列です。両方のシーケンスの要素は、 *ハッシュ可能* である必
   要があります。

   オプションの引数 *autojunk* は、自動 junk ヒューリスティックを無効
   にするために使えます。

   バージョン 3.2 で追加: *autojunk* パラメータ。

   SequenceMatcher オブジェクトは3つのデータ属性を持っています:
   *bjunk* は、 *isjunk* が "True" であるような *b* の要素の集合です;
   *bpopular* は、 (無効でなければ) ヒューリスティックによって popular
   であると考えられる非ジャンク要素の集合です; *b2j* は、 *b* の残りの
   要素をそれらが生じる位置のリストに写像する dict です。この 3 つは
   "set_seqs()" または "set_seq2()" で *b* がリセットされる場合は常に
   リセットされます。

   バージョン 3.2 で追加: *bjunk* および *bpopular* 属性。

   "SequenceMatcher" オブジェクトは以下のメソッドを持ちます:

   set_seqs(a, b)

      比較される2つの文字列を設定します。

   "SequenceMatcher" オブジェクトは、2つ目のシーケンスについての詳細な
   情報を計算し、キャッシュします。 1つのシーケンスをいくつものシーケ
   ンスと比較する場合、まず "set_seq2()" を使って文字列を設定しておき
   、別の文字列を1つずつ比較するために、繰り返し "set_seq1()" を呼び出
   します。

   set_seq1(a)

      比較を行う1つ目のシーケンスを設定します。比較される2つ目のシーケ
      ンスは変更されません。

   set_seq2(b)

      比較を行う2つ目のシーケンスを設定します。比較される1つ目のシーケ
      ンスは変更されません。

   find_longest_match(alo=0, ahi=None, blo=0, bhi=None)

      "a[alo:ahi]" と "b[blo:bhi]" の中から、最長のマッチ列を探します
      。

      *isjunk* が省略されたか "None" の時、 "find_longest_match()" は
      "a[i:i+k]" が "b[j:j+k]" と等しいような "(i, j, k)" を返します。
      その値は "alo <= i <= i+k <= ahi" かつ "blo <= j <= j+k <= bhi"
      となります。 "(i', j', k')" でも、同じようになります。さらに "k
      >= k'", "i <= i'" が "i == i'", "j <= j'" でも同様です。言い換え
      ると、いくつものマッチ列すべてのうち、 *a* 内で最初に始まるもの
      を返します。そしてその *a* 内で最初のマッチ列すべてのうち *b* 内
      で最初に始まるものを返します。

      >>> s = SequenceMatcher(None, " abcd", "abcd abcd")
      >>> s.find_longest_match(0, 5, 0, 9)
      Match(a=0, b=4, size=5)

      引数 *isjunk* が与えられている場合、上記の通り、はじめに最長のマ
      ッチ列を判定します。ブロック内に junk 要素が見当たらないような追
      加条件の際はこれに該当しません。次にそのマッチ列を、その両側の
      junk 要素にマッチするよう、できる限り広げていきます。そのため結
      果となる列は、探している列のたまたま直前にあった同一の junk 以外
      の junk にはマッチしません。

      以下は前と同じサンプルですが、空白を junk とみなしています。これ
      は "' abcd'" が2つ目の列の末尾にある "' abcd'" にマッチしないよ
      うにしています。代わりに "'abcd'" にはマッチします。そして 2つ目
      の文字列中、一番左の "'abcd'" にマッチします:

      >>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
      >>> s.find_longest_match(0, 5, 0, 9)
      Match(a=1, b=0, size=4)

      どんな列にもマッチしない時は、"(alo, blo, 0)" を返します。

      このメソッドは *named tuple* "Match(a, b, size)" を返します。

      バージョン 3.9 で変更: デフォルト引数が追加されました。

   get_matching_blocks()

      マッチした互いに重複の無いシーケンスを表す、3つ組の値のリストを
      返します。 それぞれの値は "(i, j, n)" という形式で表され、
      "a[i:i+n] == b[j:j+n]" という関係を意味します。3つの値は *i* と
      *j* の間で単調に増加します。

      最後の3つ組はダミーで、"(len(a), len(b), 0)" という値を持ちます
      。これは "n == 0" である唯一のタプルです。もし "(i, j, n)" と
      "(i', j', n')" がリストで並んでいる3つ組で、2つ目が最後の3つ組で
      なければ、"i+n < i'" または "j+n < j'" です。言い換えると並んで
      いる3つ組は常に隣接していない同じブロックを表しています。

         >>> s = SequenceMatcher(None, "abxcd", "abcd")
         >>> s.get_matching_blocks()
         [Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]

   get_opcodes()

      *a* を *b* にするための方法を記述する5つのタプルを返します。それ
      ぞれのタプルは "(tag, i1, i2, j1, j2)" という形式であらわされま
      す。最初のタプルは "i1 == j1 == 0" であり、*i1* はその前にあるタ
      プルの *i2* と同じ値です。同様に *j1* は前の *j2* と同じ値になり
      ます。

      *tag* の値は文字列であり、次のような意味です:

      +-----------------+-----------------------------------------------+
      | 値              | 意味                                          |
      |=================|===============================================|
      | "'replace'"     | "a[i1:i2]" は "b[j1:j2]" に置き換えられる。   |
      +-----------------+-----------------------------------------------+
      | "'delete'"      | "a[i1:i2]" は削除される。この時、"j1 == j2"   |
      |                 | である。                                      |
      +-----------------+-----------------------------------------------+
      | "'insert'"      | "b[j1:j2]" が "a[i1:i1]" に挿入される。この時 |
      |                 | "i1 == i2" である 。                          |
      +-----------------+-----------------------------------------------+
      | "'equal'"       | "a[i1:i2] == b[j1:j2]" (サブシーケンスは等し  |
      |                 | い).                                          |
      +-----------------+-----------------------------------------------+

      例えば:

         >>> a = "qabxcd"
         >>> b = "abycdf"
         >>> s = SequenceMatcher(None, a, b)
         >>> for tag, i1, i2, j1, j2 in s.get_opcodes():
         ...     print('{:7}   a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(
         ...         tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2]))
         delete    a[0:1] --> b[0:0]      'q' --> ''
         equal     a[1:3] --> b[0:2]     'ab' --> 'ab'
         replace   a[3:4] --> b[2:3]      'x' --> 'y'
         equal     a[4:6] --> b[3:5]     'cd' --> 'cd'
         insert    a[6:6] --> b[5:6]       '' --> 'f'

   get_grouped_opcodes(n=3)

      最大 *n* 行までのコンテキストを含むグループを生成するような、 *
      ジェネレータ* を返します。

      このメソッドは、 "get_opcodes()" で返されるグループの中から、似
      たような差異のかたまりに分け、間に挟まっている変更の無い部分を省
      きます。

      グループは "get_opcodes()" と同じ書式で返されます。

   ratio()

      [0, 1] の範囲の浮動小数点数で、シーケンスの類似度を測る値を返し
      ます。

      T が2つのシーケンスの要素数の総計だと仮定し、M をマッチした数と
      すると、この値は 2.0*M / T であらわされます。もしシーケンスがま
      ったく同じ場合、値は "1.0" となり、まったく異なる場合には "0.0"
      となります。

      このメソッドは "get_matching_blocks()" または "get_opcodes()" が
      まだ呼び出されていない場合には非常にコストが高いです。この場合、
      上限を素早く計算するために、 "quick_ratio()" もしくは
      "real_quick_ratio()" を最初に試してみる方がいいかもしれません。

      注釈:

        注意: "ratio()" の呼び出しの結果は引数の順序に依存します。例え
        ば次の通りです:

           >>> SequenceMatcher(None, 'tide', 'diet').ratio()
           0.25
           >>> SequenceMatcher(None, 'diet', 'tide').ratio()
           0.5

   quick_ratio()

      "ratio()" の上界を、より高速に計算します。

   real_quick_ratio()

      "ratio()" の上界を、非常に高速に計算します。

この文字列全体のマッチ率を返す3つのメソッドは、精度の異なる近似値を返
します。 "quick_ratio()" と "real_quick_ratio()" は、常に "ratio()" 以
上の値を返します:

>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0


SequenceMatcher の例
====================

この例は2つの文字列を比較します。空白を "junk" とします:

>>> s = SequenceMatcher(lambda x: x == " ",
...                     "private Thread currentThread;",
...                     "private volatile Thread currentThread;")

"ratio()" は、[0, 1] の範囲の値を返し、シーケンスの類似度を測ります。
経験によると、 "ratio()" の値が0.6を超えると、シーケンスがよく似ている
ことを示します:

>>> print(round(s.ratio(), 3))
0.866

シーケンスのどこがマッチしているかにだけ興味のある時には
"get_matching_blocks()" が手軽でしょう:

>>> for block in s.get_matching_blocks():
...     print("a[%d] and b[%d] match for %d elements" % block)
a[0] and b[0] match for 8 elements
a[8] and b[17] match for 21 elements
a[29] and b[38] match for 0 elements

"get_matching_blocks()" が返す最後のタプルが常にダミーであることに注目
してください。このダミーは "(len(a), len(b), 0)" であり、これはタプル
の最後の要素（マッチする要素の数）が "0" となる唯一のケースです。

はじめのシーケンスがどのようにして2番目のものになるのかを知るには、
"get_opcodes()" を使います:

>>> for opcode in s.get_opcodes():
...     print("%6s a[%d:%d] b[%d:%d]" % opcode)
 equal a[0:8] b[0:8]
insert a[8:8] b[8:17]
 equal a[8:29] b[17:38]

参考:

  * "SequenceMatcher" を使った、シンプルで使えるコードを知るには、この
    モジュールの関数 "get_close_matches()" を参照してください。

  * Simple version control recipe "SequenceMatcher" で作った小規模アプ
    リケーション。


Differ オブジェクト
===================

"Differ" オブジェクトによって生成された差分が **最小** であるなどとは
言いません。むしろ、最小の差分はしばしば直観に反しています。その理由は
、どこでもできるとなれば一致を見いだしてしまうからで、ときには思いがけ
なく100ページも離れたマッチになってしまうのです。一致点を互いに隣接し
たマッチに制限することで、場合によって長めの差分を出力するというコスト
を掛けることにはなっても、ある種の局所性を保つことができるのです。

"Differ" は、以下のようなコンストラクタを持ちます:

class difflib.Differ(linejunk=None, charjunk=None)

   オプションのキーワードパラメータ *linejunk* と *charjunk* は、フィ
   ルタ関数を渡します (使わないときは "None"):

   *linejunk*: ひとつの文字列引数を受け取る関数です。文字列が junk の
   ときに真を返します。デフォルトでは、"None" であり、どんな行であって
   も junk とは見なされません。

   *charjunk*: この関数は文字(長さ1の文字列)を引数として受け取り、文字
   が junk であるときに真を返します。デフォルトは "None" であり、どん
   な文字も junk とは見なされません。

   これらの junk フィルター関数により、差分を発見するマッチングが高速
   化し、差分の行や文字が無視されることがなくなります。説明については
   、 "find_longest_match()" メソッドの *isjunk* 引数の説明をご覧くだ
   さい。

   "Differ" オブジェクトは、以下の1つのメソッドを通して利用されます。
   （差分を生成します）:

   compare(a, b)

      文字列からなる2つのシーケンスを比較し、差分（を表す文字列からな
      るシーケンス）を生成します。

      各シーケンスの要素は、改行で終わる独立した単一行からなる文字列で
      なければなりません。そのようなシーケンスは、ファイル風オブジェク
      トの "readlines()" メソッドによって得ることができます。（得られ
      る）差分は改行文字で終了する文字列のシーケンスとして得られ、ファ
      イル風オブジェクトの "writelines()" メソッドによって出力できる形
      になっています。


Differ の例
===========

以下の例は2つのテキストを比較しています。最初に、テキストを行毎に改行
で終わる文字列のシーケンスにセットアップします (そのようなシーケンスは
、ファイル風オブジェクトの "readlines()" メソッドからも得ることができ
ます):

>>> text1 = '''  1. Beautiful is better than ugly.
...   2. Explicit is better than implicit.
...   3. Simple is better than complex.
...   4. Complex is better than complicated.
... '''.splitlines(keepends=True)
>>> len(text1)
4
>>> text1[0][-1]
'\n'
>>> text2 = '''  1. Beautiful is better than ugly.
...   3.   Simple is better than complex.
...   4. Complicated is better than complex.
...   5. Flat is better than nested.
... '''.splitlines(keepends=True)

次に Differ オブジェクトをインスタンス化します:

>>> d = Differ()

注意:  "Differ" オブジェクトをインスタンス化するとき、行 junk と文字
junk をフィルタリングする関数を渡すことができます。詳細は "Differ()"
コンストラクタを参照してください。

最後に、2つを比較します:

>>> result = list(d.compare(text1, text2))

"result" は文字列のリストなので、pretty-printしてみましょう:

>>> from pprint import pprint
>>> pprint(result)
['    1. Beautiful is better than ugly.\n',
 '-   2. Explicit is better than implicit.\n',
 '-   3. Simple is better than complex.\n',
 '+   3.   Simple is better than complex.\n',
 '?     ++\n',
 '-   4. Complex is better than complicated.\n',
 '?            ^                     ---- ^\n',
 '+   4. Complicated is better than complex.\n',
 '?           ++++ ^                      ^\n',
 '+   5. Flat is better than nested.\n']

これは、複数行の文字列として、次のように出力されます:

>>> import sys
>>> sys.stdout.writelines(result)
    1. Beautiful is better than ugly.
-   2. Explicit is better than implicit.
-   3. Simple is better than complex.
+   3.   Simple is better than complex.
?     ++
-   4. Complex is better than complicated.
?            ^                     ---- ^
+   4. Complicated is better than complex.
?           ++++ ^                      ^
+   5. Flat is better than nested.


difflib のコマンドラインインターフェース
========================================

この例は、 difflib を使って "diff" に似たユーティリティーを作成する方
法を示します。これは、 Python のソース配布物にも、
"Tools/scripts/diff.py" として含まれています。

   #!/usr/bin/env python3
   """ Command line interface to difflib.py providing diffs in four formats:

   * ndiff:    lists every line and highlights interline changes.
   * context:  highlights clusters of changes in a before/after format.
   * unified:  highlights clusters of changes in an inline format.
   * html:     generates side by side comparison with change highlights.

   """

   import sys, os, difflib, argparse
   from datetime import datetime, timezone

   def file_mtime(path):
       t = datetime.fromtimestamp(os.stat(path).st_mtime,
                                  timezone.utc)
       return t.astimezone().isoformat()

   def main():

       parser = argparse.ArgumentParser()
       parser.add_argument('-c', action='store_true', default=False,
                           help='Produce a context format diff (default)')
       parser.add_argument('-u', action='store_true', default=False,
                           help='Produce a unified format diff')
       parser.add_argument('-m', action='store_true', default=False,
                           help='Produce HTML side by side diff '
                                '(can use -c and -l in conjunction)')
       parser.add_argument('-n', action='store_true', default=False,
                           help='Produce a ndiff format diff')
       parser.add_argument('-l', '--lines', type=int, default=3,
                           help='Set number of context lines (default 3)')
       parser.add_argument('fromfile')
       parser.add_argument('tofile')
       options = parser.parse_args()

       n = options.lines
       fromfile = options.fromfile
       tofile = options.tofile

       fromdate = file_mtime(fromfile)
       todate = file_mtime(tofile)
       with open(fromfile) as ff:
           fromlines = ff.readlines()
       with open(tofile) as tf:
           tolines = tf.readlines()

       if options.u:
           diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
       elif options.n:
           diff = difflib.ndiff(fromlines, tolines)
       elif options.m:
           diff = difflib.HtmlDiff().make_file(fromlines,tolines,fromfile,tofile,context=options.c,numlines=n)
       else:
           diff = difflib.context_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)

       sys.stdout.writelines(diff)

   if __name__ == '__main__':
       main()
