This Class implements the Difference Algorithm published in "An O(ND) Difference Algorithm and its Variations" by Eugene Myers Algorithmica Vol. 1 No. 2, 1986, p 251. There are many C, Java, Lisp implementations public available but they all seem to come from the same source (diffutils) that is under the (unfree) GNU public License and cannot be reused as a sourcecode for a commercial application. There are very old C implementations that use other (worse) algorithms. Microsoft also published sourcecode of a diff-tool (windiff) that uses some tree data. Also, a direct transfer from a C source to C# is not easy because there is a lot of pointer arithmetic in the typical C solutions and i need a managed solution. These are the reasons why I implemented the original published algorithm from the scratch and make it avaliable without the GNU license limitations. I do not need a high performance diff tool because it is used only sometimes. I will do some performace tweaking when needed. The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents each line is converted into a (hash) number. See DiffText(). Some chages to the original algorithm: The original algorithm was described using a recursive approach and comparing zero indexed arrays. Extracting sub-arrays and rejoining them is very performance and memory intensive so the same (readonly) data arrays are passed arround together with their lower and upper bounds. This circumstance makes the LCS and SMS functions more complicate. I added some code to the LCS function to get a fast response on sub-arrays that are identical, completely deleted or inserted. The result from a comparisation is stored in 2 arrays that flag for modified (deleted or inserted) lines in the 2 data arrays. These bits are then analysed to produce a array of Item objects. Further possible optimizations: (first rule: don't do it; second: don't do it yet) The arrays DataA and DataB are passed as parameters, but are never changed after the creation so they can be members of the class to avoid the paramter overhead. In SMS is a lot of boundary arithmetic in the for-D and for-k loops that can be done by increment and decrement of local variables. The DownVector and UpVector arrays are alywas created and destroyed each time the SMS gets called. It is possible to reuse tehm when transfering them to members of the class. See TODO: hints. diff.cs: A port of the algorythm to C# Copyright (c) by Matthias Hertel, http://www.mathertel.de This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx Changes: 2002.09.20 There was a "hang" in some situations. Now I undestand a little bit more of the SMS algorithm. There have been overlapping boxes; that where analyzed partial differently. One return-point is enough. A assertion was added in CreateDiffs when in debug-mode, that counts the number of equal (no modified) lines in both arrays. They must be identical. 2003.02.07 Out of bounds error in the Up/Down vector arrays in some situations. The two vetors are now accessed using different offsets that are adjusted using the start k-Line. A test case is added. 2006.03.05 Some documentation and a direct Diff entry point. 2006.03.08 Refactored the API to static methods on the Diff class to make usage simpler. 2006.03.10 using the standard Debug class for self-test now. compile with: csc /target:exe /out:diffTest.exe /d:DEBUG /d:TRACE /d:SELFTEST Diff.cs 2007.01.06 license agreement changed to a BSD style license. 2007.06.03 added the Optimize method. 2007.09.23 UpVector and DownVector optimization by Jan Stoklasa (). 2008.05.31 Adjusted the testing code that failed because of the Optimize method (not a bug in the diff algorithm). 2008.10.08 Fixing a test case and adding a new test case.
Example #1
0
        public static Dictionary <ExecutionCall, ExecutionCall> GeneratePathDiff(List <ExecutionCall> left, List <ExecutionCall> right)
        {
            Dictionary <ExecutionCall, ExecutionCall> sameMap = new Dictionary <ExecutionCall, ExecutionCall>();

            var flatLeft  = FlattenPath(left);
            var flatRight = FlattenPath(right);

            StringBuilder sbLeft = new StringBuilder();

            foreach (var call in flatLeft)
            {
                SerializeCall(sbLeft, call);
            }

            StringBuilder sbRight = new StringBuilder();

            foreach (var call in flatRight)
            {
                SerializeCall(sbRight, call);
            }

            sbLeft.Length  -= Environment.NewLine.Length;
            sbRight.Length -= Environment.NewLine.Length;

            var l     = sbLeft.ToString();
            var r     = sbRight.ToString();
            var diff  = new Diff.Diff();
            var items = diff.DiffText(l, r);
            int pos   = 0;

            List <int> diffItems = new List <int>();

            for (var n = 0; n < items.Length; n++)
            {
                Diff.Diff.Item i = items[n];
                while ((pos < i.StartB) && (pos < flatRight.Count))
                {
                    diffItems.Add(0);
                    pos++;
                }
                if (i.deletedA > 0)
                {
                    for (var m = 0; m < i.deletedA; m++)
                    {
                        diffItems.Add(-1);
                    }
                }
                if (pos < i.StartB + i.insertedB)
                {
                    while (pos < i.StartB + i.insertedB)
                    {
                        diffItems.Add(1);
                        pos++;
                    }
                }
            }

            while (pos < flatRight.Count)
            {
                diffItems.Add(0);
                pos++;
            }

            var leftPointer  = 0;
            var rightPointer = 0;
            var lineIndex    = 0;

            for (lineIndex = 0; lineIndex < diffItems.Count; lineIndex++)
            {
                var curDiff = diffItems[lineIndex];
                switch (curDiff)
                {
                case 0:
                    sameMap.Add(flatLeft[leftPointer++], flatRight[rightPointer++]);
                    break;

                case 1:
                    var call = flatRight[rightPointer++];
                    call.DiffStatus = DiffStatus.INSERT;
                    var parent = call.Parent;
                    while (parent != null)
                    {
                        if (parent.DiffStatus == DiffStatus.SAME)
                        {
                            parent.DiffStatus = DiffStatus.MODIFIED;
                        }

                        parent = parent.Parent;
                    }
                    break;

                case -1:
                    call            = flatLeft[leftPointer++];
                    call.DiffStatus = DiffStatus.DELETE;
                    parent          = call.Parent;
                    while (parent != null)
                    {
                        if (parent.DiffStatus == DiffStatus.SAME)
                        {
                            parent.DiffStatus = DiffStatus.MODIFIED;
                        }

                        parent = parent.Parent;
                    }
                    break;
                }
            }
            return(sameMap);
        }
