user399356
user399356

Reputation:

Difference between two lists

I Have two generic list filled with CustomsObjects.

I need to retrieve the difference between those two lists(Items who are in the first without the items in the second one) in a third one.

I was thinking using .Except() was a good idea but I don't see how to use this.. Help!

Upvotes: 196

Views: 301197

Answers (16)

Urapola
Urapola

Reputation: 1

var list1 = new List() { "apple", "bread", "cheese" };

var list2 = new List() { "bread", "cheese", "mango" };

var result = list1.Intersect(list2);

Upvotes: 0

NanoWar
NanoWar

Reputation: 81

I'd like to share my take, becauses it improves on the update function. There it returns a tuple of old and new value, so you can update easier.

It also uses many of the newer C# features like value tuples, inline functions, and my favorite (oldish) IEnumerable/yield list iterator for conditional lists.

Could be improved by using better list operation, such as Except/Join/Intersect. Also could be an extension method.

using System;
using System.Collections.Generic;
using System.Linq;
        
public class Program
{
    public static void Main()
    {
        var diff = Diff(new List<string>{"remove", "update"}, new List<string>{"update", "add"}, i => i, i => i); 
        Console.WriteLine(diff.Added.Count()); // = 1
        Console.WriteLine(diff.Removed.Count()); // = 1
        Console.WriteLine(diff.Updated.Count()); // = 1
    }
    
    public static (IEnumerable<T2> Added, IEnumerable<T1> Removed, IEnumerable<(T1, T2)> Updated) Diff<T1, T2, TKey>(
        IList<T1> first,
        IList<T2> second,
        Func<T1, TKey> firstKey,
        Func<T2, TKey> secondKey) where TKey : IEquatable<TKey>
    {
        var added = second.Where(s => first.All(f => !firstKey(f).Equals(secondKey(s))));
        var removed = first.Where(f => second.All(s => !firstKey(f).Equals(secondKey(s))));
        var updated = Updated();

        return (added, removed, updated);

        IEnumerable<(T1, T2)> Updated()
        {
            foreach (var f in first)
            {
                var secondMatch = second.FirstOrDefault(s => firstKey(f).Equals(secondKey(s)));
                if (secondMatch is not null)
                {
                    yield return (f, secondMatch);
                }
            }
        }
    }
}

Upvotes: 0

croban
croban

Reputation: 447

To get unique differences from both lists you could merge them (Union) Except those values which are same inside both lists (Intersect) e.g.:

var list1 = new List<int> { 1, 2, 3, 4, 5 };
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var diffs = list1.Union(list2).Except(list1.Intersect(list2));

For complex types implement IComparable if you comparing your instances all the time with the same compare pattern. If you need different compare pattern in some cases than you can always create an class which implements IEqualityComparer.

Upvotes: 7

user3526723
user3526723

Reputation: 121

Following helper can be usefull if for such task:

There are 2 collections local collection called oldValues and remote called newValues From time to time you get notification bout some elements on remote collection have changed and you want to know which elements were added, removed and updated. Remote collection always returns ALL elements that it has.

    public class ChangesTracker<T1, T2>
{
    private readonly IEnumerable<T1> oldValues;
    private readonly IEnumerable<T2> newValues;
    private readonly Func<T1, T2, bool> areEqual;

    public ChangesTracker(IEnumerable<T1> oldValues, IEnumerable<T2> newValues, Func<T1, T2, bool> areEqual)
    {
        this.oldValues = oldValues;
        this.newValues = newValues;
        this.areEqual = areEqual;
    }

    public IEnumerable<T2> AddedItems
    {
        get => newValues.Where(n => oldValues.All(o => !areEqual(o, n)));
    }

    public IEnumerable<T1> RemovedItems
    {
        get => oldValues.Where(n => newValues.All(o => !areEqual(n, o)));
    }

    public IEnumerable<T1> UpdatedItems
    {
        get => oldValues.Where(n => newValues.Any(o => areEqual(n, o)));
    }
}

Usage

        [Test]
    public void AddRemoveAndUpdate()
    {
        // Arrange
        var listA = ChangesTrackerMockups.GetAList(10); // ids 1-10
        var listB = ChangesTrackerMockups.GetBList(11)  // ids 1-11
            .Where(b => b.Iddd != 7); // Exclude element means it will be delete
        var changesTracker = new ChangesTracker<A, B>(listA, listB, AreEqual);

        // Assert
        Assert.AreEqual(1, changesTracker.AddedItems.Count()); // b.id = 11
        Assert.AreEqual(1, changesTracker.RemovedItems.Count()); // b.id = 7
        Assert.AreEqual(9, changesTracker.UpdatedItems.Count()); // all a.id == b.iddd
    }

    private bool AreEqual(A a, B b)
    {
        if (a == null && b == null)
            return true;
        if (a == null || b == null)
            return false;
        return a.Id == b.Iddd;
    }

Upvotes: 7

Roni Al Ka
Roni Al Ka

Reputation: 26

here is my solution:

    List<String> list1 = new List<String>();

    List<String> list2 = new List<String>();

    List<String> exceptValue = new List<String>();

