From 10abf5329493de9cb0496f027bf292e6a92e0ef7 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Mon, 4 May 2020 18:21:28 +0200 Subject: [PATCH 1/3] Add stand-alone & generic "Diff" functions --- ListDiff/Diff.cs | 151 +++++++++++++++++++++++++++++++++++++++ ListDiff/ListDiff.cs | 60 +++------------- ListDiff/ListDiff.csproj | 4 ++ Tests/ListDiffTests.cs | 17 +++-- 4 files changed, 176 insertions(+), 56 deletions(-) create mode 100644 ListDiff/Diff.cs diff --git a/ListDiff/Diff.cs b/ListDiff/Diff.cs new file mode 100644 index 0000000..fca54d1 --- /dev/null +++ b/ListDiff/Diff.cs @@ -0,0 +1,151 @@ +// +// 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 +{ + static partial class ListDiff + { + public static List<(A, T, T)> Diff (IEnumerable source, IEnumerable destination, + A update, A add, A remove) => + Diff (source, destination, update, add, remove, out _); + + 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); + + 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 _); + + 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); + + public static List Diff (IEnumerable source, IEnumerable destination, + Func updateResult, Func addResult, Func removeResult) => + Diff (source, destination, updateResult, addResult, removeResult, out _); + + 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); + + public static List Diff (IEnumerable source, IEnumerable destination, + Func match, + Func updateResult, Func addResult, Func removeResult) => + Diff (source, destination, match, updateResult, addResult, removeResult, out _); + + 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..ad1fa49 100644 --- a/ListDiff/ListDiff.cs +++ b/ListDiff/ListDiff.cs @@ -23,7 +23,6 @@ using System; using System.Collections.Generic; using System.Text; -using System.Linq; namespace ListDiff { @@ -136,57 +135,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 = ListDiff.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 +295,6 @@ public static ListDiff Diff (this return new ListDiff (source, destination, match); } } + + public partial class ListDiff {} } 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..09621c7 100644 --- a/Tests/ListDiffTests.cs +++ b/Tests/ListDiffTests.cs @@ -22,8 +22,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 = ListDiff.ListDiff.Diff (left, right, (s, _) => s.ToString (), d => $"+({d})", s => $"-({s})"); + + Assert.Equal (expectedDiff, string.Join (string.Empty, diff2)); } [Theory] @@ -43,9 +48,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 = ListDiff.ListDiff.Diff (original, modified, (s, _) => s.ToString (), d => $"+({d})", s => $"-({s})"); - Assert.Equal (expectedDiff, diff.ToString ()); + Assert.Equal (expectedDiff, string.Join (string.Empty, diff2)); } [Fact] From bda7f645b77810ce2490d2824903ecc1de432a7e Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Mon, 4 May 2020 18:24:34 +0200 Subject: [PATCH 2/3] Refactor as module --- ListDiff/Diff.cs | 2 +- ListDiff/ListDiff.cs | 13 +++++++------ Tests/ListDiffTests.cs | 5 +++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/ListDiff/Diff.cs b/ListDiff/Diff.cs index fca54d1..caf46db 100644 --- a/ListDiff/Diff.cs +++ b/ListDiff/Diff.cs @@ -26,7 +26,7 @@ namespace ListDiff { - static partial class ListDiff + static partial class DiffModule { public static List<(A, T, T)> Diff (IEnumerable source, IEnumerable destination, A update, A add, A remove) => diff --git a/ListDiff/ListDiff.cs b/ListDiff/ListDiff.cs index ad1fa49..0a05a5c 100644 --- a/ListDiff/ListDiff.cs +++ b/ListDiff/ListDiff.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using System.Text; +using static ListDiff.DiffModule; namespace ListDiff { @@ -135,11 +136,11 @@ public ListDiff (IEnumerable source, IEnumerable destination) /// Predicate used to match source and destination items public ListDiff (IEnumerable source, IEnumerable destination, Func match) { - Actions = ListDiff.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); + 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; } @@ -296,5 +297,5 @@ public static ListDiff Diff (this } } - public partial class ListDiff {} + public partial class DiffModule {} } diff --git a/Tests/ListDiffTests.cs b/Tests/ListDiffTests.cs index 09621c7..c68cafd 100644 --- a/Tests/ListDiffTests.cs +++ b/Tests/ListDiffTests.cs @@ -1,4 +1,5 @@ using ListDiff; +using static ListDiff.DiffModule; using Xunit; namespace ListDiffTests @@ -26,7 +27,7 @@ public void SimpleCases (string left, string right, string expectedDiff) Assert.Equal (expectedDiff, diff1.ToString ()); - var diff2 = ListDiff.ListDiff.Diff (left, right, (s, _) => s.ToString (), d => $"+({d})", s => $"-({s})"); + var diff2 = Diff (left, right, (s, _) => s.ToString (), d => $"+({d})", s => $"-({s})"); Assert.Equal (expectedDiff, string.Join (string.Empty, diff2)); } @@ -52,7 +53,7 @@ public void DiffMiddleModificationOfLongList (int listSize) Assert.Equal (expectedDiff, diff1.ToString ()); - var diff2 = ListDiff.ListDiff.Diff (original, modified, (s, _) => s.ToString (), d => $"+({d})", s => $"-({s})"); + var diff2 = Diff (original, modified, (s, _) => s.ToString (), d => $"+({d})", s => $"-({s})"); Assert.Equal (expectedDiff, string.Join (string.Empty, diff2)); } From 9e67da46f70060ac498d96cc7d5cf2ff5658e9f0 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 6 May 2020 20:14:26 +0200 Subject: [PATCH 3/3] Add doc comments to-do This is just to fix release builds for now and will be fleshed post initial PR review. --- ListDiff/Diff.cs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/ListDiff/Diff.cs b/ListDiff/Diff.cs index caf46db..97fa5a8 100644 --- a/ListDiff/Diff.cs +++ b/ListDiff/Diff.cs @@ -26,23 +26,38 @@ 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, @@ -52,21 +67,33 @@ static partial class DiffModule 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,