Merging two lists in C # and combining objects with the same identifier into one list item

I was already thinking about how I am going to solve this by copying my own solution, but I was wondering if .NET already has functionality for what I'm trying to achieve - if so, I would prefer to use something built-in.

Suppose I have two instances of a Widget object, call them PartA and PartB . Information from each of them was obtained from two different web services, but both have corresponding identifiers.

 PartA { ID: 19, name: "Percy", taste: "", colour: "Blue", shape: "", same_same: "but different" } PartB { ID: 19, name: "", taste: "Sweet", colour: "", shape: "Hexagon", same_same: "but not the same" } 

I want to combine them to create the following:

 Result { ID: 19, name: "Percy", taste: "Sweet", colour: "Blue", shape: "Hexagon", same_same: "but different" } 

Notice how the value for same_same is different from each, but we are considering PartA master, so the result retains the value of but different .

Now, to complicate matters:

Suppose we have two lists:

 List<Widget> PartA = getPartA(); List<Widget> PartB = getPartB(); 

Now here is some pseudo code describing what I want to do:

 List<Widget> Result = PartA.MergeWith(PartB).MergeObjectsOn(Widget.ID).toList(); 
+6
source share
3 answers

You can write your own extension method (s), for example:

 static class Extensions { public static IEnumerable<T> MergeWith<T>(this IEnumerable<T> source, IEnumerable<T> other) where T : ICanMerge { var otherItems = other.ToDictionary(x => x.Key); foreach (var item in source) { yield return (T)item.MergeWith(otherItems[item.Key]); } } public static string AsNullIfEmpty(this string s) { if (string.IsNullOrEmpty(s)) return null; else return s; } } 

Where ICanMerge as follows:

 public interface ICanMerge { object Key { get; } ICanMerge MergeWith(ICanMerge other); } 

