8

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?

1
  • perhaps you can clarify using a before/after illustration? you have me completely lost now Commented Oct 11, 2011 at 19:24

4 Answers 4

9

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
       };
Sign up to request clarification or add additional context in comments.

3 Comments

That last line should return f.X when the condition is true, instead of assuming that it will always be the empty string, but otherwise, +1
Give us AJAX-style refreshes on answer edits, SO, seriously. ;)
How do you convert from r in joinResult.DefaultIfEmpty() to linq method form?
6

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;

Comments

1

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());
}

Comments

1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.