"difflib" --- 差分の計算の補助
******************************

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

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

This module provides classes and functions for comparing sequences.
Most of them compare sequences of text lines (for example lists of
strings, or *file objects*) and produce *diffs* -- reports on the
differences. Diffs can be produced in in various formats, including
HTML and context and unified diffs -- formats produced by tools like
*diff* and *git diff*.

Comparisons are done using a matching algorithm implemented in
"SequenceMatcher" -- a flexible class for comparing pairs of sequences
of any type, not just text, so long as the sequence elements are
*hashable*.


Junk heuristic
==============

"difflib" uses a *junk* heuristic: some items are deemed to be *junk*,
and ignored when searching for similarities. Ideally, these are
uninteresting or common items, such as blank lines or whitespace.

This heuristic can speed the algorithm up (because it reduces the
number of possible combinations) and it can produce results that are
more understandable for humans (typically breaking on whitespace). But
it can also cause pathological cases:

* Inappropriately chosen junk items can cause an unexpectedly
  **large** (but still correct) result.

* The default heuristic is **asymmetric**: only the second sequence is
  inspected when determining what is considered junk, so comparing A
  to B can give different results than comparing B to A and reversing
  the result.

By default, if the second input sequence is at least 200 items long,
items that account for more than 1% it are considered *junk*.

