I want to write a new override of Except extension method for IEnumerable which is able to take a comparer inline instead of using IEqualityComparer.
A, B are collections of a reference type..
Something like this:
A: [1, A], [2, A], [3, A]
B: [3, B], [4, B], [5, B]
C = A.Except(B, (a,b) => a.Id == b.Id);
C: [1, A], [2, A]
I wonder if you could help me with the code of the method.
public static class IEnumerableExntesion
{
public IEnumerable<T> Except<T>(this IEnumerable<T> source,
IEnumerable<T> second,
Func<T, T, bool> predicate)
{
}
}
I was thinking of:
return source.Where (s => !second.Any(p => p.Id == s.Id));
But actually I couldn't convert it to a generic solution using the passed predicate!
Any help!
Do you need to do the comparison using a predicate or can you use a projection instead and compare the projected values? If so then you could use some sort of ExceptBy method:
var c = a.ExceptBy(b, x => x.Id);
var r = p.ExceptBy(q, x => x.Name, StringComparer.OrdinalIgnoreCase);
// ...
public static class EnumerableExtensions
{
public static IEnumerable<TSource> ExceptBy<TSource, TKey>(
this IEnumerable<TSource> first, IEnumerable<TSource> second,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> keyComparer = null)
{
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");
if (keySelector == null) throw new ArgumentNullException("keySelector");
return first.ExceptByIterator(second, keySelector, keyComparer);
}
private static IEnumerable<TSource> ExceptByIterator<TSource, TKey>(
this IEnumerable<TSource> first, IEnumerable<TSource> second,
Func<TSource, TKey> keySelector, IEqualityComparer<TKey> keyComparer)
{
var keys = new HashSet<TKey>(second.Select(keySelector), keyComparer);
foreach (TSource item in first)
{
if (keys.Add(keySelector(item)))
yield return item;
}
}
}
This should work:
return source.Where(s => !second.Any(p => predicate(s, p))
return source.Where(s => !second.Any(p => predicate(p, s)));
I would instead use your passed Func<T,T,bool> to create a generic IEqualityComparer and pass it to regular Except:
public class PredicateEqualityComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _predicate;
public PredicateEqualityComparer(Func<T, T, bool> predicate)
{
_predicate = predicate;
}
public bool Equals(T x, T y)
{
return _predicate(x, y);
}
public int GetHashCode(T x)
{
return 0;
}
}
Your extension method:
public static class IEnumerableExntesion
{
public IEnumerable<T> Except<T>(this IEnumerable<T> source,
IEnumerable<T> second,
Func<T, T, bool> predicate)
{
return source.Except(second, new PredicateEqualityComparer(predicate));
}
}
Please note that I have stub GetHashCode implementation, it would be better to implement it properly but you would have to pass another delegate for it.
Related
I have a list of movies, and I need to merge them with another list and duplicate.
I am using Jon Skeet's DistinctBy(m => m.SomeUniqueMovieProperty) to achieve this and it works OK. Except, we soon discovered that there would be cases where 10-20% of the movies (in either list) don't have this property filled out, causing DistinctBy to collapse them into 1 lucky movie.
This is a problem, we want to keep all those movies that don't have a value for this property. Initially I thought of extracting these movies from each collection, duplicating, then merging them again, is there a shorter solution to this problem?
Concatenate the results of DistinctBy() with the results of Where([null or empty]).
var nullMovies = allMovies.Where(m=>string.IsNullOrEmpty(m.SomeUniqueMovieProperty));
var distinctNonNullMovies = allMovies.Where(m => !string.IsNullOrEmpty(m.SomeUniqueMovieProperty)).DistinctBy(m => m.SomeUniqueMovieProperty);
var result = nullMovies.Concat(distinctNonNullMovies);
Assuming m's Equals/GetHashCode isn't overridden, if m.SomeUniqueMovieProperty is null and you don't have any other unique key, you can use m itself as the unique key.
DistinctBy(m => (object) m.SomeUniqueMovieProperty ?? m)
If you want to include all of the nulls you need to replace the null property with something that is unique when it is null. Assuming the property is a string a Guid will do nicely for this job.
.DistinctBy(m => m.SomeUniqueMovieProperty ?? Guid.NewGuid().ToString())
Any time it hits a property with a null value it will be filled in with a random new guid value.
If you want to also have empty titles not get removed change the query to
.DistinctBy(m => String.IsNullOrEmpty(m.SomeUniqueMovieProperty) ? Guid.NewGuid().ToString() : m.SomeUniqueMovieProperty)
Another option is make your own DistinctBy that behaves the way you want. This is a tweaked version of the original source that only applies the filter if shouldApplyFilter returns true, comments are also removed for brevity.
static partial class MoreEnumerable
{
public static IEnumerable<TSource> ConditionalDistinctBy<TSource, TKey>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TKey, bool> shouldApplyFilter)
{
return source.ConditionalDistinctBy(keySelector, shouldApplyFilter, null);
}
public static IEnumerable<TSource> ConditionalDistinctBy<TSource, TKey>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TKey, bool> shouldApplyFilter, IEqualityComparer<TKey> comparer)
{
if (source == null) throw new ArgumentNullException("source");
if (keySelector == null) throw new ArgumentNullException("keySelector");
if (shouldApplyFilter == null) throw new ArgumentNullException("shouldApplyFilter");
return ConditionalDistinctByImpl(source, keySelector, shouldApplyFilter, comparer);
}
private static IEnumerable<TSource> ConditionalDistinctByImpl<TSource, TKey>(IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TKey, bool> shouldApplyFilter, IEqualityComparer<TKey> comparer)
{
var knownKeys = new HashSet<TKey>(comparer);
foreach (var element in source)
{
var key = keySelector(element);
if (shouldApplyFilter(key) && knownKeys.Add(key))
{
yield return element;
}
}
}
}
It would be used like
.ConditionalDistinctBy(m => m.SomeUniqueMovieProperty, s => !String.IsNullOrEmpty(s));
Perhaps you could filter them on a composite distinct key, like as follows
movies.DistinctBy(m => String.Format({0}{1}{...},m.prop1,m.prop2,[]));
One last way, which is probably overkill, you can implement IEqualityComparer and put the logic in there if null is consider unique. DistinctBy has an overload for just this case.
public class MovieComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
if (x == null || y == null)
{
return false;
}
return x == y;
}
public int GetHashCode(string obj)
{
if (obj == null)
{
return 0;
}
return obj.GetHashCode();
}
}
Is it possible with C# to pass a lambda expression as an IComparer argument in a method call?
eg something like
var x = someIEnumerable.OrderBy(aClass e => e.someProperty,
(aClass x, aClass y) =>
x.someProperty > y.SomeProperty ? 1 : x.someProperty < y.SomeProperty ? -1 : 0);
I can't quite get this to compile so I'm guessing not, but it seems such an obvious synergy between lambdas and anonymous delegates that I feel I must be doing something foolishly wrong.
TIA
If you're on .NET 4.5, you can use the static method Comparer<aClass>.Create.
Documentation: Comparer<T>.Create Method .
Example:
var x = someIEnumerable.OrderBy(e => e.someProperty,
Comparer<aClass>.Create((x, y) => x.someProperty > y.SomeProperty ? 1 : x.someProperty < y.SomeProperty ? -1 : 0)
);
As Jeppe points out, if you're on .NET 4.5, you can use the static method Comparer<T>.Create.
If not, this is an implementation that should be equivalent:
public class FunctionalComparer<T> : IComparer<T>
{
private Func<T, T, int> comparer;
public FunctionalComparer(Func<T, T, int> comparer)
{
this.comparer = comparer;
}
public static IComparer<T> Create(Func<T, T, int> comparer)
{
return new FunctionalComparer<T>(comparer);
}
public int Compare(T x, T y)
{
return comparer(x, y);
}
}
If you consistently want to compare projected keys (such as a single property), you can define a class that encapsulates all the key comparison logic for you, including null checks, key extraction on both objects, and key comparison using the specified or default inner comparer:
public class KeyComparer<TSource, TKey> : Comparer<TSource>
{
private readonly Func<TSource, TKey> _keySelector;
private readonly IComparer<TKey> _innerComparer;
public KeyComparer(
Func<TSource, TKey> keySelector,
IComparer<TKey> innerComparer = null)
{
_keySelector = keySelector;
_innerComparer = innerComparer ?? Comparer<TKey>.Default;
}
public override int Compare(TSource x, TSource y)
{
if (object.ReferenceEquals(x, y))
return 0;
if (x == null)
return -1;
if (y == null)
return 1;
TKey xKey = _keySelector(x);
TKey yKey = _keySelector(y);
return _innerComparer.Compare(xKey, yKey);
}
}
For convenience, a factory method:
public static class KeyComparer
{
public static KeyComparer<TSource, TKey> Create<TSource, TKey>(
Func<TSource, TKey> keySelector,
IComparer<TKey> innerComparer = null)
{
return new KeyComparer<TSource, TKey>(keySelector, innerComparer);
}
}
You could then use this like so:
var sortedSet = new SortedSet<MyClass>(KeyComparer.Create((MyClass o) => o.MyProperty));
You can refer to my blog post for an expanded discussion of this implementation.
I have a class (Patch) that I want to have sorted so I implemented IComparer.
However, it needs to be sorted depending on how the user wants it, e.g.:
- key1, key2, key3
- key1, key3, key2
For each key compare I have written a IComparer class, however, I was wondering how to implement its connection. i.e. when sorting I only can pass one IComparer instance.
Or should I make an IComparer class for each kind of full sorting, i.e. IComparerKey1Key2Key3, IComparerKey1Key3Key2 etc?
You could make a generic comparer that takes a delegate to select the key:
class ByKeyComparer<T, TKey> : IComparer<T>
{
private readonly Func<T, TKey> _keySelector;
private readonly IComparer<TKey> _keyComparer;
public ByKeyComparer(Func<T, TKey> keySelector, IComparer<TKey> keyComparer = null)
{
if (keySelector == null) throw new ArgumentNullException("keySelector");
_keySelector = keySelector;
_keyComparer = keyComparer ?? Comparer<TKey>.Default;
}
public int Compare(T x, T y)
{
return _keyComparer.Compare(_keySelector(x), _keySelector(y));
}
}
With a helper class to take advantage of type inference (so you don't need to specify the type of the key):
static class ByKeyComparer<T>
{
public static IComparer<T> Create<TKey>(Func<T, TKey> keySelector, IComparer<TKey> keyComparer = null)
{
return new ByKeyComparer<T, TKey>(keySelector, keyComparer);
}
}
You can use it like this:
var patchVersionComparer = ByKeyComparer<Patch>.Create(p => p.Version);
patches.Sort(patchVersionComparer);
If you need to combine several compare keys, you can create a comparer that uses other comparers:
class CompositeComparer<T> : IComparer<T>
{
private readonly IEnumerable<IComparer<T>> _comparers;
public CompositeComparer(IEnumerable<IComparer<T>> comparers)
{
if (comparers == null) throw new ArgumentNullException("comparers");
_comparers = comparers;
}
public CompositeComparer(params IComparer<T>[] comparers)
: this((IEnumerable<IComparer<T>>)comparers)
{
}
public int Compare(T x, T y)
{
foreach (var comparer in _comparers)
{
int result = comparer.Compare(x, y);
if (result != 0)
return result;
}
return 0;
}
}
Example usage:
var comparer = new CompositeComparer<Patch>(
ByKeyComparer<Patch>.Create(p => p.Key1),
ByKeyComparer<Patch>.Create(p => p.Key2),
ByKeyComparer<Patch>.Create(p => p.Key3));
patches.Sort(comparer);
EDIT: here's a more fluent API:
static class ByKeyComparer<T>
{
public static IComparer<T> CompareBy<TKey>(Func<T, TKey> keySelector, IComparer<TKey> keyComparer = null)
{
return new ByKeyComparer<T, TKey>(keySelector, keyComparer);
}
}
static class ComparerExtensions
{
public static IComparer<T> ThenBy<T, TKey>(this IComparer<T> comparer, Func<T, TKey> keySelector, IComparer<TKey> keyComparer = null)
{
var newComparer = ByKeyComparer<T>.CompareBy(keySelector, keyComparer);
var composite = comparer as CompositeComparer<T>;
if (composite != null)
return new CompositeComparer<T>(composite.Comparers.Concat(new[] { newComparer }));
return new CompositeComparer<T>(comparer, newComparer);
}
}
Example:
var comparer = ByKeyComparer<Patch>.CompareBy(p => p.Key1)
.ThenBy(p => p.Key2)
.ThenBy(p => p.Key3);
patches.Sort(comparer);
(obviously you might want to add *Descending versions of the CompareBy and ThenBy methods to allow ordering in descending order)
If you can use LINQ it'll be quite easy to sort classes like this.
Consider you have a List of Patch List<Patch> and you wanna sort it by key2, key1 and key4. What you do is:
List<Patch> patches = new List<Patch>();
patches = GetPatches().ToList().OrderBy(p=>p.Key2).ThenBy(p=>p.Key1).ThenBy(p=>p.Key4).ToList();
That's all. we love linq. :)
First ToList is not needed if function returns list itself.
You also can use LINQ Dynamic Query Library
or have a look at Dynamic LINQ OrderBy
Short question:
Is there a simple way in LINQ to objects to get a distinct list of objects from a list based on a key property on the objects.
Long question:
I am trying to do a Distinct() operation on a list of objects that have a key as one of their properties.
class GalleryImage {
public int Key { get;set; }
public string Caption { get;set; }
public string Filename { get; set; }
public string[] Tags {g et; set; }
}
I have a list of Gallery objects that contain GalleryImage[].
Because of the way the webservice works [sic] I have duplicates of the
GalleryImage object. i thought it would be a simple matter to use Distinct() to get a distinct list.
This is the LINQ query I want to use :
var allImages = Galleries.SelectMany(x => x.Images);
var distinctImages = allImages.Distinct<GalleryImage>(new
EqualityComparer<GalleryImage>((a, b) => a.id == b.id));
The problem is that EqualityComparer is an abstract class.
I dont want to :
implement IEquatable on GalleryImage because it is generated
have to write a separate class to implement IEqualityComparer as shown here
Is there a concrete implementation of EqualityComparer somewhere that I'm missing?
I would have thought there would be an easy way to get 'distinct' objects from a set based on a key.
(There are two solutions here - see the end for the second one):
My MiscUtil library has a ProjectionEqualityComparer class (and two supporting classes to make use of type inference).
Here's an example of using it:
EqualityComparer<GalleryImage> comparer =
ProjectionEqualityComparer<GalleryImage>.Create(x => x.id);
Here's the code (comments removed)
// Helper class for construction
public static class ProjectionEqualityComparer
{
public static ProjectionEqualityComparer<TSource, TKey>
Create<TSource, TKey>(Func<TSource, TKey> projection)
{
return new ProjectionEqualityComparer<TSource, TKey>(projection);
}
public static ProjectionEqualityComparer<TSource, TKey>
Create<TSource, TKey> (TSource ignored,
Func<TSource, TKey> projection)
{
return new ProjectionEqualityComparer<TSource, TKey>(projection);
}
}
public static class ProjectionEqualityComparer<TSource>
{
public static ProjectionEqualityComparer<TSource, TKey>
Create<TKey>(Func<TSource, TKey> projection)
{
return new ProjectionEqualityComparer<TSource, TKey>(projection);
}
}
public class ProjectionEqualityComparer<TSource, TKey>
: IEqualityComparer<TSource>
{
readonly Func<TSource, TKey> projection;
readonly IEqualityComparer<TKey> comparer;
public ProjectionEqualityComparer(Func<TSource, TKey> projection)
: this(projection, null)
{
}
public ProjectionEqualityComparer(
Func<TSource, TKey> projection,
IEqualityComparer<TKey> comparer)
{
projection.ThrowIfNull("projection");
this.comparer = comparer ?? EqualityComparer<TKey>.Default;
this.projection = projection;
}
public bool Equals(TSource x, TSource y)
{
if (x == null && y == null)
{
return true;
}
if (x == null || y == null)
{
return false;
}
return comparer.Equals(projection(x), projection(y));
}
public int GetHashCode(TSource obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
return comparer.GetHashCode(projection(obj));
}
}
Second solution
To do this just for Distinct, you can use the DistinctBy extension in MoreLINQ:
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector)
{
return source.DistinctBy(keySelector, null);
}
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer)
{
source.ThrowIfNull("source");
keySelector.ThrowIfNull("keySelector");
return DistinctByImpl(source, keySelector, comparer);
}
private static IEnumerable<TSource> DistinctByImpl<TSource, TKey>
(IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer)
{
HashSet<TKey> knownKeys = new HashSet<TKey>(comparer);
foreach (TSource element in source)
{
if (knownKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
In both cases, ThrowIfNull looks like this:
public static void ThrowIfNull<T>(this T data, string name) where T : class
{
if (data == null)
{
throw new ArgumentNullException(name);
}
}
Building on Charlie Flowers' answer, you can create your own extension method to do what you want which internally uses grouping:
public static IEnumerable<T> Distinct<T, U>(
this IEnumerable<T> seq, Func<T, U> getKey)
{
return
from item in seq
group item by getKey(item) into gp
select gp.First();
}
You could also create a generic class deriving from EqualityComparer, but it sounds like you'd like to avoid this:
public class KeyEqualityComparer<T,U> : IEqualityComparer<T>
{
private Func<T,U> GetKey { get; set; }
public KeyEqualityComparer(Func<T,U> getKey) {
GetKey = getKey;
}
public bool Equals(T x, T y)
{
return GetKey(x).Equals(GetKey(y));
}
public int GetHashCode(T obj)
{
return GetKey(obj).GetHashCode();
}
}
This is the best i can come up with for the problem in hand.
Still curious whether theres a nice way to create a EqualityComparer on the fly though.
Galleries.SelectMany(x => x.Images).ToLookup(x => x.id).Select(x => x.First());
Create lookup table and take 'top' from each one
Note: this is the same as #charlie suggested but using ILookup - which i think is what a group must be anyway.
What about a throw away IEqualityComparer generic class?
public class ThrowAwayEqualityComparer<T> : IEqualityComparer<T>
{
Func<T, T, bool> comparer;
public ThrowAwayEqualityComparer(Func<T, T, bool> comparer)
{
this.comparer = comparer;
}
public bool Equals(T a, T b)
{
return comparer(a, b);
}
public int GetHashCode(T a)
{
return a.GetHashCode();
}
}
So now you can use Distinct with a custom comparer.
var distinctImages = allImages.Distinct(
new ThrowAwayEqualityComparer<GalleryImage>((a, b) => a.Key == b.Key));
You might be able to get away with the <GalleryImage>, but I'm not sure if the compiler could infer the type (don't have access to it right now.)
And in an additional extension method:
public static class IEnumerableExtensions
{
public static IEnumerable<TValue> Distinct<TValue>(this IEnumerable<TValue> #this, Func<TValue, TValue, bool> comparer)
{
return #this.Distinct(new ThrowAwayEqualityComparer<TValue>(comparer);
}
private class ThrowAwayEqualityComparer...
}
You could group by the key value and then select the top item from each group. Would that work for you?
This idea is being debated here, and while I'm hoping the .NET Core team adopt a method to generate IEqualityComparer<T>s from lambda, I'd suggest you to please vote and comment on that idea, and use the following:
Usage:
IEqualityComparer<Contact> comp1 = EqualityComparerImpl<Contact>.Create(c => c.Name);
var comp2 = EqualityComparerImpl<Contact>.Create(c => c.Name, c => c.Age);
class Contact { public Name { get; set; } public Age { get; set; } }
Code:
public class EqualityComparerImpl<T> : IEqualityComparer<T>
{
public static EqualityComparerImpl<T> Create(
params Expression<Func<T, object>>[] properties) =>
new EqualityComparerImpl<T>(properties);
PropertyInfo[] _properties;
EqualityComparerImpl(Expression<Func<T, object>>[] properties)
{
if (properties == null)
throw new ArgumentNullException(nameof(properties));
if (properties.Length == 0)
throw new ArgumentOutOfRangeException(nameof(properties));
var length = properties.Length;
var extractions = new PropertyInfo[length];
for (int i = 0; i < length; i++)
{
var property = properties[i];
extractions[i] = ExtractProperty(property);
}
_properties = extractions;
}
public bool Equals(T x, T y)
{
if (ReferenceEquals(x, y))
//covers both are null
return true;
if (x == null || y == null)
return false;
var len = _properties.Length;
for (int i = 0; i < _properties.Length; i++)
{
var property = _properties[i];
if (!Equals(property.GetValue(x), property.GetValue(y)))
return false;
}
return true;
}
public int GetHashCode(T obj)
{
if (obj == null)
return 0;
var hashes = _properties
.Select(pi => pi.GetValue(obj)?.GetHashCode() ?? 0).ToArray();
return Combine(hashes);
}
static int Combine(int[] hashes)
{
int result = 0;
foreach (var hash in hashes)
{
uint rol5 = ((uint)result << 5) | ((uint)result >> 27);
result = ((int)rol5 + result) ^ hash;
}
return result;
}
static PropertyInfo ExtractProperty(Expression<Func<T, object>> property)
{
if (property.NodeType != ExpressionType.Lambda)
throwEx();
var body = property.Body;
if (body.NodeType == ExpressionType.Convert)
if (body is UnaryExpression unary)
body = unary.Operand;
else
throwEx();
if (!(body is MemberExpression member))
throwEx();
if (!(member.Member is PropertyInfo pi))
throwEx();
return pi;
void throwEx() =>
throw new NotSupportedException($"The expression '{property}' isn't supported.");
}
}
Here's an interesting article that extends LINQ for this purpose...
http://www.singingeels.com/Articles/Extending_LINQ__Specifying_a_Property_in_the_Distinct_Function.aspx
The default Distinct compares objects based on their hashcode - to easily make your objects work with Distinct, you could override the GetHashcode method.. but you mentioned that you are retrieving your objects from a web service, so you may not be able to do that in this case.
implement IEquatable on GalleryImage because it is generated
A different approach would be to generate GalleryImage as a partial class, and then have another file with the inheritance and IEquatable, Equals, GetHash implementation.
I want to get the difference between two sets of ints in c#. Given s1 and s2 I want to return those ints which are in s1 and not in s2. I can do something such as:
List<int> s1 = new List<int>();
List<int> s2 = new List<int>();
foreach (int i in s1)
{
if (s1.Contains(i))
{
//
}
else
{
//
}
}
But I was wondering if anyone can point out anything cleaner. I would like to do something such as
List<int> omitted = s1.Difference(s2);
Not sure if there is an existing method or a LINQ construct that anyone might be able to point out? Thank you.
I think you want HashSet.Except. That is, rather than use Lists, use HashSets, and then the operation is available. This is a better type if what you are representing is really a 'set' anyway. (If you already have a list, you can just create a 'new HashSet' out of it.)
IEnumerable<T> a, b;
var added = a.Except(b);
var removed = b.Except(a);
Another useful API, get the symmetric difference:
HashSet.SymmetricExceptWith()
List<int> s1 = new List<int>();
List<int> s2 = new List<int>();
return sl.FindAll( i => !s2.Contains(i) )
from x in s1
where ! s2.contains(x)
select x
Here are two extension methods that might come handy when you need to find unordered differences between two IEnumerable (it's more or less the same as the answer given by leppie wrapper into extension methods):
public class EnumerableDifferences<T>
{
public IEnumerable<T> Added { get; }
public IEnumerable<T> Removed { get; }
public EnumerableDifferences(IEnumerable<T> added, IEnumerable<T> removed)
{
Added = added;
Removed = removed;
}
}
public static class EnumerableExtensions
{
public static HashSet<TSource> ToHashSet<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
{
return new HashSet<TSource>(source, comparer);
}
public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> keyComparer = null)
{
return first
.ExceptBy(keySelector, second.Select(keySelector), keyComparer);
}
public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEnumerable<TKey> keys, IEqualityComparer<TKey> keyComparer = null)
{
var secondKeys = keys.ToHashSet(keyComparer);
foreach (var firstItem in source)
{
var firstItemKey = keySelector(firstItem);
if (!secondKeys.Contains(firstItemKey))
{
yield return firstItem;
}
}
}
public static EnumerableDifferences<TSource> DifferencesBy<TSource, TKey>(this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> keyComparer = null)
{
keyComparer = keyComparer ?? EqualityComparer<TKey>.Default;
var removed = first.ExceptBy(second, keySelector, keyComparer);
var added = second.ExceptBy(first, keySelector, keyComparer);
var result = new EnumerableDifferences<TSource>(added, removed);
return result;
}
public static EnumerableDifferences<TSource> Differences<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer = null)
{
return first
.DifferencesBy(second, x => x, comparer);
}
}
public static class Program
{
public static void Main(params string[] args)
{
var l1 = new[] { 'a', 'b', 'c' };
var l2 = new[] { 'a', 'd', 'c' };
var result = l1.Differences(l2);
Console.ReadKey();
}
}