1

I have two lists will say ListA and ListB. I need to loop through ListB and compare ID's to ListA. If there is a match then I need to remove that item from ListB and replace it with the matching item/object from ListA.

I've been looking at THIS article. I've also had a look at Intersect. But I'm really not sure on how to get this to work with Linq.

Here is my code:

ListB is a query generated else where and passed in

var itemsForListA = Context.Set<Item>().AsQueryable();
var ListA = from i in itemsForListA 
            where i.ReplacementItemID != null
                  && (i.ItemStatus == "DISC" || i.ItemStatus == "ACT" 
                  && i.StoreID == null)
            select i;

foreach (var i in ListB)
{
    ListB = ListA.Where(x => x.Id == ListA.Id);
}

I thought I could do something like that. Do I first have to find the id in ListB and remove it then append on the new id from ListA to B?

Community
  • 1
  • 1
Troy Bryant
  • 994
  • 8
  • 29
  • 60

5 Answers5

3

I thought you can use left join in linq as below.

var list = from lb in ListB
    join la in ListA
        on lb.Id equals la.Id into ListC
    from lc in ListC.DefaultIfEmpty()
    select lc ?? lb;

It won't remove and replace the items, but it will give the same result and you can reassign to ListB

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
1

Use the built in Enumerable.Except() method using the IEqualityComparer.

Class to compare

public class Item
{
  public int Id { get; set; }
}

Comparer

public class ItemIdComparer<Item>
{
  public bool Equals(Item left, Item right)
  {
    return left.Id == right.Id;
  }
  public int GetHashCode(Item item)
  {
    return item.Id.GetHashCode();
  }
}

Usage

var all = new List<Item>();
var existing = new List<Item>();

var nonExisting = all.Except(existing, new ItemIdComparer())

I can't test this exactly... but it should be very close.

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
0

you can do as follow:

var list = listB.Join(listA, x => x.Id, y => y.Id, (x, y) => y).ToList();

list.AddRange(listB.Where(b => listA.Any(c => c.Id != b.Id)).ToList());
  1. First, I compare the listA with listB and selected objects from listA with same ID.
  2. to the selected objects I add it the elements from listB which have no comon id with listA.
Lucian Bumb
  • 2,821
  • 5
  • 26
  • 39
  • y.Id has this error "Error CS1061 'int' does not contain a definition for 'Id' and no extension method 'Id' accepting a first argument of type 'int' could be found (are you missing a using directive or an assembly reference?) – Troy Bryant Jul 28 '16 at 18:44
  • you said that the compare must be done by Id, to do so, you need to have the id prop, in both list objects – Lucian Bumb Jul 28 '16 at 18:46
0

I'm not sure how your model looks like, so I created my own model:

public class Person
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public byte Age { get; set; }
}

Create equality comparer:

public class PersonComaparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        if (x == null && y == null)
            return true;

        if ((x == null && y != null)
            || (x != null && y == null))
            return false;

        return x.Id == y.Id;
    }

    public int GetHashCode(Person obj)
    {
        if (obj == null)
            return 0;

        return obj.Id.GetHashCode();
    }
}

Then use them to compare lists:

List<Person> ListA = new List<Person>
{
    new Person { Id = 1, FullName = "Someone" }
};

List<Person> ListB = new List<Person>
{
    new Person { Id = 1 },
    new Person { Id = 2 }
};

PersonComaparer comparer = new PersonComaparer();
ListB = ListA
    .Intersect(ListB, comparer)
    .Union(ListB, comparer)
    .ToList();

The result will be:

Id  |   Full name
1       Someone
2       null
Adil Mammadov
  • 8,476
  • 4
  • 31
  • 59
0

I compared my answer below to Bapaiah Malasani's join solution using 10,000 items. My solution consistently took six seconds or more. His was consistently 10ms or less. Ouch. I have to go back and look at a whole ton of code I've written.


Should you use an IEqualityComparer<Item>? I would, because you're probably going to have lots and lots of code like this. Write this once and use it forever:

public class ItemComparer: IEqualityComparer<Item>
{
    public bool Equals(Item i1, Item i2)
    {
        if(i1 == null && i2 == null) return true;
        if(i1 == null ^ i2 ==null) return false;
        return(i1 == i2 || i1.Id == i2.Id);
    }

    public int GetHashCode(Item item)
    {
        return item != null ? item.Id.GetHashcode() : 0;
    }
}

Then you could wrap this whole thing up in a function which makes it easier to read. With a series of Linq statements it might not be clear what your intent is. But a function with a clear name helps:

IEnumerable<T> ReplaceMatchingItems<T>(IEnumerable<T> discardFrom,
    IEnumerable<T> replaceWith,
    IEqualityComparer<T> comparer = null)
    {
        //prevent multiple enumerations
        var discardFromArray = discardFrom as T[] ?? discardFrom.ToArray();
        var replacements = replaceWith.Where(item => discardFromArray.Contains(item, comparer)).ToArray();
        var newList = discardFromArray.Except(replacements, comparer).ToList();
        newList.AddRange(replacements);
        return newList;
    }

Now, the original function you wanted looks like this:

var listWithReplacements = ReplaceMatchingItems(ListB, ListA, new ItemComparer());

It looks like more code at first, but the IEqualityComparer is going to save you a lot of time down the road. And when someone sees a function call like ReplaceMatchingItems they're more likely to understand what the code is doing rather than just looking at a bunch of Linq queries.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
  • You also have *bunch of Linq queries*, but wrapped in a method. I think wrapping it in a method is a good idea, but it might as well contain the "outer join" (GroupJoin) from another answer. The outer join will perform much better than your code that iterates sequences multiple times. – Gert Arnold Jul 28 '16 at 22:03
  • Yes, you have the same queries either way. The difference is you don't have to see them and figure out what they're doing when you read the calling function. You just see the function call. So if you don't care about the details of that function you don't have to read them. Then in the place where all those queries are, it's easy to tell what they're doing because they're in a function with a name that describes them. – Scott Hannen Jul 28 '16 at 22:05
  • I like the left join solution. If you say it performs better I'm inclined to take your word for it. But now I'm curious so maybe I'll run some tests. In my environment the difference in performance wouldn't matter, but it might to someone else. – Scott Hannen Jul 28 '16 at 22:12
  • 1
    I tested side-by-side with 10,000 items. Not extremely scientific, but revealing enough. My method was consistently around 6-8 seconds. The method using the join was consistently < *10 ms*. Wow. That's not splitting hairs. – Scott Hannen Jul 28 '16 at 22:35