概要
Pythonのdifflibのうち、私のような軟派な用途にあたり便利だな・面白いなと思ったものをまとめてみました。
オレ様チョイス 3大difflibライブラリ
(1) difflib.SequenceMatcher・・・ ratio()
2つの文字列の類似度です。通常の文字列のequal判定とは異なり、似ている文字列どおしであれば、1に近い値が戻ります。
中身の単語の順序を入れ替えたような文字列でも0よりはそれなりに大きい数字が戻るようです。
In [162]: from difflib import SequenceMatcher In [167]: SequenceMatcher(None, "abcd", "abce").ratio() Out[167]: 0.75 In [166]: SequenceMatcher(None, "ばなな みかん りんご", "りんご みかん ばなな").ratio() Out[166]: 0.45454545454545453 In [165]: SequenceMatcher(None, ["ばなな","みかん","りんご"], ["りんご","みかん","ばなな"]).ratio() Out[165]: 0.3333333333333333
(2) difflib.get_close_matches()
ある文字列の配列のうち、インプットの文字列に「近い」と見なされるものを戻します。
In [171]: import difflib In [171]: difflib.get_close_matches('appel',['ape', 'apple', 'peach', 'puppy']) Out[171]: ['apple', 'ape']
(3) difflib.HtmlDiff ・・・ make_file、make_table
差異がある部分(Added/Changed/Deleted)を、色で可視化して対比して確認できるような、htmlファイルを吐き出します。
ブラウザ起動まで含めて、ワンライナーにしておいて、2つのファイルの差異を大まかに把握するための、目grepに便利かも。
In [153]: print(difflib.HtmlDiff().make_file(["あいうえお","たちつてと","わおん"],["あいうえか","たちすてと","わを"])) <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title></title> <style type="text/css"> table.diff {font-family:Courier; border:medium;} .diff_header {background-color:#e0e0e0} td.diff_header {text-align:right} .diff_next {background-color:#c0c0c0} .diff_add {background-color:#aaffaa} .diff_chg {background-color:#ffff77} .diff_sub {background-color:#ffaaaa} </style> </head> <body> <table class="diff" id="difflib_chg_to4__top" cellspacing="0" cellpadding="0" rules="groups" > <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <colgroup></colgroup> <tbody> <tr><td class="diff_next" id="difflib_chg_to4__0"><a href="#difflib_chg_to4__top">t</a></td><td class="diff_header" id="from4_1">1</td><td nowrap="nowrap">あいうえ<span class="diff_chg">お</span></td><td class="diff_next"><a href="#difflib_chg_to4__top">t</a></td><td class="diff_header" id="to4_1">1</td><td nowrap="nowrap">あいうえ<span class="diff_chg">か</span></td></tr> <tr><td class="diff_next"></td><td class="diff_header" id="from4_2">2</td><td nowrap="nowrap">たち<span class="diff_chg">つ</span>てと</td><td class="diff_next"></td><td class="diff_header" id="to4_2">2</td><td nowrap="nowrap">たち<span class="diff_chg">す</span>てと</td></tr> <tr><td class="diff_next"></td><td class="diff_header" id="from4_3">3</td><td nowrap="nowrap"><span class="diff_sub">わおん</span></td><td class="diff_next"></td><td class="diff_header" id="to4_3">3</td><td nowrap="nowrap"><span class="diff_add">わを</span></td></tr> </tbody> </table> <table class="diff" summary="Legends"> <tr> <th colspan="2"> Legends </th> </tr> <tr> <td> <table border="" summary="Colors"> <tr><th> Colors </th> </tr> <tr><td class="diff_add"> Added </td></tr> <tr><td class="diff_chg">Changed</td> </tr> <tr><td class="diff_sub">Deleted</td> </tr> </table></td> <td> <table border="" summary="Links"> <tr><th colspan="2"> Links </th> </tr> <tr><td>(f)irst change</td> </tr> <tr><td>(n)ext change</td> </tr> <tr><td>(t)op</td> </tr> </table></td> </tr> </table> </body> </html>
difflib.SequenceMatcher再び
SequenceMatcherには、いわゆる GNU diffっぽい出力が得られるメソッドがいくつかあります。
一方で、「共通する部分」を抜き出す、正規表現を使わない変則grepぽいメソッドとして、find_longest_matchとget_matched_blocksがありますので、これらを紹介します。
なお、共通する部分を抜き出せますので、(ある程度ですが)共通しない部分も抜き出せます。
find_longest_match
最長一致部分を抜き出せるインデックスのタプルが得られます。
In [175]: i,j,k = SequenceMatcher(None,'あいうえおかきく','あいうEおかきく').find_longest_match(0,8,0,8) In [176]: a ='あいうえおかきく' In [177]: b = 'あいうEおかきく' In [178]: i,j,k = SequenceMatcher(None,a,b).find_longest_match(0,len(a),0,len(b)) In [180]: a[i:i+k] Out[180]: 'おかきく' In [181]: b[j:j+k] Out[181]: 'おかきく' In [182]: a[0:i] Out[182]: 'あいうえ' In [183]: b[0:j] Out[183]: 'あいうE'
get_matching_blocks
途中の不一致部分を境に前部分と後ろ部分を抜き出せるインデックスを表すタプルの配列が得られます。 (※2020/12/25 以下、初出時より誤り・不正確なまとめがありましたので、訂正しています。)
ただし、一致部分が3つ以上の場合はエラーになるようです。
In [73]: str1,str2 = 'あいうえおかきくけこさし','あいxえxかxくけxさし' In [74]: [x,y,*_] = SequenceMatcher(None,str1,str2).get_matching_blocks() In [75]: x Out[75]: Match(a=0, b=0, size=2) In [76]: y Out[76]: Match(a=3, b=3, size=1) In [77]: str1[0:2] Out[77]: 'あい' In [79]: str1[3:3+1] Out[79]: 'え' In [80]: str2[0:2] Out[80]: 'あい' In [81]: str1,str2 = 'あいうえおかきくけこさし','あいxxxえxかxくけxさし' ↑ 「xxx」に注目 In [82]: [x,y,*_] = SequenceMatcher(None,str1,str2).get_matching_blocks() In [83]: y Out[83]: Match(a=3, b=5, size=1) ↓ 「diff」らしく同じシーケンスが現れるところまで考慮してくれる。 In [84]: str1[3:3+1] Out[84]: 'え' In [85]: str2[5:5+1] Out[85]: 'え'