More Details

     This article has been written with an emphasis on augmenting the explanation found in the original paper.  There were a few things I would have personally simplified myself, but I stuck with keeping many things as close to the original paper as I could.  In addition, the next few paragraphs contain a few miscellaneous details that I had to figure out when I was reading the original paper.

     The V array in the paper contains the elements from 1 to MAX because it must be capable of expressing the worst-case edit script where everything is deleted in A, then everything from B is inserted.  The elements from -1 to -MAX are needed to represent the worst-case edit sequence in the opposite order:  Insert everything from B, then deleting everything in A.  As seen above, the size of this array can be reduced by restricting the range of k values, and also using the array as a circular buffer.

     One of the challenges I had in understanding the basic algorithm was trying to figure out what this part was doing:

if k == -D or k != D and V[k - 1] < V[k + 1]:
    x = V[k + 1]
else:
    x = V[k - 1] + 1

     Here is another version of the same thing re-written to be a bit more clear:

#  If k == -D, this means we're on the left-hand border
#  of the edit grid, so there is only one valid option
#  to extend any path and it comes from the k line above.
if k == -D
    x = V[k + 1]
#  If k == D, this means we're on the top border
#  of the edit grid, so there is only one valid option
#  to extend any path and it comes from the k line below.
elif k == D:
    x = V[k - 1] + 1
#  If the k line below offers a smaller x value, then just
#  take the one above.  After all, this is a greedy algorithm.
elif V[k - 1] < V[k + 1]:
    x = V[k + 1]
#  If both paths have made equal progress, take the one from
#  the k line below because this will allow us to increase 
#  the x variable (which we're trying to optimize) by adding
#  a deletion.  If we took the k line above, we would be adding
#  an insertion which would not increase the variable x.
elif V[k - 1] == V[k + 1]:
    x = V[k - 1] + 1
#  In this case, the k line below already has a higher x, but
#  we can also increase x by using it.  Have your cake and
#  eat it too.
elif V[k - 1] > V[k + 1]:
    x = V[k - 1] + 1

     Take note that when D == 0, then k == -D and k == D, so the ordering of the if statements is important.  Another question I wondered when reviewing this code, is why does the calculation only add '+ 1' when choosing V[k - 1] and not when choosing k[k + 1]?  The answer is related to the fact that the V array stores the 'best x values' and the equation x - y = k described in the paper.  If we take V[k - 1] we're incrementing x by adding a deletion to the path.  Therefore, we explicitly increment the x value.  When we take V[k + 1], we're incrementing the y variable (an insertion) implicitly by taking the x value from a k line that is a distance of 1 away.  Since x - y = k, the increment to the y variable happens implicitly by taking the x value from the neighbouring k line.

     If you plan on just skimming the paper note that 'N' is used ambiguously in two different contexts the paper.  In the introduction and title, 'N' is defined as being len(string a) + len(string b).  Later, N is re-defined to be len(string b) and used repeatedly after this point.

A Warning About Portability And Modulus

     The Myers algorithms and variants make heavy use modular arithmetic, and it's worth pointing out that the '%' operator does not do the same thing in every language.  For example this python code:

print(str(-1 % 5))

will output the following:

4

but this C code will output the following:

#include <stdio.h>

int main(void){
        printf("%d\n", -1 % 5);
}

will output the following:

-1

Please make sure you tell your children about the dangers of modular arithmetic and negative numbers.

Conclusion

     In conclusion, the Myers diff algorithm was reviewed in detail using several interactive visualizations.  There were two main variants of this algorithm:  The first was a simple algorithm to calculate the length of a minimal edit script, and the second was a recursive divide-and-conquer approach to recovering the full edit sequence using only linear space.  Also discussed were two refinements to the Myers diff algorithm that reduce the worst-case execution time from O((len(a) + len(b))*D) to O(min(len(a), len(b))*D) and the space requirements from O(len(a) + len(b)) to O(min(len(a),len(b))).

     In addition, a patch for GNU diffutils was reviewed that empirically provides better asymptotic run time when calculating a minimal difference between a relatively large file and a relatively small file, but has overall worse performance when run on more typical non--minimal flag inputs.  The patch could probably be improved to make this disadvantage go away, but I have no plans to work on this.

     Multiple variants of the Myers algorithms and the diffutils patch are available here on GitHub.

References

[1]  Here are a number of links in case some of them go dead: PDF Link 1, PDF Link 2, DOI: 10.1007/BF01840446

[2]  A potential improvement in Git: Link To Git Source

[3]  A potential improvement in Unix diff: Link To Diff Source

Why Bother Subscribing?
  • Free Software/Engineering Content. I publish all of my educational content publicly for free so everybody can make use of it.  Why bother signing up for a paid 'course', when you can just sign up for this email list?
  • Read about cool new products that I'm building. How do I make money? Glad you asked!  You'll get some emails with examples of things that I sell.  You might even get some business ideas of your own :)
  • People actually like this email list. I know that sounds crazy, because who actually subscribes to email lists these days, right?  Well, some do, and if you end up not liking it, I give you permission to unsubscribe and mark it as spam.