Depending on your data, you should consider turning this heuristic off
(setting "SequenceMatcher"'s *autojunk* argument to  to "False") or
tuning it (using the *isjunk* argument, perhaps to one of the
predefined functions).


The "difflib" algorithm
=======================

The algorithm used in "SequenceMatcher" predates, and is a little
fancier than, an algorithm published in the late 1980s by Ratcliff and
Obershelp under the hyperbolic name "gestalt pattern matching." The
idea is to find the longest contiguous subsequence common to both
inputs, then recursively handle the pieces of the sequences to the
left and to the right of the matching subsequence.

参考:

  Pattern Matching: The Gestalt Approach
     Discussion of a similar algorithm by John W. Ratcliff and D. E.
     Metzener. This was published in Dr. Dobb's Journal in July, 1988.

As an extension to the Ratcliff and Obershelp algorithm, "difflib"
searches for the longest *junk-free* contiguous subsequence. See the
Junk heuristic section for details.

**CPython 実装の詳細:** Timing

The basic Ratcliff-Obershelp algorithm is cubic time in the worst case
and quadratic time in the expected case. "difflib"'s algorithm is
quadratic time for the worst case and has expected-case behavior
dependent in a complicated way on how many elements the sequences have
in common; best case time is linear.


Diff generation
===============

class difflib.Differ

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

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

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

   Lines beginning with '"?"' attempt to guide the eye to intraline
   differences, and were not present in either input sequence. These
   lines can be confusing if the sequences contain whitespace
   characters, such as spaces, tabs or line breaks.

   Note that "Differ"-generated deltas make no claim to be **minimal**
   diffs. To the contrary, minimal diffs are often counter-intuitive
   for humans, because they synch up anywhere possible, sometimes at
   accidental matches 100 pages apart. Restricting synch points to
   contiguous matches preserves some notion of locality, at the
   occasional cost of producing a longer diff.

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

   __init__(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つのシーケンスを比較し、差分（を表す文字列からな
      るシーケンス）を生成します。

      Each sequence must contain individual single-line strings ending
      with newlines.  Such sequences can be obtained from the
      "readlines()" method of file-like objects.  The generated delta
      also consists of newline-terminated strings, ready to be printed
      as-is via the "writelines()" method of a file-like object.

class difflib.HtmlDiff

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

   警告:

     The trailing newlines get stripped before the diff, so the result
     can be incomplete. See gh-71896 for details.

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

   __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()" メソッドの引数と同じです。

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 フォ
   ーマットで表されます。指定しなかった場合のデフォルト値は、空文字列
   です。

   >>> import sys
   >>> from difflib import *
   >>> 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*: A function that accepts a single string argument, and
   returns true if the string is junk, or false if not. The default is
   "None". There is also a module-level function "IS_LINE_JUNK()",
   which filters out lines without visible characters, except for at
   most one hash character ("'#'") -- however the underlying
   "SequenceMatcher" class does a dynamic analysis of which lines are
   so frequent as to constitute noise, and this usually works better
   than using this function.

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

   >>> 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* 引数に """" を渡してください。

   The unified diff format normally has a header for filenames and
   modification times.  Any or all of these may be specified using
   strings for *fromfile*, *tofile*, *fromfiledate*, and *tofiledate*.
   The modification times are normally expressed in the ISO 8601
   format. If not specified, the strings default to blanks.

   >>> 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* の未知または一貫性のないエンコーディングと同一にな
   ります。

   Added in version 3.5.


Junk definition functions
=========================

difflib.IS_LINE_JUNK(line)

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

difflib.IS_CHARACTER_JUNK(ch)

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


SequenceMatcher objects
=======================

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 で変更: Added the *autojunk* parameter.

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

   Added in version 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()" を最初に試してみる方がいいかもしれません。

   quick_ratio()

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

   real_quick_ratio()

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

The three methods that return the ratio of matching to total
characters can give different results due to differing levels of
approximation, although "quick_ratio()" and "real_quick_ratio()" are
always at least as large as "ratio()":

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


使用例
======


SequenceMatcher examples
------------------------

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

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

"ratio()" returns a float in [0, 1], measuring the similarity of the
sequences.  As a rule of thumb, a "ratio()" value over 0.6 means the
sequences are close matches:

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

If you're only interested in where the sequences match,
"get_matching_blocks()" is handy:

>>> 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

Note that the last tuple returned by "get_matching_blocks()" is always
a dummy, "(len(a), len(b), 0)", and this is the only case in which the
last tuple element (number of elements matched) is "0".

If you want to know how to change the first sequence into the second,
use "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 for a small application built with
    "SequenceMatcher".


Differ example
--------------

This example compares two texts. First we set up the texts, sequences
of individual single-line strings ending with newlines (such sequences
can also be obtained from the "readlines()" method of file-like
objects):

>>> 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 のコマンドラインインターフェース
----------------------------------------

This example shows how to use difflib to create a "diff"-like utility.

   """ 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
   import datetime as dt

   def file_mtime(path):
       t = dt.datetime.fromtimestamp(os.stat(path).st_mtime,
                                     dt.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()


ndiff example
-------------

This example shows how to use "difflib.ndiff()".

   """ndiff [-q] file1 file2
       or
   ndiff (-r1 | -r2) < ndiff_output > file1_or_file2

   Print a human-friendly file difference report to stdout.  Both inter-
   and intra-line differences are noted.  In the second form, recreate file1
   (-r1) or file2 (-r2) on stdout, from an ndiff report on stdin.

   In the first form, if -q ("quiet") is not specified, the first two lines
   of output are

   -: file1
   +: file2

   Each remaining line begins with a two-letter code:

       "- "    line unique to file1
       "+ "    line unique to file2
       "  "    line common to both files
       "? "    line not present in either input file

   Lines beginning with "? " attempt to guide the eye to intraline
   differences, and were not present in either input file.  These lines can be
   confusing if the source files contain tab characters.

   The first file can be recovered by retaining only lines that begin with
   "  " or "- ", and deleting those 2-character prefixes; use ndiff with -r1.

   The second file can be recovered similarly, but by retaining only "  " and
   "+ " lines; use ndiff with -r2; or, on Unix, the second file can be
   recovered by piping the output through

       sed -n '/^[+ ] /s/^..//p'
   """

   __version__ = 1, 7, 0

   import difflib, sys

   def fail(msg):
       out = sys.stderr.write
       out(msg + "\n\n")
       out(__doc__)
       return 0

   # open a file & return the file object; gripe and return 0 if it
   # couldn't be opened
   def fopen(fname):
       try:
           return open(fname)
       except IOError as detail:
           return fail("couldn't open " + fname + ": " + str(detail))

   # open two files & spray the diff to stdout; return false iff a problem
   def fcompare(f1name, f2name):
       f1 = fopen(f1name)
       f2 = fopen(f2name)
       if not f1 or not f2:
           return 0

       a = f1.readlines(); f1.close()
       b = f2.readlines(); f2.close()
       for line in difflib.ndiff(a, b):
           print(line, end=' ')

       return 1

   # crack args (sys.argv[1:] is normal) & compare;
   # return false iff a problem

   def main(args):
       import getopt
       try:
           opts, args = getopt.getopt(args, "qr:")
       except getopt.error as detail:
           return fail(str(detail))
       noisy = 1
       qseen = rseen = 0
       for opt, val in opts:
           if opt == "-q":
               qseen = 1
               noisy = 0
           elif opt == "-r":
               rseen = 1
               whichfile = val
       if qseen and rseen:
           return fail("can't specify both -q and -r")
       if rseen:
           if args:
               return fail("no args allowed with -r option")
           if whichfile in ("1", "2"):
               restore(whichfile)
               return 1
           return fail("-r value must be 1 or 2")
       if len(args) != 2:
           return fail("need 2 filename args")
       f1name, f2name = args
       if noisy:
           print('-:', f1name)
           print('+:', f2name)
       return fcompare(f1name, f2name)

   # read ndiff output from stdin, and print file1 (which=='1') or
   # file2 (which=='2') to stdout

   def restore(which):
       restored = difflib.restore(sys.stdin.readlines(), which)
       sys.stdout.writelines(restored)

   if __name__ == '__main__':
       main(sys.argv[1:])