foreach(String L1 in List1) 
{
    if(!List2.Contains(L1)
    {
         exceptValue.Add(L1);
    }
}
foreach(String L2 in List2) 
{
    if(!List1.Contains(L2)
    {
         exceptValue.Add(L2);
    }
}

Upvotes: 1

Daniel Nicolay
Daniel Nicolay

Reputation: 153

var list3 = list1.Where(x => !list2.Any(z => z.Id == x.Id)).ToList();

Note: list3 will contain the items or objects that are not in both lists. Note: Its ToList() not toList()

Upvotes: 13

Teezy7
Teezy7

Reputation: 535

var resultList = checklist.Where(p => myList.All(l => p.value != l.value)).ToList();

Upvotes: 0

Stas Boyarincev
Stas Boyarincev

Reputation: 3980

I think important to emphasize - using Except method will return you items who are in the first without the items in the second one only. It does not return those elements in second that do not appear in first.

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2).ToList(); //list3 contains only 1, 2

But if you want get real difference between two lists:

Items who are in the first without the items in the second one and items who are in the second without the items in the first one.

You need using Except twice:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2); //list3 contains only 1, 2
var list4 = list2.Except(list1); //list4 contains only 6, 7
var resultList = list3.Concat(list4).ToList(); //resultList contains 1, 2, 6, 7

Or you can use SymmetricExceptWith method of HashSet. But it changes the set on which called:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list1Set = list1.ToHashSet(); //.net framework 4.7.2 and .net core 2.0 and above otherwise new HashSet(list1)
list1Set.SymmetricExceptWith(list2);
var resultList = list1Set.ToList(); //resultList contains 1, 2, 6, 7

Upvotes: 76

List<ObjectC> _list_DF_BW_ANB = new List<ObjectC>();    
List<ObjectA> _listA = new List<ObjectA>();
List<ObjectB> _listB = new List<ObjectB>();

foreach (var itemB in _listB )
{     
    var flat = 0;
    foreach(var itemA in _listA )
    {
        if(itemA.ProductId==itemB.ProductId)
        {
            flat = 1;
            break;
        }
    }
    if (flat == 0)
    {
        _list_DF_BW_ANB.Add(itemB);
    }
}

Upvotes: -2

Jon Skeet
Jon Skeet

Reputation: 1500105

Using Except is exactly the right way to go. If your type overrides Equals and GetHashCode, or you're only interested in reference type equality (i.e. two references are only "equal" if they refer to the exact same object), you can just use:

var list3 = list1.Except(list2).ToList();

If you need to express a custom idea of equality, e.g. by ID, you'll need to implement IEqualityComparer<T>. For example:

public class IdComparer : IEqualityComparer<CustomObject>
{
    public int GetHashCode(CustomObject co)
    {
        if (co == null)
        {
            return 0;
        }
        return co.Id.GetHashCode();
    }

    public bool Equals(CustomObject x1, CustomObject x2)
    {
        if (object.ReferenceEquals(x1, x2))
        {
            return true;
        }
        if (object.ReferenceEquals(x1, null) ||
            object.ReferenceEquals(x2, null))
        {
            return false;
        }
        return x1.Id == x2.Id;
    }
}

Then use:

var list3 = list1.Except(list2, new IdComparer()).ToList();

Note that this will remove any duplicate elements. If you need duplicates to be preserved, it would probably be easiest to create a set from list2 and use something like:

var list3 = list1.Where(x => !set2.Contains(x)).ToList();

Upvotes: 346

Milad
Milad

Reputation: 31

        List<int> list1 = new List<int>();
        List<int> list2 = new List<int>();
        List<int> listDifference = new List<int>();

        foreach (var item1 in list1)
        {
            foreach (var item2 in list2)
            {
                if (item1 != item2)
                    listDifference.Add(item1);
            }
        }

Upvotes: -3

Tony The Lion
Tony The Lion

Reputation: 63190

You could do something like this:

var result = customlist.Where(p => !otherlist.Any(l => p.someproperty == l.someproperty));

Upvotes: 95

Atul Chaudhary
Atul Chaudhary

Reputation: 3736

bit late but here is working solution for me

 var myBaseProperty = (typeof(BaseClass)).GetProperties();//get base code properties
                    var allProperty = entity.GetProperties()[0].DeclaringType.GetProperties();//get derived class property plus base code as it is derived from it
                    var declaredClassProperties = allProperty.Where(x => !myBaseProperty.Any(l => l.Name == x.Name)).ToList();//get the difference

In above mention code I am getting the properties difference between my base class and derived class list

Upvotes: -1

foson
foson

Reputation: 10227

Since the Except extension method operates on two IEumerables, it seems to me that it will be a O(n^2) operation. If performance is an issue (if say your lists are large), I'd suggest creating a HashSet from list1 and use HashSet's ExceptWith method.

Upvotes: 1

rsenna
rsenna

Reputation: 11964

var third = first.Except(second);

(you can also call ToList() after Except(), if you don't like referencing lazy collections.)

The Except() method compares the values using the default comparer, if the values being compared are of base data types, such as int, string, decimal etc.

Otherwise the comparison will be made by object address, which is probably not what you want... In that case, make your custom objects implement IComparable (or implement a custom IEqualityComparer and pass it to the Except() method).

Upvotes: 12

dcarneiro
dcarneiro

Reputation: 7150

If both your lists implement IEnumerable interface you can achieve this using LINQ.

list3 = list1.where(i => !list2.contains(i));

Upvotes: -3

Related Questions