Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge two Lists in C# and merge objects with the same id into one list item

Tags:

c#

.net

list

linq

I have already thought about how I'm going to solve this by rolling my own solution, but I wondered if .NET already has the functionality for what I'm trying to acheive - if so, I'd rather use something built-in.

Suppose I have two instances of a Widget object, let's call them PartA and PartB. The information from each has been garnered from two different web services, but both have matching IDs.

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 merge these 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 differs between each, but we consider PartA the master, so the result retains the value but different.

Now to complicate matters:

Suppose we have two lists:

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

Now here's some pseudocode describing what I want to do:

List<Widget> Result = PartA.MergeWith(PartB).MergeObjectsOn(Widget.ID).toList();
like image 886
Iain Fraser Avatar asked Sep 04 '12 00:09

Iain Fraser


2 Answers

You could write your own extension method(s), something like this:

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 is like:

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

Implemented e.g. like:

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's as simple as PartA.MergeWith(PartB).ToList().

like image 69
Tim S. Avatar answered Sep 24 '22 12:09

Tim S.


If your lists are one for one (i.e. same number of items and each item in the PartA list has a match in the PartB list), then I would consider the Zip extension method. Note that Zip does not actually require each list to have same number of items. However, if you can't rely on "pairing up" items with matching IDs, then my simplistic approach won't work.

You could 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 could encapsulate the individual Widget merging logic in a function or extension method and use that instead of the inline delegate.

like image 24
wageoghe Avatar answered Sep 22 '22 12:09

wageoghe