# frozen_string_literal: trueclass<<Diff::LCSdefdiff_traversal(method,seq1,seq2,callbacks,&block)callbacks=callbacks_for(callbacks)casemethodwhen:difftraverse_sequences(seq1,seq2,callbacks)when:sdifftraverse_balanced(seq1,seq2,callbacks)endcallbacks.finishifcallbacks.respond_to?:finishifblockcallbacks.diffs.mapdo|hunk|ifhunk.kind_of?Arrayhunk.map{|hunk_block|block[hunk_block]}elseblock[hunk]endendelsecallbacks.diffsendendprivate:diff_traversalendmoduleDiff::LCS::Internals# :nodoc:endclass<<Diff::LCS::Internals# Compute the longest common subsequence between the sequenced# Enumerables +a+ and +b+. The result is an array whose contents is such# that## result = Diff::LCS::Internals.lcs(a, b)# result.each_with_index do |e, i|# assert_equal(a[i], b[e]) unless e.nil?# enddeflcs(a,b)a_start=b_start=0a_finish=a.size-1b_finish=b.size-1vector=[]# Collect any common elements at the beginning...while(a_start<=a_finish)and(b_start<=b_finish)and(a[a_start]==b[b_start])vector[a_start]=b_starta_start+=1b_start+=1end# Now the end...while(a_start<=a_finish)and(b_start<=b_finish)and(a[a_finish]==b[b_finish])vector[a_finish]=b_finisha_finish-=1b_finish-=1end# Now, compute the equivalence classes of positions of elements.# An explanation for how this works: https://codeforces.com/topic/92191b_matches=position_hash(b,b_start..b_finish)thresh=[]links=[]string=a.kind_of?(String)(a_start..a_finish).eachdo|i|ai=string?a[i,1]:a[i]bm=b_matches[ai]k=nilbm.reverse_eachdo|j|# Although the threshold check is not mandatory for this to work,# it may have an optimization purpose# An attempt to remove it: https://github.com/halostatue/diff-lcs/pull/72# Why it is reintroduced: https://github.com/halostatue/diff-lcs/issues/78ifkand(thresh[k]>j)and(thresh[k-1]<j)thresh[k]=jelsek=replace_next_larger(thresh,j,k)endlinks[k]=[k.positive??links[k-1]:nil,i,j]unlessk.nil?endendunlessthresh.empty?link=links[thresh.size-1]untillink.nil?vector[link[1]]=link[2]link=link[0]endendvectorend# This method will analyze the provided patchset to provide a single-pass# normalization (conversion of the array form of Diff::LCS::Change objects to# the object form of same) and detection of whether the patchset represents# changes to be made.defanalyze_patchset(patchset,depth=0)fail'Patchset too complex'ifdepth>1has_changes=falsenew_patchset=[]# Format:# [ # patchset# # hunk (change)# [ # hunk# # change# ]# ]patchset.eachdo|hunk|casehunkwhenDiff::LCS::Changehas_changes||=!hunk.unchanged?new_patchset<<hunkwhenArray# Detect if the 'hunk' is actually an array-format change object.ifDiff::LCS::Change.valid_action?hunk[0]hunk=Diff::LCS::Change.from_a(hunk)has_changes||=!hunk.unchanged?new_patchset<<hunkelsewith_changes,hunk=analyze_patchset(hunk,depth+1)has_changes||=with_changesnew_patchset.concat(hunk)endelsefailArgumentError,"Cannot normalise a hunk of class #{hunk.class}."endend[has_changes,new_patchset]end# Examine the patchset and the source to see in which direction the# patch should be applied.## WARNING: By default, this examines the whole patch, so this could take# some time. This also works better with Diff::LCS::ContextChange or# Diff::LCS::Change as its source, as an array will cause the creation# of one of the above.defintuit_diff_direction(src,patchset,limit=nil)string=src.kind_of?(String)count=left_match=left_miss=right_match=right_miss=0patchset.eachdo|change|count+=1casechangewhenDiff::LCS::ContextChangele=string?src[change.old_position,1]:src[change.old_position]re=string?src[change.new_position,1]:src[change.new_position]casechange.actionwhen'-'# Remove details from the old stringifle==change.old_elementleft_match+=1elseleft_miss+=1endwhen'+'ifre==change.new_elementright_match+=1elseright_miss+=1endwhen'='left_miss+=1ifle!=change.old_elementright_miss+=1ifre!=change.new_elementwhen'!'ifle==change.old_elementleft_match+=1elsifre==change.new_elementright_match+=1elseleft_miss+=1right_miss+=1endendwhenDiff::LCS::Change# With a simplistic change, we can't tell the difference between# the left and right on '!' actions, so we ignore those. On '='# actions, if there's a miss, we miss both left and right.element=string?src[change.position,1]:src[change.position]casechange.actionwhen'-'ifelement==change.elementleft_match+=1elseleft_miss+=1endwhen'+'ifelement==change.elementright_match+=1elseright_miss+=1endwhen'='ifelement!=change.elementleft_miss+=1right_miss+=1endendendbreakif!limit.nil?&&(count>limit)endno_left=left_match.zero?&&left_miss.positive?no_right=right_match.zero?&&right_miss.positive?case[no_left,no_right]when[false,true]:patchwhen[true,false]:unpatchelsecaseleft_match<=>right_matchwhen1ifleft_miss.zero?:patchelse:unpatchendwhen-1ifright_miss.zero?:unpatchelse:patchendelsefail"The provided patchset does not appear to apply to the provided \
enumerable as either source or destination value."endendend# Find the place at which +value+ would normally be inserted into the# Enumerable. If that place is already occupied by +value+, do nothing# and return +nil+. If the place does not exist (i.e., it is off the end# of the Enumerable), add it to the end. Otherwise, replace the element# at that point with +value+. It is assumed that the Enumerable's values# are numeric.## This operation preserves the sort order.defreplace_next_larger(enum,value,last_index=nil)# Off the end?ifenum.empty?or(value>enum[-1])enum<<valuereturnenum.size-1end# Binary search for the insertion pointlast_index||=enum.size-1first_index=0whilefirst_index<=last_indexi=(first_index+last_index)>>1found=enum[i]returnnilifvalue==foundifvalue>foundfirst_index=i+1elselast_index=i-1endend# The insertion point is in first_index; overwrite the next larger# value.enum[first_index]=valuefirst_indexendprivate:replace_next_larger# If +vector+ maps the matching elements of another collection onto this# Enumerable, compute the inverse of +vector+ that maps this Enumerable# onto the collection. (Currently unused.)definverse_vector(a,vector)inverse=a.dup(0...vector.size).eachdo|i|inverse[vector[i]]=iunlessvector[i].nil?endinverseendprivate:inverse_vector# Returns a hash mapping each element of an Enumerable to the set of# positions it occupies in the Enumerable, optionally restricted to the# elements specified in the range of indexes specified by +interval+.defposition_hash(enum,interval)string=enum.kind_of?(String)hash=Hash.new{|h,k|h[k]=[]}interval.eachdo|i|k=string?enum[i,1]:enum[i]hash[k]<<iendhashendprivate:position_hashend