diff --git a/ListDiff/Diff.cs b/ListDiff/Diff.cs new file mode 100644 index 0000000..97fa5a8 --- /dev/null +++ b/ListDiff/Diff.cs @@ -0,0 +1,178 @@ +// +// Copyright (c) Krueger Systems, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ListDiff +{ + /// + /// TODO + /// + static partial class DiffModule + { + /// + /// TODO + /// + public static List<(A, T, T)> Diff (IEnumerable source, IEnumerable destination, + A update, A add, A remove) => + Diff (source, destination, update, add, remove, out _); + + /// + /// TODO + /// + public static List<(A, T, T)> Diff (IEnumerable source, IEnumerable destination, + A update, A add, A remove, + out bool containsOnlyUpdates) => + Diff (source, destination, (s, d) => EqualityComparer.Default.Equals (s, d), + update, add, remove, out containsOnlyUpdates); + + /// + /// TODO + /// + public static List<(A, S, D)> Diff (IEnumerable source, IEnumerable destination, + Func match, + A update, A add, A remove) => + Diff (source, destination, match, update, add, remove, out _); + + /// + /// TODO + /// + public static List<(A, S, D)> Diff (IEnumerable source, IEnumerable destination, + Func match, + A update, A add, A remove, + out bool containsOnlyUpdates) => + Diff (source, destination, match, (s, d) => (update, s, d), + d => (add, default, d), + s => (remove, s, default), + out containsOnlyUpdates); + + /// + /// TODO + /// + public static List Diff (IEnumerable source, IEnumerable destination, + Func updateResult, Func addResult, Func removeResult) => + Diff (source, destination, updateResult, addResult, removeResult, out _); + + /// + /// TODO + /// + public static List Diff (IEnumerable source, IEnumerable destination, + Func updateResult, Func addResult, Func removeResult, + out bool containsOnlyUpdates) => + Diff (source, destination, (s, d) => EqualityComparer.Default.Equals (s, d), + updateResult, addResult, removeResult, out containsOnlyUpdates); + + /// + /// TODO + /// + public static List Diff (IEnumerable source, IEnumerable destination, + Func match, + Func updateResult, Func addResult, Func removeResult) => + Diff (source, destination, match, updateResult, addResult, removeResult, out _); + + /// + /// TODO + /// + public static List Diff (IEnumerable source, IEnumerable destination, + Func match, + Func updateResult, Func addResult, Func removeResult, + out bool containsOnlyUpdates) + { + if (source == null) throw new ArgumentNullException (nameof (source)); + if (destination == null) throw new ArgumentNullException (nameof (destination)); + if (match == null) throw new ArgumentNullException (nameof (match)); + + IList x = source as IList ?? source.ToArray (); + IList y = destination as IList ?? destination.ToArray (); + + var actions = new List (); + + var m = x.Count; + var n = y.Count; + + var start = 0; + + while (start < m && start < n && match (x[start], y[start])) { + start++; + } + + while (start < m && start < n && match (x[m - 1], y[n - 1])) { + m--; + n--; + } + + // + // Construct the C matrix + // + var c = new int[m - start + 1, n - start + 1]; + for (var i = 1; i <= m - start; i++) { + for (var j = 1; j <= n - start; j++) { + if (match (x[i - 1], y[j - 1])) { + c[i, j] = c[i - 1, j - 1] + 1; + } + else { + c[i, j] = Math.Max (c[i, j - 1], c[i - 1, j]); + } + } + } + + // + // Generate the actions + // + for (int i = 0; i < start; i++) { + actions.Add (updateResult (x[i], y[i])); + } + + var varContainsOnlyUpdates = true; + GenDiff (m, n); + + for (int i = 0; i < x.Count - m; i++) { + actions.Add (updateResult (x[m + i], y[n + i])); + } + + containsOnlyUpdates = varContainsOnlyUpdates; + return actions; + + void GenDiff (int i, int j) + { + if (i > start && j > start && match (x[i - 1], y[j - 1])) { + GenDiff (i - 1, j - 1); + actions.Add (updateResult (x[i - 1], y[j - 1])); + } + else { + if (j > start && (i == start || c[i - start, j - start - 1] >= c[i - start - 1, j - start])) { + GenDiff (i, j - 1); + varContainsOnlyUpdates = false; + actions.Add (addResult (y[j - 1])); + } + else if (i > start && (j == start || c[i - start, j - start - 1] < c[i - start - 1, j - start])) { + GenDiff (i - 1, j); + varContainsOnlyUpdates = false; + actions.Add (removeResult (x[i - 1])); + } + } + } + } + } +} diff --git a/ListDiff/ListDiff.cs b/ListDiff/ListDiff.cs index 80aa03b..0a05a5c 100644 --- a/ListDiff/ListDiff.cs +++ b/ListDiff/ListDiff.cs @@ -23,7 +23,7 @@ using System; using System.Collections.Generic; using System.Text; -using System.Linq; +using static ListDiff.DiffModule; namespace ListDiff { @@ -136,57 +136,12 @@ public ListDiff (IEnumerable source, IEnumerable destination) /// Predicate used to match source and destination items public ListDiff (IEnumerable source, IEnumerable destination, Func match) { - if (source == null) throw new ArgumentNullException (nameof (source)); - if (destination == null) throw new ArgumentNullException (nameof (destination)); - if (match == null) throw new ArgumentNullException (nameof (match)); - - IList x = source as IList ?? source.ToArray (); - IList y = destination as IList ?? destination.ToArray (); - - Actions = new List> (); - - var m = x.Count; - var n = y.Count; - - var start = 0; - - while (start < m && start < n && match (x[start], y[start])) { - start++; - } - - while (start < m && start < n && match (x[m - 1], y[n - 1])) { - m--; - n--; - } - - // - // Construct the C matrix - // - var c = new int[m - start + 1, n - start + 1]; - for (var i = 1; i <= m - start; i++) { - for (var j = 1; j <= n - start; j++) { - if (match (x[i - 1], y[j - 1])) { - c[i, j] = c[i - 1, j - 1] + 1; - } - else { - c[i, j] = Math.Max (c[i, j - 1], c[i - 1, j]); - } - } - } - - // - // Generate the actions - // - for (int i = 0; i < start; i++) { - Actions.Add (new ListDiffAction (ListDiffActionType.Update, x[i], y[i])); - } - - ContainsOnlyUpdates = true; - GenDiff (c, x, y, start, m, n, match); - - for (int i = 0; i < x.Count - m; i++) { - Actions.Add (new ListDiffAction (ListDiffActionType.Update, x[m + i], y[n + i])); - } + Actions = Diff (source, destination, match, + (s, d) => new ListDiffAction (ListDiffActionType.Update, s, d), + d => new ListDiffAction (ListDiffActionType.Add, default, d), + s => new ListDiffAction (ListDiffActionType.Remove, s, default), + out var containsOnlyUpdates); + ContainsOnlyUpdates = containsOnlyUpdates; } void GenDiff (int[,] c, IList x, IList y, int start, int i, int j, Func match) @@ -341,4 +296,6 @@ public static ListDiff Diff (this return new ListDiff (source, destination, match); } } + + public partial class DiffModule {} } diff --git a/ListDiff/ListDiff.csproj b/ListDiff/ListDiff.csproj index cc3603a..c78481c 100644 --- a/ListDiff/ListDiff.csproj +++ b/ListDiff/ListDiff.csproj @@ -11,4 +11,8 @@ bin\Release\netstandard1.0\ListDiff.xml + + + + diff --git a/Tests/ListDiffTests.cs b/Tests/ListDiffTests.cs index 50554aa..c68cafd 100644 --- a/Tests/ListDiffTests.cs +++ b/Tests/ListDiffTests.cs @@ -1,4 +1,5 @@ using ListDiff; +using static ListDiff.DiffModule; using Xunit; namespace ListDiffTests @@ -22,8 +23,13 @@ public class Tests [InlineData ("abc", "", "-(a)-(b)-(c)")] public void SimpleCases (string left, string right, string expectedDiff) { - var diff = left.Diff (right); - Assert.Equal (expectedDiff, diff.ToString ()); + var diff1 = left.Diff (right); + + Assert.Equal (expectedDiff, diff1.ToString ()); + + var diff2 = Diff (left, right, (s, _) => s.ToString (), d => $"+({d})", s => $"-({s})"); + + Assert.Equal (expectedDiff, string.Join (string.Empty, diff2)); } [Theory] @@ -43,9 +49,13 @@ public void DiffMiddleModificationOfLongList (int listSize) var modified = sb.ToString ().Remove (middleIndex, 1); var expectedDiff = modified.Insert (middleIndex, string.Format("-({0})", middleItem)); - var diff = original.Diff (modified); + var diff1 = original.Diff (modified); + + Assert.Equal (expectedDiff, diff1.ToString ()); + + var diff2 = Diff (original, modified, (s, _) => s.ToString (), d => $"+({d})", s => $"-({s})"); - Assert.Equal (expectedDiff, diff.ToString ()); + Assert.Equal (expectedDiff, string.Join (string.Empty, diff2)); } [Fact]