FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

difflib.SequenceMatcher not matching long sequences · Issue #47235 · python/cpython · GitHub

Repository navigation

difflib.SequenceMatcher not matching long sequences #47235

Closed

Description

mannequin
BPO 2986
Nosy @tim-one, @warsaw, @birkenfeld, @terryjreedy, @jcea, @pitrou, @bitdancer
PRs
  • bpo-2986: Allow disabling difflib's automatic junk heuristic #17082
  • Superseder
  • bpo-10534: difflib.SequenceMatcher: expose junk sets, deprecate undocumented isb... functions.
  • Files
  • difflib_test_inq.py: test file for difflib.SequenceMatcher comparing strings with minimal differences
  • issue2986.docs26.1.patch
  • issue2986.fix27.4.patch
  • issue2986.docs31.1.patch
  • issue2986.fix27.5.patch
  • issue2986.fix32.5.patch: Version of issue2986.fix27.5.patch that applies and passes tests in Python 3.2a.
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/terryjreedy'
    closed_at = <Date 2010-11-25.20:23:36.839>
    created_at = <Date 2008-05-27.20:29:56.025>
    labels = ['type-bug', 'library', 'docs']
    title = 'difflib.SequenceMatcher not matching long sequences'
    updated_at = <Date 2019-11-07.16:24:52.380>
    user = 'https://bugs.python.org/hagna'

    bugs.python.org fields:

    activity = <Date 2019-11-07.16:24:52.380>
    actor = 'python-dev'
    assignee = 'terry.reedy'
    closed = True
    closed_date = <Date 2010-11-25.20:23:36.839>
    closer = 'terry.reedy'
    components = ['Documentation', 'Library (Lib)']
    creation = <Date 2008-05-27.20:29:56.025>
    creator = 'hagna'
    dependencies = []
    files = ['17001', '18171', '18719', '19538', '19569', '19675']
    hgrepos = []
    issue_num = 2986
    keywords = ['patch']
    message_count = 37.0
    messages = ['67428', '84387', '84446', '84449', '93438', '103660', '108636', '108856', '109090', '109442', '109507', '109636', '109639', '109654', '110251', '110261', '111372', '111425', '112116', '112120', '112490', '115335', '115419', '115787', '120713', '120927', '120939', '120992', '121079', '121596', '121662', '121697', '121902', '122335', '122337', '122338', '122401']
    nosy_count = 19.0
    nosy_names = ['tim.peters', 'barry', 'georg.brandl', 'terry.reedy', 'jcea', 'jimjjewett', 'sjmachin', 'gjb1002', 'ggenellina', 'pitrou', 'rtvd', 'vbr', 'LambertDW', 'hodgestar', 'hagna', 'r.david.murray', 'eli.bendersky', 'janpf', 'mrotondo']
    pr_nums = ['17082']
    priority = 'high'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = '10534'
    type = 'behavior'
    url = 'https://bugs.python.org/issue2986'
    versions = ['Python 2.6', 'Python 3.1', 'Python 2.7', 'Python 3.2']

    Activity

    1. hagna commented on May 27, 2008

      hagnamannequin
      MannequinAuthor

      The following code shows no matches though the strings clearly match.

      from difflib import * 
      
      a =
      '''3904320338155955662857322172779218727992471109386112515279452352973279311752006856588512503244702012502812653160306927721351031250270279878152125021081471125246894603319162986283456469448293252335442814953964029718671705515246437056879456095915444174665464026255415736754542680178373675412998898571410483714801783736754144828361714801783736754133068408714801783736754140859665714801783736754153851004471480178373675415715864371410690714801783736754147488890714801783736205957668017837367545448801783104170539154677705102536314736754477780178373675415217103227148017837367541737811137714801783736754172791151671480178373675417692995271480178373675417575983571480178373675417398965871480178310417055026467770551235573705687945609591544562532964082675415736300610425832914520311514810301595721999571547897879113780178373618951021983280377781981989237498913678981414213198924949892679989164882577810944751102884217048258978791137801783104170511836542073627327981801279360326159714801783736171798080178310415420736447510213871790638471586131412631592131012571210126718031314200414571314893700123874777987006697747115770067074789312578013869801783104120529166337056879456095918495136604565251349544838956219513495753741344870733943253617458316356794745831634651172458316348316144586052838244151360641656349118903581890331689038658903263218549028909605134957536316060'''
      b =
      '''4634320338155955662857322172779218727992471109386112515279452352973279311752006856588512503244702012502812653160306927721351031250270279878152125021081471125246894603319162986283456469448293252335442814953964029718671705515246437056879456095915444174665464026255415736754542680178373675412998898571410483714801783736754144828361714801783736754133068408714801783736754140859665714801783736754153851004471480178373675415715864371410690714801783736754147488890714801783736205957668017837367545448801783104170539154677705102536314736754477780178373675413182108117148017837367541737811137714801783736754172791151671480178373675417692995271480178373675417575983571480178373675417398965871480178310417055026467770551235573705687945609591544562532964082675415736300610425832914520311514810301595721999571547897879113780178373618951021983280377781981989237498913678981414213198924949892679989164882577810944751102884217048258978791137801783104170511836542073627327981801279360326159714801783736171798080178310415420736447510213871790638471412131420041457131485122165131466702097131466731723131466741536131466751581131466771649131466761975131467212090131467261974131467231858131467201556131467212538131467221553131467221943131467231748131466711452131467271787131412578013869801783104154307361718482280178373638585436251621338931320893185072980138084820801545115716861861152948618615002682261422349251058108327767521397977810837298017831041205291663370568794560959184951366045652513495448389562195134957537413448707339432536174583163'''
      lst = [(a,b)]
      for a, b in lst:
          print "---------------------------"
          s = SequenceMatcher(None, a, b)
          print "length of a is %d" % len(a)
          print "length of b is %d" % len(b)
          print s.find_longest_match(0, len(a), 0, len(b))
          print s.ratio()
          for block in s.get_matching_blocks():
              m = a[block[0]:block[0]+block[2]]
              print "a[%d] and b[%d] match for %d elements and it is \"%s\"" %
      (block[0], block[1], block[2], m)
    2. birkenfeld commented on Mar 29, 2009

      Member

      Tim, I think you've had some enlightening comments about difflib issues
      in the past.

    3. mrotondo commented on Mar 30, 2009

      mrotondomannequin
      Mannequin

      From the source, it seems that there is undocumented behavior to
      SequenceMatcher which is causing this error. If b is longer than 200
      characters, it will consider any element x in b that takes up more than
      1% of it's contents as "popular", and thus junk.

      So, in this case, difflib is treating each individual digit as an
      element of your sequences, and each one takes up more than 1% of the
      complete sequence b. Therefore, each one is "popular", and therefore
      ignored.

      A snippet which demonstrates this:

      from difflib import SequenceMatcher
      for i in range(1, 202)[::10]:
        a = "a" * i
        b = "b" + "a" * i
        s = SequenceMatcher(None, a, b)
        print s.find_longest_match(0, len(a), 0, len(b))

      Up til i=200, the strings match, but afterwards they do not because "a"
      is "popular".

      Strangely, if you get rid of the "b" at the beginning of b, they
      continue to match at lengths greater than 200. This may be a bug, I'll
      keep looking into it but someone who knows more should probably take a
      look too.

      The comments from difflib.py say some interesting things:
      # 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;"

      This seems to mean that you won't actually get an accurate diff in
      certain cases, which seems odd. At the very least, this behavior should
      probably be documented. Do people think it should be changed to get rid
      of the "popularity" heuristic?

    4. bitdancer commented on Mar 30, 2009

      Member

      On Mon, 30 Mar 2009 at 00:40, Mike Rotondo wrote:

      This seems to mean that you won't actually get an accurate diff in
      certain cases, which seems odd. At the very least, this behavior should
      probably be documented. Do people think it should be changed to get rid
      of the "popularity" heuristic?

      A better way, I think, would be to provide a way to turn
      it off (and then document it, of course).

    5. added
      docsDocumentation in the Doc dir
      stdlibStandard Library Python modules in the Lib/ directory
      type-featureA feature request or enhancement
      and removed on May 28, 2009
    6. pitrou commented on Oct 2, 2009

      Member

      The popularity heuristic could be tuned to depend on the number N of
      distinct elements in the sequence, and kick in if an element appears say
      more than 1/(N**0.5) of the time.

    7. vbr commented on Apr 19, 2010

      vbrmannequin
      Mannequin

      I just stumbled on some seemingly different unexpected behaviour of
      difflib.SequenceMatcher, but it turns out, it may have the same cause, i.e. the "popular" heuristics.
      I hopefully managed to replicate it on an illustrative sample text - in as included in the attached file. (I also mentioned this issue in hte python-list
      http://mail.python.org/pipermail/python-list/2010-April/1241951.html but as there were no replies I eventually found, this might be more appropriate place.)
      Both strings differ in a minimal way, each having one extra character
      in a "strategic" position, which probably meets some pathological case
      for difflib.
      Instead of just reporting the insertion and deletion of these single
      characters (which works well for most cases - with most other
      positions of the differing characters), the output of the
      SequenceMatcher decides to delete a large part of the string in
      between the differences and to insert the almost same text after that.
      The attached code simply prints the results of the comparison with the
      respective tags, and substrings. No junk function is used.
      I get the same results on Python 2.5.4, 2.6.5, 3.1.1 on windows XPp SP3.
      I didn't find any plausible mentions of such cases in the documentation, but after some searching I found several reports in the bug tracker mentioning the erroneous output of SequenceMatcher on longer repetitive sequences.

      besides this
      http://bugs.python.org/issue2986
      e.g.
      http://bugs.python.org/issue1711800
      http://bugs.python.org/issue4622
      http://bugs.python.org/issue1528074

      In my case, disabling the "popular" heuristics as mentioned by John Machin in
      http://bugs.python.org/issue1528074#msg29269

      seems to have solved the problem; with a modified version of difflib containing:

                      if 0:   # disable popular heuristics
                          if n >= 200 and len(indices) * 100 > n:
                              populardict[elt] = 1
                              del indices[:]

      the comparison catches the differences in the test strings as expected - i.e. one character addition and deletion only. It is likely, that some other use cases for difflib may rely on the "popular"-heuristics but it also seems useful to have some control over this behaviour, which might not be appropriate in all cases.
      (The issue seems to be the same in python 2.5, 2.6 and 3.1.)

      regards,
      vbr

    8. terryjreedy commented on Jun 25, 2010

      Member

      This appears to be one of at least three duplicate issues: bpo-1528074, bpo-2986, and bpo-4622. I am closing two, leaving 2986 open, and merging the nearly disjoint nosy lists. (If no longer interested, you can delete yourself from 2986.) bpo-1711800 appears to be slightly different (if not, it could be closed also.)

      Whether or not a new feature is ever added (earliest, now, 3.2), it appears that the docs need improvement to at least explain the current behavior. If someone who understands the issue could open a separate doc issue (for 2.6/7/3.1/2) with a suggested addition, that would be great.

    9. terryjreedy commented on Jun 28, 2010

      Member

      The discussion on bpo-152807 references two other closed tracker issues:
      bpo-1678339 Test case that currently fails
      bpo-1678345 Patch to change behavior - rejected because crippled behavior is supposedly intentional and removing the change would slow things down.

      The patch simply removes the internal heuristic. I think a better patch would be to make it optional, with a tunable popularity threshold.

      I say 'supposedly intentional' because the code comments only justify the popularity hack for code line comparison and give no indication of awareness that it disables SequenceMatcher for general purpose use, and in particular, for non-toy finite character set comparisons of the type (ascii) used in all the examples.

    10. elibendersky commented on Jul 2, 2010

      elibenderskymannequin
      Mannequin

      The new "junk heuristic" has been added to difflib.py in SVN revision 26661 in 2002 (which is, incidentally, the last revision to modify difflib.py). Its commit log says:

      ---------------------------------------------
      Mostly in SequenceMatcher.{__chain_b, find_longest_match}:
      This now does a dynamic analysis of which elements are so frequently
      repeated as to constitute noise. The primary benefit is an enormous
      speedup in find_longest_match, as the innermost loop can have factors
      of 100s less potential matches to worry about, in cases where the
      sequences have many duplicate elements. In effect, this zooms in on
      sequences of non-ubiquitous elements now.

      While I like what I've seen of the effects so far, I still consider
      this experimental. Please give it a try!
      ---------------------------------------------

    11. 29 remaining items

    12. terryjreedy commented on Nov 12, 2010

      Member

      r86437 - correct and replicate version-added message

    13. elibendersky commented on Nov 20, 2010

      elibenderskymannequin
      Mannequin

      Terry, when is the deadline for producing the patch for 3.2? Perhaps we should at least submit the 2.7 patch for now so that it goes in for sure?

    14. hodgestar commented on Nov 20, 2010

      hodgestarmannequin
      Mannequin

      I made the minor changes needed to get Eli Bendersky's patch to apply against 3.2. Diff attached.

    15. terryjreedy commented on Nov 20, 2010

      Member

      Deadline is probably next Fri. However I will apply this or slight revision thereof in a couple of days to make sure this much is in. I have to fixup some work stuff today.

    16. elibendersky commented on Nov 21, 2010

      elibenderskymannequin
      Mannequin

      Simon's patch fix for 3.2 looks good to me - applies cleanly to py3k and tests pass.

    17. terryjreedy commented on Nov 25, 2010

      Member

      Since I am not sure I will be able to do any more before the 3.2b1 feature freeze, I went ahead with the minimal patch after checking the differences from the 2.7 version and redoing the Misc/News entry.
      (I suspect putting a new entry immediately after the appropriate heading, instead of between other headings, is probably least likely to fatally conflict with intervening changes.) r86745 Thank you Eli and Simon.

      Leaving this open for possible further changes.

    18. added
      type-featureA feature request or enhancement
      and removed
      type-bugAn unexpected behavior, bug, or error
      on Nov 25, 2010
    19. hodgestar commented on Nov 25, 2010

      hodgestarmannequin
      Mannequin

      My vote is that this bug be closed and a new feature request be opened. Failing that, it would be good to have a concise description of what else we would like done (and the priority should be downgraded, I guess).

    20. elibendersky commented on Nov 25, 2010

      elibenderskymannequin
      Mannequin

      Terry, I agree with Simon re closing and opening a new feature request. This issue has too much baggage in it, and you we always link to it. A new feature request should be opened strictly for 3.2

      If you want I can close this issue and open a new one, but I'm waiting for your approval.

    21. terryjreedy commented on Nov 25, 2010

      Member

      Agreed. bpo-10534. This is really a 'follow-on' rather than 'superseder',
      but the forward reference should be easy for anyone to find.

    22. added
      type-bugAn unexpected behavior, bug, or error
      and removed
      type-featureA feature request or enhancement
      on Nov 25, 2010
    23. transferred this issue fromon Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    Labels

    docsDocumentation in the Doc dirstdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL