/// <summary> /// Mutate the specified list to have the same elements as another list, by inserting or removing as needed. The end result is that /// <paramref name="target"/> will have equivalent elements as <paramref name="source"/>, in the same order and positions. /// </summary> /// <typeparam name="T"> /// The type of elements in the lists. /// </typeparam> /// <param name="target"> /// The list to mutate. Elements will possibly be inserted into or deleted from this list. /// </param> /// <param name="source"> /// The list to use as the source of mutations for <paramref name="target"/>. /// </param> /// <param name="options"> /// A <see cref="DiffOptions"/> object specifying options to the diff algorithm, or <c>null</c> if defaults should be used. /// </param> /// <param name="comparer"> /// The optional <see cref="IEqualityComparer{T}"/> to use when comparing elements. /// If not specified/<c>null</c>, <see cref="EqualityComparer{T}.Default"/> will be used. /// </param> /// <param name="aligner"> /// The <see cref="IDiffElementAligner{T}"/> to use when aligning elements. /// If not specified/<c>null</c>, <see cref="BasicReplaceInsertDeleteDiffElementAligner{T}"/> will be used. /// </param> /// <exception cref="ArgumentNullException"> /// <para><paramref name="target"/> is <c>null</c>.</para> /// <para>- or -</para> /// <para><paramref name="source"/> is <c>null</c>.</para> /// </exception> /// <remarks> /// The main purpose of this method is to avoid clearing and refilling the list from scratch and instead /// make adjustments to it to have the right elements. Useful in conjunction with UI bindings and /// similar that react to changes to the list. /// </remarks> public static void MutateToBeLike <T>([NotNull] this IList <T> target, [NotNull] IList <T> source, [CanBeNull] DiffOptions options, [CanBeNull] IEqualityComparer <T> comparer = null, [CanBeNull] IDiffElementAligner <T> aligner = null) { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (source == null) { throw new ArgumentNullException(nameof(source)); } options = options ?? new DiffOptions(); comparer = comparer ?? EqualityComparer <T> .Default; aligner = aligner ?? new BasicReplaceInsertDeleteDiffElementAligner <T>(); var sections = Diff.CalculateSections(target, source, options, comparer); var items = Diff.AlignElements(target, source, sections, aligner).ToList(); Assume.That(items != null); int targetIndex = 0; foreach (var item in items) { switch (item.Operation) { case DiffOperation.Match: targetIndex++; break; case DiffOperation.Insert: target.Insert(targetIndex, item.ElementFromCollection2.Value); targetIndex++; break; case DiffOperation.Delete: target.RemoveAt(targetIndex); break; case DiffOperation.Replace: case DiffOperation.Modify: target[targetIndex] = item.ElementFromCollection2.Value; targetIndex++; break; default: throw new ArgumentOutOfRangeException(); } } }
public Merge([NotNull] IList <T> commonBase, [NotNull] IList <T> left, [NotNull] IList <T> right, [NotNull] IDiffElementAligner <T> aligner, [NotNull] IMergeConflictResolver <T> conflictResolver, [NotNull] IEqualityComparer <T> comparer, [NotNull] DiffOptions diffOptions) { if (commonBase == null) { throw new ArgumentNullException(nameof(commonBase)); } if (left == null) { throw new ArgumentNullException(nameof(left)); } if (right == null) { throw new ArgumentNullException(nameof(right)); } if (aligner == null) { throw new ArgumentNullException(nameof(aligner)); } if (comparer == null) { throw new ArgumentNullException(nameof(comparer)); } if (diffOptions == null) { throw new ArgumentNullException(nameof(diffOptions)); } _ConflictResolver = conflictResolver ?? throw new ArgumentNullException(nameof(conflictResolver)); var diffCommonBaseToLeft = Diff.AlignElements(commonBase, left, Diff.CalculateSections(commonBase, left, diffOptions, comparer), aligner).ToList(); Assume.That(diffCommonBaseToLeft != null); _DiffCommonBaseToLeft = diffCommonBaseToLeft; var diffCommonBaseToRight = Diff.AlignElements(commonBase, right, Diff.CalculateSections(commonBase, right, diffOptions, comparer), aligner).ToList(); Assume.That(diffCommonBaseToRight != null); _DiffCommonBaseToRight = diffCommonBaseToRight; var mergeSections = Diff.CalculateSections(diffCommonBaseToLeft, diffCommonBaseToRight, diffOptions, new DiffSectionMergeComparer <T>(comparer)).ToList(); Assume.That(mergeSections != null); _MergeSections = mergeSections; }
public static IEnumerable <DiffSection> CalculateSections <T>([NotNull] IList <T> collection1, [NotNull] IList <T> collection2, [CanBeNull] DiffOptions options, [CanBeNull] IEqualityComparer <T> comparer = null) { if (collection1 == null) { throw new ArgumentNullException(nameof(collection1)); } if (collection2 == null) { throw new ArgumentNullException(nameof(collection2)); } comparer = comparer ?? EqualityComparer <T> .Default; Assume.That(comparer != null); options = options ?? new DiffOptions(); return(LongestCommonSubsectionDiff.Calculate(collection1, collection2, options, comparer)); }
public static IEnumerable <T> Perform <T>([NotNull] IList <T> commonBase, [NotNull] IList <T> left, [NotNull] IList <T> right, [CanBeNull] DiffOptions diffOptions, [NotNull] IDiffElementAligner <T> aligner, [NotNull] IMergeConflictResolver <T> conflictResolver, [CanBeNull] IEqualityComparer <T> comparer = null) { if (commonBase == null) { throw new ArgumentNullException(nameof(commonBase)); } if (left == null) { throw new ArgumentNullException(nameof(left)); } if (right == null) { throw new ArgumentNullException(nameof(right)); } if (aligner == null) { throw new ArgumentNullException(nameof(aligner)); } if (conflictResolver == null) { throw new ArgumentNullException(nameof(conflictResolver)); } diffOptions = diffOptions ?? new DiffOptions(); comparer = comparer ?? EqualityComparer <T> .Default; Assume.That(comparer != null); return(new Merge <T>(commonBase, left, right, aligner, conflictResolver, comparer, diffOptions)); }
public static IEnumerable <DiffSection> Calculate <T>([NotNull] IList <T> collection1, [NotNull] IList <T> collection2, [NotNull] DiffOptions options, [NotNull] IEqualityComparer <T> comparer) { return(Calculate(collection1, 0, collection1.Count, collection2, 0, collection2.Count, comparer, new LongestCommonSubsequence <T>(collection1, collection2, comparer), options)); }
private static IEnumerable <DiffSection> Calculate <T>([NotNull] IList <T> collection1, int lower1, int upper1, [NotNull] IList <T> collection2, int lower2, int upper2, [NotNull] IEqualityComparer <T> comparer, [NotNull] LongestCommonSubsequence <T> lcs, [NotNull] DiffOptions options) { // Short-circuit recursive call when nothing left (usually because match was found at the very start or end of a subsection if (lower1 == upper1 && lower2 == upper2) { yield break; } // Patience modification, let's find matching elements at both ends and remove those from LCS consideration int matchEnd = 0; if (options.EnablePatienceOptimization) { int matchStart = MatchStart(collection1, lower1, upper1, collection2, lower2, upper2, comparer); if (matchStart > 0) { yield return(new DiffSection(isMatch: true, lengthInCollection1: matchStart, lengthInCollection2: matchStart)); lower1 += matchStart; lower2 += matchStart; } matchEnd = MatchEnd(collection1, lower1, upper1, collection2, lower2, upper2, comparer); if (matchEnd > 0) { upper1 -= matchEnd; upper2 -= matchEnd; } } if (lower1 < upper1 || lower2 < upper2) { if (lower1 == upper1 || lower2 == upper2) { // Degenerate case, only one of the collections still have elements yield return(new DiffSection(isMatch: false, lengthInCollection1: upper1 - lower1, lengthInCollection2: upper2 - lower2)); } else { int position1; int position2; int length; if (lcs.Find(lower1, upper1, lower2, upper2, out position1, out position2, out length)) { // Recursively apply calculation to portion before common subsequence foreach (var section in Calculate(collection1, lower1, position1, collection2, lower2, position2, comparer, lcs, options)) { yield return(section); } // Output match yield return(new DiffSection(isMatch: true, lengthInCollection1: length, lengthInCollection2: length)); // Recursively apply calculation to portion after common subsequence foreach (var section in Calculate(collection1, position1 + length, upper1, collection2, position2 + length, upper2, comparer, lcs, options)) { yield return(section); } } else { // Unable to find a match, so just return section as unmatched yield return(new DiffSection(isMatch: false, lengthInCollection1: upper1 - lower1, lengthInCollection2: upper2 - lower2)); } } } if (matchEnd > 0) { yield return(new DiffSection(isMatch: true, lengthInCollection1: matchEnd, lengthInCollection2: matchEnd)); } }