Skip to content

Instantly share code, notes, and snippets.

@mladenb
Created April 4, 2019 07:28
Show Gist options
  • Save mladenb/b76bcbc4063f138289243fb06d099dda to your computer and use it in GitHub Desktop.
Save mladenb/b76bcbc4063f138289243fb06d099dda to your computer and use it in GitHub Desktop.
.NET Core ExceptBy / IntersectBy
public static IEnumerable<TFirst> ExceptBy<TFirst, TSecond>(this IEnumerable<TFirst> first, Func<TFirst, TSecond> mappingFunc, IEnumerable<TSecond> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
return ExceptByIterator(first, mappingFunc, second, null);
}
public static IEnumerable<TFirst> ExceptBy<TFirst, TSecond>(this IEnumerable<TFirst> first, Func<TFirst, TSecond> mappingFunc, IEnumerable<TSecond> second, IEqualityComparer<TSecond> comparer)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
return ExceptByIterator(first, mappingFunc, second, comparer);
}
private static IEnumerable<TFirst> ExceptByIterator<TFirst, TSecond>(IEnumerable<TFirst> first, Func<TFirst, TSecond> mappingFunc, IEnumerable<TSecond> second, IEqualityComparer<TSecond> comparer)
{
var set = new HashSet<TSecond>(comparer);
foreach (var element in second)
{
set.Add(element);
}
foreach (var element in first)
{
if (!set.Contains(mappingFunc(element))) yield return element;
}
}
public static IEnumerable<TFirst> IntersectBy<TFirst, TSecond>(this IEnumerable<TFirst> first, Func<TFirst, TSecond> mappingFunc, IEnumerable<TSecond> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
return IntersectByIterator(first, mappingFunc, second, null);
}
public static IEnumerable<TFirst> IntersectBy<TFirst, TSecond>(this IEnumerable<TFirst> first, Func<TFirst, TSecond> mappingFunc, IEnumerable<TSecond> second, IEqualityComparer<TSecond> comparer)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
return IntersectByIterator(first, mappingFunc, second, comparer);
}
private static IEnumerable<TFirst> IntersectByIterator<TFirst, TSecond>(IEnumerable<TFirst> first, Func<TFirst, TSecond> mappingFunc, IEnumerable<TSecond> second, IEqualityComparer<TSecond> comparer)
{
var set = new HashSet<TSecond>(comparer);
foreach (var element in second)
{
set.Add(element);
}
foreach (var firstItem in first)
{
if (set.Contains(mappingFunc(firstItem))) yield return firstItem;
}
}
@mladenb
Copy link
Author

mladenb commented Apr 4, 2019

The original Except/Intersect return a collection of unique items, even though their contract doesn't state so (e.g. the return value of those methods isn't a HashSet/Set, but rather IEnumerable), which is probably a result of a poor design decision. Instead, we can use more intuitive implementation, which returns as much of the same elements from the first enumeration as there are, not just a unique one (using Set.Contains).

Further more, mapping function was added in order to help intersect/except collections of different types.

Original source code:
https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Except.cs
https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Intersect.cs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment