BrunoLM
BrunoLM

Reputation: 100322

How to merge two lists based on a property?

I have two lists, one fake and one real, like:

BEFORE

// fake (list 1)
{ ID = 1, Year = 2011, X = "" }
, { ID = 2, Year = 2012, X = "" }
, { ID = 3, Year = 2013, X = "" }

// real (list 2)
{ ID = 35, Year = 2011, X = "Information" }
, { ID = 77, Year = 2013, X = "Important" }

I want to merge them looking for the Year, the result should be:

AFTER

{ ID = 35, Year = 2011, X = "Information" }
, { ID = 2, Year = 2012, X = "" }
, { ID = 77, Year = 2013, X = "Important" }

It must remove elements with the same year on the first list and add the element with the equivalent Year on the second list to the first list, keeping the order.

How can I do it using Linq?

Upvotes: 8

Views: 8678

Answers (4)

KeithS
KeithS

Reputation: 71565

Instead of defining the fake list yourself, try having Linq do it for you:

Enumerable.Range(2011,3) //2011, 2012, 2013             
          //use the overload that provides a 0-based ordinal position of each element
          .Select(x,i=> new {ID = i+1, Year = x, X = String.Empty) 
          //now you have your fake list; join with the "real" list based on Year fields, 
          //taking the real element wherever it exists and the fake one otherwise
          .Join(real, l=>l.Year, r=>r.Year, (l,r) => r == null ? l : r);

This will produce exactly the result set you want. You will likely need to define a named type for the list items, though, as two separately-defined anonymous types cannot be implicitly converted even if they have all the same member types/names.

Upvotes: 1

amit_g
amit_g

Reputation: 31250

Using IEnumerable.Union and IEqualityComparer.

P.S. This would result in a different result when compared to left join if the real list had more elements (years that are not present in fake list). The left join would not return those results which could be a desired result (not clear from OP).

public class MyClass
{
    public int ID {get; set;}
    public int Year {get; set;}
    public string X {get; set;}
}

public class MyClassEqualityComparer :  IEqualityComparer<MyClass>
{
    public bool Equals(MyClass x, MyClass y)
    {
        return x.Year == y.Year;
    }

    public int GetHashCode(MyClass obj)
    {
        return obj.ToString().ToLower().GetHashCode();
    }
}

void Main()
{
    var fake = new List<MyClass> {
          new MyClass { ID = 1, Year = 2011, X = "" }
        , new MyClass { ID = 2, Year = 2012, X = "" }
        , new MyClass { ID = 3, Year = 2013, X = "" }
    };

    var real = new List<MyClass> {
          new MyClass { ID = 35, Year = 2011, X = "Information" }
        , new MyClass { ID = 77, Year = 2013, X = "Important" }
    };

    var merged = real.Union(fake, new MyClassEqualityComparer());
}

Upvotes: 1

Adam Maras
Adam Maras

Reputation: 26843

Justin's query is the most efficient way to do it, but if you're concerned with keeping identical objects (and not creating new records from the query) you could do it like this:

var combined = from f in fake
               let r = (from r1 in real
                        where r1.Year == f.Year
                        select r1).SingleOrDefault()
               select r ?? f;

Upvotes: 6

Justin Niessner
Justin Niessner

Reputation: 245389

You should be able to do that using a "left join":

from f in fake
join r in real
on f.Year equals r.Year
into joinResult
from r in joinResult.DefaultIfEmpty()
select new
       {
           ID = r == null ? f.ID : r.ID,
           Year = f.Year,
           X = r == null ? f.X : r.X
       };

Upvotes: 9

Related Questions