private static int CountEqual <T> ( IList <T> listA, IList <T> listB, int startA, int endA, int startB, int endB, DiffComparison update, DiffCache cache) where T : IDiffComparable { int length = 0; while (startA < endA && startB < endB) { var comp = cache.Compare(listA, startA, listB, startB); if (comp != DiffComparison.Same && comp != update) { break; } startA++; startB++; length++; } return(length); }
/// <summary> /// Calculates move and replace operation (besides add and remove) and adjusts indices /// so events can be applied sequentially (included moves) to the source list /// </summary> public static IList <DiffSection <T> > Calculate <T> (IList <T> listA, IList <T> listB) where T : IDiffComparable { var cache = new DiffCache(); var diffs = Calculate(listA, listB, 0, listA.Count, 0, listB.Count, DiffComparison.SoftUpdate, cache); var diffsWithReplace = diffs.Select(x => { if (x.Type == DiffType.Copy && cache.Compare(listA, x.OldIndex, listB, x.NewIndex) != DiffComparison.Same) { x.Type = DiffType.Replace; } return(x); }); var diffsDic = diffsWithReplace .Where(x => x.Type != DiffType.Copy) .GroupBy(diff => diff.Type) .ToDictionary(gr => gr.Key, gr => gr.ToList()); // Calculate Move diffs if (diffsDic.ContainsKey(DiffType.Add) && diffsDic.ContainsKey(DiffType.Remove)) { foreach (var addDiff in diffsDic[DiffType.Add]) { var rmDiff = diffsDic [DiffType.Remove].FirstOrDefault(x => cache.Compare(listA, x.OldIndex, listB, addDiff.NewIndex) == DiffComparison.Update); if (rmDiff != null) { addDiff.Link = rmDiff; rmDiff.Link = addDiff; addDiff.OldIndex = rmDiff.NewIndex; rmDiff.Move = addDiff.Move = rmDiff.NewIndex < addDiff.NewIndex ? DiffMove.Forward : DiffMove.Backward; } } } var fwOffset = 0; var bwOffsetDic = new Dictionary <DiffSection <T>, int> (); return(diffsDic .SelectMany(x => x.Value) .OrderBy(x => x.NewIndex) // Deletes must happen before inserts in the same position to prevent problems with indices .ThenBy(x => x.Type == DiffType.Remove ? -1 : 0) // Add offset to indices so events can be raised linearly without conflicting with moves .Select(x => { if (x.Type == DiffType.Add) { if (x.IsMove) { if (x.Move == DiffMove.Forward) { fwOffset--; } else { bwOffsetDic.Add(x.Link, 0); } x.Link = null; // Reference is not needed any more x.Type = DiffType.Move; } foreach (var k in bwOffsetDic.Keys.ToList()) { bwOffsetDic[k] -= 1; } } else if (x.Type == DiffType.Remove) { if (!x.IsMove) { foreach (var k in bwOffsetDic.Keys.ToList()) { bwOffsetDic[k] += 1; } } else if (x.Move == DiffMove.Forward) { fwOffset++; } else // Move == Diff.Backward { x.Link.OldIndex += bwOffsetDic[x]; bwOffsetDic.Remove(x); } } x.NewIndex += fwOffset; return x; }) .Where(x => !(x.Type == DiffType.Remove && x.IsMove)) .ToList()); }