class_docstr: 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 syncing 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.
docstr: Find longest matching block in a[alo:ahi] and b[blo:bhi].
  
  By default it will find the longest match in the entirety of a and b.
  
  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)
  Match(a=0, b=4, size=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)
  Match(a=1, b=0, size=4)
  
  If no blocks match, return (alo, blo, 0).
  
  >>> s = SequenceMatcher(None, "ab", "c")
  >>> s.find_longest_match(0, 2, 0, 1)
  Match(a=0, b=0, size=0)
docstr_tok: ["Find","longest","matching","block","in","a","[","alo",":","ahi","]","and","b","[","blo",":","bhi","]",".","By","default","it","will","find","the","longest","match","in","the","entirety","of","a","and","b",".","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",")","Match","(","a=0",",","b=4",",","size=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",")","Match","(","a=1",",","b=0",",","size=4",")","If","no","blocks","match",",","return","(","alo",",","blo",",","0",")",".",">",">",">","s","=","SequenceMatcher","(","None",",","``","ab","''",",","``","c","''",")",">",">",">","s.find_longest_match","(","0",",","2",",","0",",","1",")","Match","(","a=0",",","b=0",",","size=0",")"]
code: def find_longest_match(self, alo=0, ahi=None, blo=0, bhi=None):
      """Find longest matching block in a[alo:ahi] and b[blo:bhi].
  
      By default it will find the longest match in the entirety of a and b.
  
      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)
      Match(a=0, b=4, size=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)
      Match(a=1, b=0, size=4)
  
      If no blocks match, return (alo, blo, 0).
  
      >>> s = SequenceMatcher(None, "ab", "c")
      >>> s.find_longest_match(0, 2, 0, 1)
      Match(a=0, b=0, size=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.bjunk.__contains__,
      )
      if ahi is None:
          ahi = len(a)
      if bhi is None:
          bhi = len(b)
      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 range(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 Match(besti, bestj, bestsize)
  
imports: from heapq import nlargest
  from collections import namedtuple
  from types import GenericAlias
  import re
label_desc: Use image node_id 6 for calling the SequenceMatcher obj's underlying member method code with example usage: obj.find_longest_match(alo, ahi, blo, bhi) and returns: Match