Implemented, for example. as:

 public class Widget : ICanMerge { object ICanMerge.Key { get { return this.ID; } } int ID {get;set;} string taste {get;set;} public ICanMerge MergeWith(ICanMerge other) { var merged = new Widget(); var otherWidget = (Widget)other; merged.taste = this.taste.AsNullIfEmpty() ?? otherWidget.taste; //... return merged; } } 

Then it is as simple as PartA.MergeWith(PartB).ToList() .

+13
source

If your lists are the same for one (the same number of elements, and each element in the PartA list has a match in the PartB list), I would consider the Zip Extension . Note that Zip does not actually require that each list have the same number of items. However, if you cannot rely on the elements of "conjugation" with the corresponding identifiers, my simplified approach will not work.

You can do something like this:

 var alist = GetPartAWidgets().OrderBy(w => w.ID); var blist = GetPartBWidgets().OrderBy(w => w.ID); var merged = alist.Zip(blist, (a,b) => new Widget() { ID = a.ID, Name = string.IsNullOrEmpty(a.Name) ? b.Name : a.Name, //etc. }); 

If you want your linq to look cleaner, you can encapsulate the separate logic of merging the widget into a function or extension method and use this instead of the built-in delegate.

+2
source
  public interface IMerge<out T> { IEnumerable<IMergeMatched<T>> Matched(); IEnumerable<IMergeMatched<T>> Matched(Func<T, T, bool> predicate); IEnumerable<T> NotMatchedBySource(); IEnumerable<T> NotMatchedBySource(Func<T, bool> predicate); IEnumerable<T> NotMatchedByTarget(); IEnumerable<T> NotMatchedByTarget(Func<T, bool> predicate); } public interface IMergeMatched<out T> { T Source { get; } T Target { get; } } public static class Enumerable { public static IMerge<TSource> Merge<TSource>(this IEnumerable<TSource> source, IEnumerable<TSource> target, Func<TSource, TSource, bool> predicate) { return new Merge<TSource>(source, target, predicate); } } public class Merge<T> : IMerge<T> { private readonly Func<T, T, bool> _predicate; private readonly IEnumerable<T> _source; private readonly IEnumerable<T> _target; private IEnumerable<IMergeMatched<T>> _matcheds; private IEnumerable<T> _notMatchedBySource; private IEnumerable<T> _notMatchedByTarget; public Merge(IEnumerable<T> source, IEnumerable<T> taget, Func<T, T, bool> predicate) { _source = source; _target = taget; _predicate = predicate; } public IEnumerable<IMergeMatched<T>> Matched() { if (_matcheds == null) { Analize(); } return _matcheds; } public IEnumerable<IMergeMatched<T>> Matched(Func<T, T, bool> predicate) { return Matched() .Where(t => predicate.Invoke(t.Source, t.Target)) .ToArray(); } public IEnumerable<T> NotMatchedBySource() { if (_notMatchedBySource == null) { Analize(); } return _notMatchedBySource; } public IEnumerable<T> NotMatchedBySource(Func<T, bool> predicate) { return NotMatchedBySource() .Where(predicate) .ToArray(); } public IEnumerable<T> NotMatchedByTarget() { if (_notMatchedByTarget == null) { Analize(); } return _notMatchedByTarget; } public IEnumerable<T> NotMatchedByTarget(Func<T, bool> predicate) { return NotMatchedByTarget() .Where(predicate) .ToArray(); } private void Analize() { var macheds = new List<MergeMached<T>>(); var notMachedBySource = new List<T>(_source); var notMachedByTarget = new List<T>(_target); foreach (var source in _source) { foreach (var target in _target) { var macth = _predicate.Invoke(source, target); if (!macth) continue; macheds.Add(new MergeMached<T>(source, target)); notMachedBySource.Remove(source); notMachedByTarget.Remove(target); } } _matcheds = macheds.ToArray(); _notMatchedBySource = notMachedBySource.ToArray(); _notMatchedByTarget = notMachedByTarget.ToArray(); } } public class MergeMached<T> : IMergeMatched<T> { public MergeMached(T source, T target) { Source = source; Target = target; } public T Source { get; private set; } public T Target { get; private set; } } 

How to use?

 var source = new List<MediaFolder> { new MediaFolder { Id = "Id1", Name = "Name1", Path = "Path1" }, new MediaFolder { Id = "Id2", Name = "Name2", Path = "Path2" }, new MediaFolder { Id = "Id3", Name = "Name3", Path = "Path3" }, new MediaFolder { Id = "Id4", Name = "Name4", Path = "Path4" }, new MediaFolder { Id = "Id5", Name = "Name5", Path = "Path5" }, new MediaFolder { Id = "Id6", Name = "Name6", Path = "Path6" } }; var target = new List<MediaFolder> { new MediaFolder { Id = "Id1", Name = "Actualizado en el objeto", Path = "Path1" }, //Id2 eliminado new MediaFolder { Id = "Id3", Name = "Name3", Path = "Actualizado tambien" }, new MediaFolder { Id = "Id4", Name = "Name4", Path = "Path4" }, new MediaFolder { Id = "Id5", Name = "Name5", Path = "Path5" }, new MediaFolder { Id = "Id6", Name = "Name6", Path = "Path6" }, new MediaFolder { Id = "Id7", Name = "Nuevo Item 7", Path = "Nuevo Item 7" } }; var merge = source.Merge(target, (x, y) => x.Id == y.Id); var toUpdate = merge.Matched((x, y) => x.Name != y.Name | x.Path != y.Path) .ToArray(); var toDelete = merge.NotMatchedBySource(); var toInsert = merge.NotMatchedByTarget(); Assert.AreEqual(2, toUpdate.Count()); Assert.IsTrue(toUpdate.Count(x => x.Source.Id == "Id1" & x.Target.Id == "Id1") > 0); Assert.IsTrue(toUpdate.Count(x => x.Source.Id == "Id3" & x.Target.Id == "Id3") > 0); Assert.AreEqual("Id7", toInsert.First().Id); Assert.AreEqual("Id2", toDelete.First().Id); 
0
source

Source: https://habr.com/ru/post/924481/


All Articles