TheBoubou
TheBoubou

Reputation: 19933

Find items in one list based on items in another list of a different type

I have a List<Class1> and List<Class2>, and I'd like get all the items from List<Class1> where the item's Id exists in List<Class2>, and, conversely, get all the items from List<Class1> where the item's Id does not exist in List<Class2>.

public class Class1
{
    public int Id { get; set; }
    public string AnotherId { get; set; }
    public decimal Price { get; set; }
}

public class Class2
{
    public int Id { get; set; }
    public int AnotherId { get; set; }
    public string Message { get; set; }
}

I tried several solution with Contains but don't find the right way to do it.

Upvotes: 0

Views: 59

Answers (4)

aquinas
aquinas

Reputation: 23796

If you have a small number of items in your lists, the answer by Christos is the simplest. However, it is an O(n^2) algorithm. If you have a decent number of items in your lists (even in the few thousands), that algorithm will significantly slow down. Let's try an example:

    List<Class1> list1 = new List<Class1>();
    List<Class2> list2 = new List<Class2>();

    const int ITEMS_TO_ADD = 30000;

    for (int i=1; i<= ITEMS_TO_ADD; i++)
    {
        list1.Add(new Class1 { Id = i });
        list2.Add(new Class2 { Id = i });
    }

    for (int i = 1; i <= ITEMS_TO_ADD; i++)
    {
        list1.Add(new Class1 { Id = -i });
    }

    Stopwatch sw = new Stopwatch();
    sw.Start();                        
    var result = list1.Where(x => list2.Any(y => y.Id == x.Id)).ToList();
    sw.Stop();
    Console.WriteLine(sw.ElapsedMilliseconds);

On my machine, that takes around 30 seconds.

A better solution, again, if you have a decent sized data set would be something like this:

HashSet<int> list2Keys = new HashSet<int>(list2.Select(x => x.Id));
var resultInList2 = list1.Where(x => list2Keys.Contains(x.Id)).ToList();

Which on my machine takes about 10 milliseconds.

Of course, to get items NOT in list 2 you simply change the line to

var resultInList2 = list1.Where(x => !list2Keys.Contains(x.Id)).ToList();

This is O(n) of course as opposed to O(n^2)

Upvotes: 0

Rufus L
Rufus L

Reputation: 37070

There are several ways to do this, the simplest way would be to use the System.Linq methods, Where(), All(), and Any():

var matchingIds = list1
    .Where(list1Item => list2.Any(list2Item => list2Item.Id == list1Item.Id))
    .ToList(); 

var nonMatching = list1
    .Where(list1Item => list2.All(list2Item => list2Item.Id != list1Item.Id))
    .ToList();

But it's also easy to do with a simple loop:

var matchingIds = new List<Class1>();
var nonMatching = new List<Class1>();

foreach (var list1Item in list1)
{
    var foundMatch = false;

    foreach (var list2Item in list2)
    {
        if (list2Item.Id == list1Item.Id)
        {
            foundMatch = true;
            break;
        }
    }

    if (foundMatch)
    {
        matchingIds.Add(list1Item);
    }
    else
    {
        nonMatching.Add(list1Item);
    }
}

Upvotes: 0

Abdessamad Azzouzi
Abdessamad Azzouzi

Reputation: 122

Try something like this :

        foreach(var val in listClass2)
        {
            result.AddRange(listClass1.Where(l => l.id = val.id));
        }

Upvotes: 0

Christos
Christos

Reputation: 53958

You could try something like this:

var result = list1.Where(x => list2.Any(y=>y.Id == x.Id))
                  .ToList();

Even if both lists contained references to objects of the same type, Contains wouldn't work out of the box. Had it been this the case, you would have either implement the interface IEquatable for your class or override both Equals and GetHashCode methods. Only then Contains would work as you expected to.

Upvotes: 3

Related Questions