user2219738
user2219738

Reputation: 3

Compare lists with Linq, select records where ID appears in just one list

I am new to Linq and am tryng to filter records from two lists based on a field. Each list has an ID, I want to take any record where the ID appears in one list but not the other. I was able to do this with just a list of the ID's as follows:

List1 = _class1.getList1();
List2 = _class2.getList2();

(for introduction purposes I am using a class I would like to get rid of that has a list of the data and also a list of just the ID's, I should be able to do this with just the list of data though in two statements comparing list1 to list2 and vice versa)

var inList1ButNot2 = List1.IDList.Except(List2.IDList);
var inList2ButNot1 = List2.IDList.Except(List1.IDList);

Where I'm running into trouble is using the data list getting the comparison of the second list's ID field. I believe it should be something like:

var inList1ButNot2 = DataList1.Select(x => x.ID)
        .Except(DataList2.Select(y => y.ID));

The problem with that is that I'm not getting the entire record just the field I am comparing, do I need to individually select each field afterwards or is there a way in the statement to select the record if ID appears in one list but not the other?

Upvotes: 0

Views: 2809

Answers (6)

Rawling
Rawling

Reputation: 50114

Rather than rewrite the logic in Except (and all the other set operations), but allowing for something that can be reused for other classes and selectors, consider something like the following:

private class LambdaComparer<T, U> : IEqualityComparer<T>
{
    private Func<T, U> selector;

    public LambdaComparer(Func<T, U> selector)
    {
        this.selector = selector;
    }

    public bool Equals(T x, T y)
    {
        if (x == null && y == null) return true;
        if (x == null || y == null) return false;
        return EqualityComparer<U>.Default.Equals(selector(x), selector(y));
    }

    public int GetHashCode(T obj)
    {
        if (obj == null) return 0;
        return EqualityComparer<U>.Default.GetHashCode(selector(obj));
    }
}

var inList1ButNot2 = List1.IDList.Except(
    List2.IDList,
    new LambdaComparer<ClassWithID, int>(w => w.ID));

Upvotes: 0

Mailo
Mailo

Reputation: 29

you can try something like this

list1 = list1.Union(list2).Distinct().ToList();

Upvotes: -1

Sam
Sam

Reputation: 113

You might try something like this.

public void Test(List<List1> list1, List<List2> list2)
{
    var result = from l1 in list1
                 where list2.All(l2 => l1.Id != l2.Id)
                 select l1;

}

Or, if you have properties from both (I'm assuming they're different types?) you could return an anonymous type and also bring back properties from the callers instance

public void Test(List<List1> list1, List<List2> list2)
{
    var result = from l1 in list1
                 where list2.All(l2 => l1.Id != l2.Id)
                 select new
                 {
                     l1.Id,
                     l1.OtherField1,
                     Test = 10.5, //Example declare new field
                     SomethingElse = this.PropertyXyz; //Set new field = instance property
                 };

}

Upvotes: 0

Servy
Servy

Reputation: 203835

So what you really want here is an ExceptBy method; you want to be able to perform an Except on a projection of each element, rather than on the element iteself. Here is an implementation of such a method:

public static IEnumerable<TSource> ExceptBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    IEnumerable<TSource> other,
    Func<TSource, TKey> selector,
    IEqualityComparer<TKey> comparer = null)
{
    comparer = comparer ?? EqualityComparer<TKey>.Default;

    var set = new HashSet<TKey>(other.Select(selector), comparer);

    foreach (var item in source)
        if (set.Add(selector(item)))
            yield return item;
}

Now you can do:

var inList1ButNot2 = DataList1.ExceptBy(DataList2, item => item.ID);
var inList2ButNot1 = DataList2.ExceptBy(DataList1, item => item.ID);

Upvotes: 3

Daniel Pratt
Daniel Pratt

Reputation: 12077

There may be a better way to do this, but:

var inList1ButNot2 = DataList1.Where(x => !(DataList2.Any(y => y.ID == x.ID)));

NB: I free-handed that, so there may be a typo.

Upvotes: 2

Kamil Budziewski
Kamil Budziewski

Reputation: 23087

        var list1 = new List<Asd>();
        var list2 = new List<Asd>();

        var asd = new Asd() {Id = 1, Name = "asd"};
        var asd2 = new Asd() {Id = 2, Name = "asd"};
        var asd3 = new Asd() {Id = 3, Name = "asd"};
        var asd4 = new Asd() {Id = 4, Name = "asd"};
        var asd5 = new Asd() {Id = 5, Name = "asd"};

        list1.Add(asd);
        list1.Add(asd2);
        list1.Add(asd3);

        list2.Add(asd);
        list2.Add(asd4);
        list2.Add(asd5);

        var onlyInFirstList = list1.Where(x => !list2.Any(y => y == x));
        var onlyInSecondList = list2.Where(x => !list1.Any(y => y == x));

This should work, not perfect but working :)

Upvotes: 0

Related Questions