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
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 there a way of creating a SortedDictionary using Linq? This would avoid the inconvenience (and code bloat) of creating a comparer class.
For example, create a dictionary sorted on the reverse of the string key:
//NOT VALID SYNTAX
SortedDictionary<string, int> sortDict = new SortedDictionary(kvp => new String(kvp.Key.Reverse().ToArray());
//VALID SYNTAX
SortedDictionary<string, int> sortDict = new SortedDictionary<string, int>(new ReverseStringComparer);
private class ReverseStringComparer: IComparer<String>
{
public int Compare(string x, string y)
{
string s1 = new string(x.Reverse().ToArray());
string s2 = new string(y.Reverse().ToArray());
return s1.CompareTo(s2);
}
}
You can define a generic comparer class that applies an extraction function over the items you want to compare:
public class KeyComparer<TItem, TKey> : Comparer<TItem>
{
private readonly Func<TItem, TKey> extract;
private readonly IComparer<TKey> comparer;
public KeyComparer(Func<TItem, TKey> extract)
: this(extract, Comparer<TKey>.Default)
{ }
public KeyComparer(Func<TItem, TKey> extract, IComparer<TKey> comparer)
{
this.extract = extract;
this.comparer = comparer;
}
public override int Compare(TItem x, TItem y)
{
// need to handle nulls
TKey xKey = extract(x);
TKey yKey = extract(y);
return comparer.Compare(xKey, yKey);
}
}
I usually use this class for extracting properties; however, you could define any function, such as your string reversal:
SortedDictionary<string, int> sortDict = new SortedDictionary<string, int>(
new KeyComparer<string, string>(s => new string(s.Reverse().ToArray())));
Update: I have covered this comparer in more detail in my blog post.
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.
Right, so I have an enumerable and wish to get distinct values from it.
Using System.Linq, there's, of course, an extension method called Distinct. In the simple case, it can be used with no parameters, like:
var distinctValues = myStringList.Distinct();
Well and good, but if I have an enumerable of objects for which I need to specify equality, the only available overload is:
var distinctValues = myCustomerList.Distinct(someEqualityComparer);
The equality comparer argument must be an instance of IEqualityComparer<T>. I can do this, of course, but it's somewhat verbose and, well, cludgy.
What I would have expected is an overload that would take a lambda, say a Func<T, T, bool>:
var distinctValues = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);
Anyone know if some such extension exists, or some equivalent workaround? Or am I missing something?
Alternatively, is there a way of specifying an IEqualityComparer inline (embarrass me)?
Update
I found a reply by Anders Hejlsberg to a post in an MSDN forum on this subject. He says:
The problem you're going to run into is that when two objects compare
equal they must have the same GetHashCode return value (or else the
hash table used internally by Distinct will not function correctly).
We use IEqualityComparer because it packages compatible
implementations of Equals and GetHashCode into a single interface.
I suppose that makes sense.
IEnumerable<Customer> filteredList = originalList
.GroupBy(customer => customer.CustomerId)
.Select(group => group.First());
It looks to me like you want DistinctBy from MoreLINQ. You can then write:
var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);
Here's a cut-down version of DistinctBy (no nullity checking and no option to specify your own key comparer):
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
HashSet<TKey> knownKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (knownKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
To Wrap things up . I think most of the people which came here like me want the simplest solution possible without using any libraries and with best possible performance.
(The accepted group by method for me i think is an overkill in terms of performance. )
Here is a simple extension method using the IEqualityComparer interface which works also for null values.
Usage:
var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();
Extension Method Code
public static class LinqExtensions
{
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
{
GeneralPropertyComparer<T, TKey> comparer = new GeneralPropertyComparer<T,TKey>(property);
return items.Distinct(comparer);
}
}
public class GeneralPropertyComparer<T,TKey> : IEqualityComparer<T>
{
private Func<T, TKey> expr { get; set; }
public GeneralPropertyComparer (Func<T, TKey> expr)
{
this.expr = expr;
}
public bool Equals(T left, T right)
{
var leftProp = expr.Invoke(left);
var rightProp = expr.Invoke(right);
if (leftProp == null && rightProp == null)
return true;
else if (leftProp == null ^ rightProp == null)
return false;
else
return leftProp.Equals(rightProp);
}
public int GetHashCode(T obj)
{
var prop = expr.Invoke(obj);
return (prop==null)? 0:prop.GetHashCode();
}
}
Shorthand solution
myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());
No there is no such extension method overload for this. I've found this frustrating myself in the past and as such I usually write a helper class to deal with this problem. The goal is to convert a Func<T,T,bool> to IEqualityComparer<T,T>.
Example
public class EqualityFactory {
private sealed class Impl<T> : IEqualityComparer<T,T> {
private Func<T,T,bool> m_del;
private IEqualityComparer<T> m_comp;
public Impl(Func<T,T,bool> del) {
m_del = del;
m_comp = EqualityComparer<T>.Default;
}
public bool Equals(T left, T right) {
return m_del(left, right);
}
public int GetHashCode(T value) {
return m_comp.GetHashCode(value);
}
}
public static IEqualityComparer<T,T> Create<T>(Func<T,T,bool> del) {
return new Impl<T>(del);
}
}
This allows you to write the following
var distinctValues = myCustomerList
.Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));
Here's a simple extension method that does what I need...
public static class EnumerableExtensions
{
public static IEnumerable<TKey> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
{
return source.GroupBy(selector).Select(x => x.Key);
}
}
It's a shame they didn't bake a distinct method like this into the framework, but hey ho.
This will do what you want but I don't know about performance:
var distinctValues =
from cust in myCustomerList
group cust by cust.CustomerId
into gcust
select gcust.First();
At least it's not verbose.
From .NET 6 or later, there is a new build-in method Enumerable.DistinctBy to achieve this.
var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);
// With IEqualityComparer
var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId, someEqualityComparer);
Something I have used which worked well for me.
/// <summary>
/// A class to wrap the IEqualityComparer interface into matching functions for simple implementation
/// </summary>
/// <typeparam name="T">The type of object to be compared</typeparam>
public class MyIEqualityComparer<T> : IEqualityComparer<T>
{
/// <summary>
/// Create a new comparer based on the given Equals and GetHashCode methods
/// </summary>
/// <param name="equals">The method to compute equals of two T instances</param>
/// <param name="getHashCode">The method to compute a hashcode for a T instance</param>
public MyIEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
{
if (equals == null)
throw new ArgumentNullException("equals", "Equals parameter is required for all MyIEqualityComparer instances");
EqualsMethod = equals;
GetHashCodeMethod = getHashCode;
}
/// <summary>
/// Gets the method used to compute equals
/// </summary>
public Func<T, T, bool> EqualsMethod { get; private set; }
/// <summary>
/// Gets the method used to compute a hash code
/// </summary>
public Func<T, int> GetHashCodeMethod { get; private set; }
bool IEqualityComparer<T>.Equals(T x, T y)
{
return EqualsMethod(x, y);
}
int IEqualityComparer<T>.GetHashCode(T obj)
{
if (GetHashCodeMethod == null)
return obj.GetHashCode();
return GetHashCodeMethod(obj);
}
}
All solutions I've seen here rely on selecting an already comparable field. If one needs to compare in a different way, though, this solution here seems to work generally, for something like:
somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()
Take another way:
var distinctValues = myCustomerList.
Select(x => x._myCaustomerProperty).Distinct();
The sequence return distinct elements compare them by property '_myCaustomerProperty' .
You can use LambdaEqualityComparer:
var distinctValues
= myCustomerList.Distinct(new LambdaEqualityComparer<OurType>((c1, c2) => c1.CustomerId == c2.CustomerId));
public class LambdaEqualityComparer<T> : IEqualityComparer<T>
{
public LambdaEqualityComparer(Func<T, T, bool> equalsFunction)
{
_equalsFunction = equalsFunction;
}
public bool Equals(T x, T y)
{
return _equalsFunction(x, y);
}
public int GetHashCode(T obj)
{
return obj.GetHashCode();
}
private readonly Func<T, T, bool> _equalsFunction;
}
You can use InlineComparer
public class InlineComparer<T> : IEqualityComparer<T>
{
//private readonly Func<T, T, bool> equalsMethod;
//private readonly Func<T, int> getHashCodeMethod;
public Func<T, T, bool> EqualsMethod { get; private set; }
public Func<T, int> GetHashCodeMethod { get; private set; }
public InlineComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
{
if (equals == null) throw new ArgumentNullException("equals", "Equals parameter is required for all InlineComparer instances");
EqualsMethod = equals;
GetHashCodeMethod = hashCode;
}
public bool Equals(T x, T y)
{
return EqualsMethod(x, y);
}
public int GetHashCode(T obj)
{
if (GetHashCodeMethod == null) return obj.GetHashCode();
return GetHashCodeMethod(obj);
}
}
Usage sample:
var comparer = new InlineComparer<DetalleLog>((i1, i2) => i1.PeticionEV == i2.PeticionEV && i1.Etiqueta == i2.Etiqueta, i => i.PeticionEV.GetHashCode() + i.Etiqueta.GetHashCode());
var peticionesEV = listaLogs.Distinct(comparer).ToList();
Assert.IsNotNull(peticionesEV);
Assert.AreNotEqual(0, peticionesEV.Count);
Source:
https://stackoverflow.com/a/5969691/206730
Using IEqualityComparer for Union
Can I specify my explicit type comparator inline?
If Distinct() doesn't produce unique results, try this one:
var filteredWC = tblWorkCenter.GroupBy(cc => cc.WCID_I).Select(grp => grp.First()).Select(cc => new Model.WorkCenter { WCID = cc.WCID_I }).OrderBy(cc => cc.WCID);
ObservableCollection<Model.WorkCenter> WorkCenter = new ObservableCollection<Model.WorkCenter>(filteredWC);
A tricky way to do this is use Aggregate() extension, using a dictionary as accumulator with the key-property values as keys:
var customers = new List<Customer>();
var distincts = customers.Aggregate(new Dictionary<int, Customer>(),
(d, e) => { d[e.CustomerId] = e; return d; },
d => d.Values);
And a GroupBy-style solution is using ToLookup():
var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());
IEnumerable lambda extension:
public static class ListExtensions
{
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, int> hashCode)
{
Dictionary<int, T> hashCodeDic = new Dictionary<int, T>();
list.ToList().ForEach(t =>
{
var key = hashCode(t);
if (!hashCodeDic.ContainsKey(key))
hashCodeDic.Add(key, t);
});
return hashCodeDic.Select(kvp => kvp.Value);
}
}
Usage:
class Employee
{
public string Name { get; set; }
public int EmployeeID { get; set; }
}
//Add 5 employees to List
List<Employee> lst = new List<Employee>();
Employee e = new Employee { Name = "Shantanu", EmployeeID = 123456 };
lst.Add(e);
lst.Add(e);
Employee e1 = new Employee { Name = "Adam Warren", EmployeeID = 823456 };
lst.Add(e1);
//Add a space in the Name
Employee e2 = new Employee { Name = "Adam Warren", EmployeeID = 823456 };
lst.Add(e2);
//Name is different case
Employee e3 = new Employee { Name = "adam warren", EmployeeID = 823456 };
lst.Add(e3);
//Distinct (without IEqalityComparer<T>) - Returns 4 employees
var lstDistinct1 = lst.Distinct();
//Lambda Extension - Return 2 employees
var lstDistinct = lst.Distinct(employee => employee.EmployeeID.GetHashCode() ^ employee.Name.ToUpper().Replace(" ", "").GetHashCode());
The Microsoft System.Interactive package has a version of Distinct that takes a key selector lambda. This is effectively the same as Jon Skeet's solution, but it may be helpful for people to know, and to check out the rest of the library.
Here's how you can do it:
public static class Extensions
{
public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
Func<T, V> f,
Func<IGrouping<V,T>,T> h=null)
{
if (h==null) h=(x => x.First());
return query.GroupBy(f).Select(h);
}
}
This method allows you to use it by specifying one parameter like .MyDistinct(d => d.Name), but it also allows you to specify a having condition as a second parameter like so:
var myQuery = (from x in _myObject select x).MyDistinct(d => d.Name,
x => x.FirstOrDefault(y=>y.Name.Contains("1") || y.Name.Contains("2"))
);
N.B. This would also allow you to specify other functions like for example .LastOrDefault(...) as well.
If you want to expose just the condition, you can have it even simpler by implementing it as:
public static IEnumerable<T> MyDistinct2<T, V>(this IEnumerable<T> query,
Func<T, V> f,
Func<T,bool> h=null
)
{
if (h == null) h = (y => true);
return query.GroupBy(f).Select(x=>x.FirstOrDefault(h));
}
In this case, the query would just look like:
var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name,
y => y.Name.Contains("1") || y.Name.Contains("2")
);
N.B. Here, the expression is simpler, but note .MyDistinct2 uses .FirstOrDefault(...) implicitly.
Note: The examples above are using the following demo class
class MyObject
{
public string Name;
public string Code;
}
private MyObject[] _myObject = {
new MyObject() { Name = "Test1", Code = "T"},
new MyObject() { Name = "Test2", Code = "Q"},
new MyObject() { Name = "Test2", Code = "T"},
new MyObject() { Name = "Test5", Code = "Q"}
};
I'm assuming you have an IEnumerable<T>, and in your example delegate, you would like c1 and c2 to be referring to two elements in this list?
I believe you could achieve this with a self join:
var distinctResults = from c1 in myList
join c2 in myList on <your equality conditions>
I found this as the easiest solution.
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
return source.GroupBy(keySelector).Select(x => x.FirstOrDefault());
}
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.