views:

653

answers:

4

I have the following scenario:

List 1 has 20 items of type TItem, List 2 has 5 items of the same type. List 1 already contains the items from List 2 but in a different state. I want to overwrite the 5 items in List 1 with the items from List 2.

I thought a join might work, but I want to overwrite the items in List 1, not join them together and have duplicates.

There is a unique key that can be used to find which items to overwrite in List 1 the key is of type int

+1  A: 

LINQ isn't used to perform actual modifications to the underlying data sources; it's strictly a query language. You could, of course, do an outer join on List2 from List1 and select List2's entity if it's not null and List1's entity if it is, but that is going to give you an IEnumerable<> of the results; it won't actually modify the collection. You could do a ToList() on the result and assign it to List1, but that would change the reference; I don't know if that would affect the rest of your application.

Taking your question literally, in that you want to REPLACE the items in List1 with those from List2 if they exist, then you'll have to do that manually in a for loop over List1, checking for the existence of a corresponding entry in List2 and replacing the List1 entry by index with that from List2.

Adam Robinson
+1  A: 

As Adam says, LINQ is about querying. However, you can create a new collection in the right way using Enumerable.Union. You'd need to create an appropriate IEqualityComparer though - it would be nice to have UnionBy. (Another one for MoreLINQ perhaps?)

Basically:

var list3 = list2.Union(list1, keyComparer);

Where keyComparer would be an implementation to compare the two keys. MiscUtil contains a ProjectionEqualityComparer which would make this slightly easier.

Alternatively, you could use DistinctBy from MoreLINQ after concatenation:

var list3 = list2.Concat(list1).DistinctBy(item => item.Key);
Jon Skeet
A: 

Here's a solution with GroupJoin.

        List<string> source = new List<string>() { "1", "22", "333" };
        List<string> modifications = new List<string>() { "4", "555"};
          //alternate implementation
        //List<string> result = source.GroupJoin(
        //    modifications,
        //    s => s.Length,
        //    m => m.Length,
        //    (s, g) => g.Any() ? g.First() : s
        //).ToList();

        List<string> result =
        (
            from s in source
            join m in modifications
            on s.Length equals m.Length into g
            select g.Any() ? g.First() : s
        ).ToList();

        foreach (string s in result)
            Console.WriteLine(s);

Hmm, how about a re-usable extension method while I'm at it:

    public static IEnumerable<T> UnionBy<T, U>
    (
        this IEnumerable<T> source,
        IEnumerable<T> otherSource,
        Func<T, U> selector
    )
    {
        return source.GroupJoin(
            otherSource,
            selector,
            selector,
            (s, g) => g.Any() ? g.First() : s
                );
    }

Which is called by:

List<string> result = source
  .UnionBy(modifications, s => s.Length)
  .ToList();
David B
A: 

You could use the built in Linq .Except() but it wants an IEqualityComparer so use a fluid version of .Except() instead.

Assuming an object with an integer key as you indicated:

public class Item
{
    public int Key { get; set; }
    public int Value { get; set; }
    public override string ToString()
    {
     return String.Format("{{{0}:{1}}}", Key, Value);
    }
}

The original list of objects can be merged with the changed one as follows:

IEnumerable<Item> original = new[] { 1, 2, 3, 4, 5 }.Select(x => new Item
    {
     Key = x,
     Value = x
    });
IEnumerable<Item> changed = new[] { 2, 3, 5 }.Select(x => new Item
    {
     Key = x,
     Value = x * x
    });

IEnumerable<Item> result = original.Except(changed, x => x.Key).Concat(changed);
result.ForEach(Console.WriteLine);

output:

{1:1}
{4:4}
{2:4}
{3:9}
{5:25}
Handcraftsman