diff --git a/DeveCoolLib.Tests/Collections/ListSynchronizerFacts.cs b/DeveCoolLib.Tests/Collections/ListSynchronizerFacts.cs new file mode 100644 index 0000000..1d83932 --- /dev/null +++ b/DeveCoolLib.Tests/Collections/ListSynchronizerFacts.cs @@ -0,0 +1,346 @@ +using DeveCoolLib.Collections; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Reflection; +using System.Text; +using Xunit; +using Xunit.Abstractions; + +namespace DeveCoolLib.Tests.Collections +{ + public class ListSynchronizerFacts + { + private readonly ITestOutputHelper _output; + private readonly string _currentTestName; + + public ListSynchronizerFacts(ITestOutputHelper output) + { + _output = output; + var type = output.GetType(); + var testMember = type.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic); + var test = (ITest)testMember.GetValue(output); + _currentTestName = test.DisplayName; + } + + [Fact] + public void AddsItems() + { + //Arrange + var source = new List() + { + "Item 1", + "Item 2", + "Item 3" + }; + + var dest = new ObservableCollection() + { + "Item 1", + "Item 2" + }; + + dest.CollectionChanged += (sender, e) => _output.WriteLine($"{_currentTestName}: {e.Action}"); + + //Act + ListSynchronizer.SynchronizeLists(source, dest); + + //Assert + Assert.Equal(source.Count, dest.Count); + for (int i = 0; i < source.Count; i++) + { + Assert.Equal(source[i], dest[i]); + } + } + + [Fact] + public void RemovesItems() + { + //Arrange + var source = new List() + { + "Item 1", + "Item 2" + }; + + var dest = new ObservableCollection() + { + "Item 1", + "Item 2", + "Item 3" + }; + + dest.CollectionChanged += (sender, e) => _output.WriteLine($"{_currentTestName}: {e.Action}"); + + //Act + ListSynchronizer.SynchronizeLists(source, dest); + + //Assert + Assert.Equal(source.Count, dest.Count); + for (int i = 0; i < source.Count; i++) + { + Assert.Equal(source[i], dest[i]); + } + } + + [Fact] + public void CorrectlySortsItems() + { + //Arrange + var source = new List() + { + "Item 1", + "Item 2", + "Item 3" + }; + + var dest = new ObservableCollection() + { + "Item 3", + "Item 2", + "Item 1" + }; + + dest.CollectionChanged += (sender, e) => _output.WriteLine($"{_currentTestName}: {e.Action}"); + + //Act + ListSynchronizer.SynchronizeLists(source, dest); + + //Assert + Assert.Equal(source.Count, dest.Count); + for (int i = 0; i < source.Count; i++) + { + Assert.Equal(source[i], dest[i]); + } + } + + [Fact] + public void AddsAndSortsItems() + { + //Arrange + var source = new List() + { + "Item 1", + "Item 2", + "Item 3" + }; + + var dest = new ObservableCollection() + { + "Item 2", + "Item 3" + }; + + dest.CollectionChanged += (sender, e) => _output.WriteLine($"{_currentTestName}: {e.Action}"); + + //Act + ListSynchronizer.SynchronizeLists(source, dest); + + //Assert + Assert.Equal(source.Count, dest.Count); + for (int i = 0; i < source.Count; i++) + { + Assert.Equal(source[i], dest[i]); + } + } + + [Fact] + public void RemovesAndSortsItems() + { + //Arrange + var source = new List() + { + "Item 1", + "Item 2" + }; + + var dest = new ObservableCollection() + { + "Item 3", + "Item 1", + "Item 2" + }; + + dest.CollectionChanged += (sender, e) => _output.WriteLine($"{_currentTestName}: {e.Action}"); + + //Act + ListSynchronizer.SynchronizeLists(source, dest); + + //Assert + Assert.Equal(source.Count, dest.Count); + for (int i = 0; i < source.Count; i++) + { + Assert.Equal(source[i], dest[i]); + } + } + + [Fact] + public void AddsAndRemovesItems() + { + //Arrange + var source = new List() + { + "Item 2", + "Item 3", + "Item 4" + }; + + var dest = new ObservableCollection() + { + "Item 1", + "Item 2", + "Item 3" + }; + + dest.CollectionChanged += (sender, e) => _output.WriteLine($"{_currentTestName}: {e.Action}"); + + //Act + ListSynchronizer.SynchronizeLists(source, dest); + + //Assert + Assert.Equal(source.Count, dest.Count); + for (int i = 0; i < source.Count; i++) + { + Assert.Equal(source[i], dest[i]); + } + } + + [Fact] + public void RemovesDuplicateItemsAlso() + { + //Arrange + var source = new List() + { + "Item 1", + "Item 2", + "Item 3" + }; + + var dest = new ObservableCollection() + { + "Item 1", + "Item 1", + "Item 1", + "Item 2", + "Item 3" + }; + + dest.CollectionChanged += (sender, e) => _output.WriteLine($"{_currentTestName}: {e.Action}"); + + //Act + ListSynchronizer.SynchronizeLists(source, dest); + + //Assert + Assert.Equal(source.Count, dest.Count); + for (int i = 0; i < source.Count; i++) + { + Assert.Equal(source[i], dest[i]); + } + } + + [Fact] + public void RemovesDuplicateItemsThatAreNotInSource() + { + //Arrange + var source = new List() + { + "Item 2", + "Item 3" + }; + + var dest = new ObservableCollection() + { + "Item 1", + "Item 1", + "Item 1", + "Item 2", + "Item 3" + }; + + dest.CollectionChanged += (sender, e) => _output.WriteLine($"{_currentTestName}: {e.Action}"); + + //Act + ListSynchronizer.SynchronizeLists(source, dest); + + //Assert + Assert.Equal(source.Count, dest.Count); + for (int i = 0; i < source.Count; i++) + { + Assert.Equal(source[i], dest[i]); + } + } + + [Fact] + public void AddsDuplicateItems() + { + //Arrange + var source = new List() + { + "Item 1", + "Item 1", + "Item 1", + "Item 2", + "Item 3" + }; + + var dest = new ObservableCollection() + { + "Item 1", + "Item 2", + "Item 3" + }; + + dest.CollectionChanged += (sender, e) => _output.WriteLine($"{_currentTestName}: {e.Action}"); + + //Act + ListSynchronizer.SynchronizeLists(source, dest); + + //Assert + Assert.Equal(source.Count, dest.Count); + for (int i = 0; i < source.Count; i++) + { + Assert.Equal(source[i], dest[i]); + } + } + + [Fact] + public void DoesAllTheComplexStuffAtOnce() + { + //Arrange + var source = new List() + { + "Item 6", + "Item 1", + "Item 3", + "Item 1", + "Item 2", + "Item 1" + }; + + var dest = new ObservableCollection() + { + "Item 1", + "Item 2", + "Item 3", + "Item 3", + "Item 3", + "Item 4", + "Item 4", + "Item 5" + }; + + dest.CollectionChanged += (sender, e) => _output.WriteLine($"{_currentTestName}: {e.Action}"); + + //Act + ListSynchronizer.SynchronizeLists(source, dest); + + //Assert + Assert.Equal(source.Count, dest.Count); + for (int i = 0; i < source.Count; i++) + { + Assert.Equal(source[i], dest[i]); + } + } + } +} diff --git a/DeveCoolLib/Collections/ListSynchronizer.cs b/DeveCoolLib/Collections/ListSynchronizer.cs new file mode 100644 index 0000000..6d8041f --- /dev/null +++ b/DeveCoolLib/Collections/ListSynchronizer.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace DeveCoolLib.Collections +{ + public static class ListSynchronizer + { + public static void SynchronizeLists(IList source, IList dest) where T : class + { + var sourceDist = source.Distinct().ToList(); + var destDist = dest.Distinct().ToList(); + + var toRemoveCompletely = destDist.Except(sourceDist); + + foreach (var item in toRemoveCompletely) + { + RemoveAll(dest, item); + } + + for (int i = 0; i < sourceDist.Count; i++) + { + var sourceItem = sourceDist[i]; + + var countInSource = source.Count(t => t == sourceItem); + var countInDest = dest.Count(t => t == sourceItem); + + while (countInDest > countInSource) + { + var pos = dest.IndexOf(sourceItem); + dest.RemoveAt(pos); + countInDest--; + } + + while (countInDest < countInSource) + { + dest.Add(sourceItem); + countInDest++; + } + } + + //var dictCount = new Dictionary(); + + for (int i = 0; i < dest.Count; i++) + { + var sourceItem = source[i]; + var destItem = dest[i]; + + if (sourceItem != destItem) + { + var posToSwapFrom = FindNext(dest, sourceItem, i); + + Swap(dest, posToSwapFrom, i); + } + } + } + + public static void RemoveAll(IList list, T item) where T : class + { + for (int i = list.Count - 1; i >= 0; i--) + { + if (list[i] == item) + { + list.RemoveAt(i); + } + } + } + + //public static int GetFromDict(Dictionary dict, T item) + //{ + // if (dict.TryGetValue(item, out var value)) + // { + // return value; + // } + // else + // { + // dict[item] = 0; + // return 0; + // } + //} + + public static int FindNext(IList list, T item, int start) where T : class + { + for (int i = start; i < list.Count; i++) + { + var found = list[i]; + if (found == item) + { + return i; + } + } + return -1; + } + + public static void Swap(IList list, int source, int dest) + { + var temp = list[dest]; + list[dest] = list[source]; + list[source] = temp; + } + } +}