Reputation:
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
Reputation: 1
var list1 = new List() { "apple", "bread", "cheese" };
var list2 = new List() { "bread", "cheese", "mango" };
var result = list1.Intersect(list2);
Upvotes: 0
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
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
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
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
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
Reputation: 535
var resultList = checklist.Where(p => myList.All(l => p.value != l.value)).ToList();
Upvotes: 0
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
Reputation: 573
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
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
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
Reputation: 63190
You could do something like this:
var result = customlist.Where(p => !otherlist.Any(l => p.someproperty == l.someproperty));
Upvotes: 95
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
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
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
Reputation: 7150
If both your lists implement IEnumerable interface you can achieve this using LINQ.
list3 = list1.where(i => !list2.contains(i));
Upvotes: -3