2019 lines · 78.9 KB · Python
RawDownload
#! /usr/bin/env python """Module difflib -- helpers for computing deltas between objects. Function get_close_matches(word, possibilities, n=3, cutoff=0.6):    Use SequenceMatcher to return list of the best "good enough" matches. Function context_diff(a, b):    For two lists of strings, return a delta in context diff format. Function ndiff(a, b):    Return a delta: the difference between `a` and `b` (lists of strings). Function restore(delta, which):    Return one of the two sequences that generated an ndiff delta. Function unified_diff(a, b):    For two lists of strings, return a delta in unified diff format. Class SequenceMatcher:    A flexible class for comparing pairs of sequences of any type. Class Differ:    For producing human-readable deltas from sequences of lines of text. Class HtmlDiff:    For producing HTML side by side comparison with change highlights.""" __all__ = ['get_close_matches', 'ndiff', 'restore', 'SequenceMatcher',           'Differ','IS_CHARACTER_JUNK', 'IS_LINE_JUNK', 'context_diff',           'unified_diff', 'HtmlDiff'] import heapq def _calculate_ratio(matches, length):    if length:        return 2.0 * matches / length    return 1.0 class SequenceMatcher:     """    SequenceMatcher is a flexible class for comparing pairs of sequences of    any type, so long as the sequence elements are hashable.  The basic    algorithm predates, and is a little fancier than, an algorithm    published in the late 1980's by Ratcliff and Obershelp under the    hyperbolic name "gestalt pattern matching".  The basic idea is to find    the longest contiguous matching subsequence that contains no "junk"    elements (R-O doesn't address junk).  The same idea is then applied    recursively to the pieces of the sequences to the left and to the right    of the matching subsequence.  This does not yield minimal edit    sequences, but does tend to yield matches that "look right" to people.     SequenceMatcher tries to compute a "human-friendly diff" between two    sequences.  Unlike e.g. UNIX(tm) diff, the fundamental notion is the    longest *contiguous* & junk-free matching subsequence.  That's what    catches peoples' eyes.  The Windows(tm) windiff has another interesting    notion, pairing up elements that appear uniquely in each sequence.    That, and the method here, appear to yield more intuitive difference    reports than does diff.  This method appears to be the least vulnerable    to synching up on blocks of "junk lines", though (like blank lines in    ordinary text files, or maybe "<P>" lines in HTML files).  That may be    because this is the only method of the 3 that has a *concept* of    "junk" <wink>.     Example, comparing two strings, and considering blanks to be "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]     See the Differ class for a fancy human-friendly file differencer, which    uses SequenceMatcher both to compare sequences of lines, and to compare    sequences of characters within similar (near-matching) lines.     See also function get_close_matches() in this module, which shows how    simple code building on SequenceMatcher can be used to do useful work.     Timing:  Basic R-O is cubic time worst case and quadratic time expected    case.  SequenceMatcher 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.     Methods:     __init__(isjunk=None, a='', b='')        Construct a SequenceMatcher.     set_seqs(a, b)        Set the two sequences to be compared.     set_seq1(a)        Set the first sequence to be compared.     set_seq2(b)        Set the second sequence to be compared.     find_longest_match(alo, ahi, blo, bhi)        Find longest matching block in a[alo:ahi] and b[blo:bhi].     get_matching_blocks()        Return list of triples describing matching subsequences.     get_opcodes()        Return list of 5-tuples describing how to turn a into b.     ratio()        Return a measure of the sequences' similarity (float in [0,1]).     quick_ratio()        Return an upper bound on .ratio() relatively quickly.     real_quick_ratio()        Return an upper bound on ratio() very quickly.    """     def __init__(self, isjunk=None, a='', b=''):        """Construct a SequenceMatcher.         Optional arg isjunk is None (the default), or a one-argument        function that takes a sequence element and returns true iff the        element is junk.  None is equivalent to passing "lambda x: 0", i.e.        no elements are considered to be junk.  For example, pass            lambda x: x in " \\t"        if you're comparing lines as sequences of characters, and don't        want to synch up on blanks or hard tabs.         Optional arg a is the first of two sequences to be compared.  By        default, an empty string.  The elements of a must be hashable.  See        also .set_seqs() and .set_seq1().         Optional arg b is the second of two sequences to be compared.  By        default, an empty string.  The elements of b must be hashable. See        also .set_seqs() and .set_seq2().        """         # Members:        # a        #      first sequence        # b        #      second sequence; differences are computed as "what do        #      we need to do to 'a' to change it into 'b'?"        # b2j        #      for x in b, b2j[x] is a list of the indices (into b)        #      at which x appears; junk elements do not appear        # fullbcount        #      for x in b, fullbcount[x] == the number of times x        #      appears in b; only materialized if really needed (used        #      only for computing quick_ratio())        # matching_blocks        #      a list of (i, j, k) triples, where a[i:i+k] == b[j:j+k];        #      ascending & non-overlapping in i and in j; terminated by        #      a dummy (len(a), len(b), 0) sentinel        # opcodes        #      a list of (tag, i1, i2, j1, j2) tuples, where tag is        #      one of        #          'replace'   a[i1:i2] should be replaced by b[j1:j2]        #          'delete'    a[i1:i2] should be deleted        #          'insert'    b[j1:j2] should be inserted        #          'equal'     a[i1:i2] == b[j1:j2]        # isjunk        #      a user-supplied function taking a sequence element and        #      returning true iff the element is "junk" -- this has        #      subtle but helpful effects on the algorithm, which I'll        #      get around to writing up someday <0.9 wink>.        #      DON'T USE!  Only __chain_b uses this.  Use isbjunk.        # isbjunk        #      for x in b, isbjunk(x) == isjunk(x) but much faster;        #      it's really the has_key method of a hidden dict.        #      DOES NOT WORK for x in a!        # isbpopular        #      for x in b, isbpopular(x) is true iff b is reasonably long        #      (at least 200 elements) and x accounts for more than 1% of        #      its elements.  DOES NOT WORK for x in a!         self.isjunk = isjunk        self.a = self.b = None        self.set_seqs(a, b)     def set_seqs(self, a, b):        """Set the two sequences to be compared.         >>> s = SequenceMatcher()        >>> s.set_seqs("abcd", "bcde")        >>> s.ratio()        0.75        """         self.set_seq1(a)        self.set_seq2(b)     def set_seq1(self, a):        """Set the first sequence to be compared.         The second sequence to be compared is not changed.         >>> s = SequenceMatcher(None, "abcd", "bcde")        >>> s.ratio()        0.75        >>> s.set_seq1("bcde")        >>> s.ratio()        1.0        >>>         SequenceMatcher computes and caches detailed information about the        second sequence, so if you want to compare one sequence S against        many sequences, use .set_seq2(S) once and call .set_seq1(x)        repeatedly for each of the other sequences.         See also set_seqs() and set_seq2().        """         if a is self.a:            return        self.a = a        self.matching_blocks = self.opcodes = None     def set_seq2(self, b):        """Set the second sequence to be compared.         The first sequence to be compared is not changed.         >>> s = SequenceMatcher(None, "abcd", "bcde")        >>> s.ratio()        0.75        >>> s.set_seq2("abcd")        >>> s.ratio()        1.0        >>>         SequenceMatcher computes and caches detailed information about the        second sequence, so if you want to compare one sequence S against        many sequences, use .set_seq2(S) once and call .set_seq1(x)        repeatedly for each of the other sequences.         See also set_seqs() and set_seq1().        """         if b is self.b:            return        self.b = b        self.matching_blocks = self.opcodes = None        self.fullbcount = None        self.__chain_b()     # For each element x in b, set b2j[x] to a list of the indices in    # b where x appears; the indices are in increasing order; note that    # the number of times x appears in b is len(b2j[x]) ...    # when self.isjunk is defined, junk elements don't show up in this    # map at all, which stops the central find_longest_match method    # from starting any matching block at a junk element ...    # also creates the fast isbjunk function ...    # b2j also does not contain entries for "popular" elements, meaning    # elements that account for more than 1% of the total elements, and    # when the sequence is reasonably large (>= 200 elements); this can    # be viewed as an adaptive notion of semi-junk, and yields an enormous    # speedup when, e.g., comparing program files with hundreds of    # instances of "return NULL;" ...    # note that this is only called when b changes; so for cross-product    # kinds of matches, it's best to call set_seq2 once, then set_seq1    # repeatedly     def __chain_b(self):        # Because isjunk is a user-defined (not C) function, and we test        # for junk a LOT, it's important to minimize the number of calls.        # Before the tricks described here, __chain_b was by far the most        # time-consuming routine in the whole module!  If anyone sees        # Jim Roskind, thank him again for profile.py -- I never would        # have guessed that.        # The first trick is to build b2j ignoring the possibility        # of junk.  I.e., we don't call isjunk at all yet.  Throwing        # out the junk later is much cheaper than building b2j "right"        # from the start.        b = self.b        n = len(b)        self.b2j = b2j = {}        populardict = {}        for i, elt in enumerate(b):            if elt in b2j:                indices = b2j[elt]                if n >= 200 and len(indices) * 100 > n:                    populardict[elt] = 1                    del indices[:]                else:                    indices.append(i)            else:                b2j[elt] = [i]         # Purge leftover indices for popular elements.        for elt in populardict:            del b2j[elt]         # Now b2j.keys() contains elements uniquely, and especially when        # the sequence is a string, that's usually a good deal smaller        # than len(string).  The difference is the number of isjunk calls        # saved.        isjunk = self.isjunk        junkdict = {}        if isjunk:            for d in populardict, b2j:                for elt in d.keys():                    if isjunk(elt):                        junkdict[elt] = 1                        del d[elt]         # Now for x in b, isjunk(x) == x in junkdict, but the        # latter is much faster.  Note too that while there may be a        # lot of junk in the sequence, the number of *unique* junk        # elements is probably small.  So the memory burden of keeping        # this dict alive is likely trivial compared to the size of b2j.        self.isbjunk = junkdict.has_key        self.isbpopular = populardict.has_key     def find_longest_match(self, alo, ahi, blo, bhi):        """Find longest matching block in a[alo:ahi] and b[blo:bhi].         If isjunk is not defined:         Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where            alo <= i <= i+k <= ahi            blo <= j <= j+k <= bhi        and for all (i',j',k') meeting those conditions,            k >= k'            i <= i'            and if i == i', j <= j'         In other words, of all maximal matching blocks, return one that        starts earliest in a, and of all those maximal matching blocks that        start earliest in a, return the one that starts earliest in b.         >>> s = SequenceMatcher(None, " abcd", "abcd abcd")        >>> s.find_longest_match(0, 5, 0, 9)        (0, 4, 5)         If isjunk is defined, first the longest matching block is        determined as above, but with the additional restriction that no        junk element appears in the block.  Then that block is extended as        far as possible by matching (only) junk elements on both sides.  So        the resulting block never matches on junk except as identical junk        happens to be adjacent to an "interesting" match.         Here's the same example as before, but considering blanks to be        junk.  That prevents " abcd" from matching the " abcd" at the tail        end of the second sequence directly.  Instead only the "abcd" can        match, and matches the leftmost "abcd" in the second sequence:         >>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")        >>> s.find_longest_match(0, 5, 0, 9)        (1, 0, 4)         If no blocks match, return (alo, blo, 0).         >>> s = SequenceMatcher(None, "ab", "c")        >>> s.find_longest_match(0, 2, 0, 1)        (0, 0, 0)        """         # CAUTION:  stripping common prefix or suffix would be incorrect.        # E.g.,        #    ab        #    acab        # Longest matching block is "ab", but if common prefix is        # stripped, it's "a" (tied with "b").  UNIX(tm) diff does so        # strip, so ends up claiming that ab is changed to acab by        # inserting "ca" in the middle.  That's minimal but unintuitive:        # "it's obvious" that someone inserted "ac" at the front.        # Windiff ends up at the same place as diff, but by pairing up        # the unique 'b's and then matching the first two 'a's.         a, b, b2j, isbjunk = self.a, self.b, self.b2j, self.isbjunk        besti, bestj, bestsize = alo, blo, 0        # find longest junk-free match        # during an iteration of the loop, j2len[j] = length of longest        # junk-free match ending with a[i-1] and b[j]        j2len = {}        nothing = []        for i in xrange(alo, ahi):            # look at all instances of a[i] in b; note that because            # b2j has no junk keys, the loop is skipped if a[i] is junk            j2lenget = j2len.get            newj2len = {}            for j in b2j.get(a[i], nothing):                # a[i] matches b[j]                if j < blo:                    continue                if j >= bhi:                    break                k = newj2len[j] = j2lenget(j-1, 0) + 1                if k > bestsize:                    besti, bestj, bestsize = i-k+1, j-k+1, k            j2len = newj2len         # Extend the best by non-junk elements on each end.  In particular,        # "popular" non-junk elements aren't in b2j, which greatly speeds        # the inner loop above, but also means "the best" match so far        # doesn't contain any junk *or* popular non-junk elements.        while besti > alo and bestj > blo and \              not isbjunk(b[bestj-1]) and \              a[besti-1] == b[bestj-1]:            besti, bestj, bestsize = besti-1, bestj-1, bestsize+1        while besti+bestsize < ahi and bestj+bestsize < bhi and \              not isbjunk(b[bestj+bestsize]) and \              a[besti+bestsize] == b[bestj+bestsize]:            bestsize += 1         # Now that we have a wholly interesting match (albeit possibly        # empty!), we may as well suck up the matching junk on each        # side of it too.  Can't think of a good reason not to, and it        # saves post-processing the (possibly considerable) expense of        # figuring out what to do with it.  In the case of an empty        # interesting match, this is clearly the right thing to do,        # because no other kind of match is possible in the regions.        while besti > alo and bestj > blo and \              isbjunk(b[bestj-1]) and \              a[besti-1] == b[bestj-1]:            besti, bestj, bestsize = besti-1, bestj-1, bestsize+1        while besti+bestsize < ahi and bestj+bestsize < bhi and \              isbjunk(b[bestj+bestsize]) and \              a[besti+bestsize] == b[bestj+bestsize]:            bestsize = bestsize + 1         return besti, bestj, bestsize     def get_matching_blocks(self):        """Return list of triples describing matching subsequences.         Each triple is of the form (i, j, n), and means that        a[i:i+n] == b[j:j+n].  The triples are monotonically increasing in        i and in j.  New in Python 2.5, it's also guaranteed that if        (i, j, n) and (i', j', n') are adjacent triples in the list, and        the second is not the last triple in the list, then i+n != i' or        j+n != j'.  IOW, adjacent triples never describe adjacent equal        blocks.         The last triple is a dummy, (len(a), len(b), 0), and is the only        triple with n==0.         >>> s = SequenceMatcher(None, "abxcd", "abcd")        >>> s.get_matching_blocks()        [(0, 0, 2), (3, 2, 2), (5, 4, 0)]        """         if self.matching_blocks is not None:            return self.matching_blocks        la, lb = len(self.a), len(self.b)         # This is most naturally expressed as a recursive algorithm, but        # at least one user bumped into extreme use cases that exceeded        # the recursion limit on their box.  So, now we maintain a list        # ('queue`) of blocks we still need to look at, and append partial        # results to `matching_blocks` in a loop; the matches are sorted        # at the end.        queue = [(0, la, 0, lb)]        matching_blocks = []        while queue:            alo, ahi, blo, bhi = queue.pop()            i, j, k = x = self.find_longest_match(alo, ahi, blo, bhi)            # a[alo:i] vs b[blo:j] unknown            # a[i:i+k] same as b[j:j+k]            # a[i+k:ahi] vs b[j+k:bhi] unknown            if k:   # if k is 0, there was no matching block                matching_blocks.append(x)                if alo < i and blo < j:                    queue.append((alo, i, blo, j))                if i+k < ahi and j+k < bhi:                    queue.append((i+k, ahi, j+k, bhi))        matching_blocks.sort()

Lines 1–500 of 2019.