Example #2
0
        public static Dictionary<ExecutionCall,ExecutionCall> GeneratePathDiff(List<ExecutionCall> left, List<ExecutionCall> right)
        {
            Dictionary<ExecutionCall, ExecutionCall> sameMap = new Dictionary<ExecutionCall, ExecutionCall>();

            var flatLeft = FlattenPath(left);
            var flatRight = FlattenPath(right);

            StringBuilder sbLeft = new StringBuilder();
            foreach (var call in flatLeft)
            {
                SerializeCall(sbLeft, call);
            }

            StringBuilder sbRight = new StringBuilder();
            foreach (var call in flatRight)
            {
                SerializeCall(sbRight, call);
            }

            sbLeft.Length -= Environment.NewLine.Length;
            sbRight.Length -= Environment.NewLine.Length;

            var l = sbLeft.ToString();
            var r = sbRight.ToString();
            var diff = new Diff.Diff();
            var items = diff.DiffText(l, r);
            int pos = 0;

            List<int> diffItems = new List<int>();

            for (var n = 0; n < items.Length; n++)
            {
                Diff.Diff.Item i = items[n];
                while ((pos < i.StartB) && (pos < flatRight.Count))
                {
                    diffItems.Add(0);
                    pos++;
                }
                if (i.deletedA > 0)
                {
                    for (var m = 0; m < i.deletedA; m++)
                    {
                        diffItems.Add(-1);
                    }
                }
                if (pos < i.StartB + i.insertedB)
                {
                    while (pos < i.StartB + i.insertedB)
                    {
                        diffItems.Add(1);
                        pos++;
                    }
                }
            }

            while (pos < flatRight.Count)
            {
                diffItems.Add(0);
            }

            var leftPointer = 0;
            var rightPointer = 0;
            var lineIndex = 0;

            for (lineIndex = 0; lineIndex < diffItems.Count; lineIndex++)
            {
                var curDiff = diffItems[lineIndex];
                switch (curDiff)
                {
                    case 0:
                        sameMap.Add(flatLeft[leftPointer++], flatRight[rightPointer++]);
                        break;
                    case 1:
                        var call = flatRight[rightPointer++];
                        call.DiffStatus = DiffStatus.INSERT;
                        var parent = call.Parent;
                        while (parent != null)
                        {
                            if (parent.DiffStatus == DiffStatus.SAME)
                            {
                                parent.DiffStatus = DiffStatus.MODIFIED;
                            }
                            
                            parent = parent.Parent;
                        }
                        break;
                    case -1:
                        call = flatLeft[leftPointer++];
                        call.DiffStatus = DiffStatus.DELETE;
                        parent = call.Parent;
                        while (parent != null)
                        {
                            if (parent.DiffStatus == DiffStatus.SAME)
                            {
                                parent.DiffStatus = DiffStatus.MODIFIED;
                            }

                            parent = parent.Parent;
                        }
                        break;
                }
            }
            return sameMap;
        }