Distinct () z lambda?
Racja, więc mam liczbę i chcę uzyskać z niej różne wartości.
Używając System.Linq
, istnieje oczywiście metoda rozszerzenia o nazwie Distinct
. W prostym przypadku może być używany bez parametrów, takich jak:
var distinctValues = myStringList.Distinct();
Dobrze i dobrze, ale jeśli mam liczbę obiektów, dla których muszę określić równość, jedynym dostępnym przeciążeniem jest:
var distinctValues = myCustomerList.Distinct(someEqualityComparer);
Argument porównujący równość musi być instancją IEqualityComparer<T>
. Mogę to zrobić, oczywiście, ale jest to nieco gadatliwe i, cóż, cludgy.
Spodziewałbym się przeciążenia, które zajęłoby lambdę, powiedzmy Func
var distinctValues
= myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);
Ktoś wie, czy istnieje jakieś takie rozszerzenie, lub jakieś równoważne obejście? Czy coś przeoczyłem?
Alternatywnie, czy istnieje sposób na określenie Ieequalitycomparer inline (embass me)?
Update
Znalazłem odpowiedź Andersa Hejlsberga na post na forum MSDN na ten temat. Mówi:
To chyba ma sens..Problem, który napotkasz, polega na tym, że gdy dwa obiekty porównują równe muszą mieć taką samą wartość zwracaną GetHashCode (albo tabela hash używana wewnętrznie przez Distinct nie będzie działać poprawnie). Używamy Ieequalitycomparer ponieważ pakuje zgodne implementacje Equals i GetHashCode w jednym interfejsie.
17 answers
IEnumerable<Customer> filteredList = originalList
.GroupBy(customer => customer.CustomerId)
.Select(group => group.First());
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-21 20:24:14
Wygląda na to, że chcesz DistinctBy
z MoreLINQ . Możesz wtedy napisać:
var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);
Oto skrócona wersja DistinctBy
(bez sprawdzania nieważności i bez opcji określania własnego porównywania kluczy):
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;
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-22 11:30:50
Aby zakończyć sprawy . Myślę, że większość ludzi, którzy tu przyszli, tak jak ja, chce najprostszego rozwiązania możliwego bez użycia bibliotek i z najlepszą możliwą wydajnością.
(przyjęta Grupa według metody jest dla mnie przesadą pod względem wydajności. )
Oto prosta metoda rozszerzenia wykorzystująca interfejs IEqualityComparer , który działa również dla null wartości.
Użycie:
var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();
Kod Metody Rozszerzenia
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();
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-09 13:48:08
No nie ma takiego przeciążenia metody rozszerzenia dla tego. Znalazłem to frustrujące siebie w przeszłości i jako takie Zwykle piszę klasę pomocniczą, aby poradzić sobie z tym problemem. Celem jest konwersja Func<T,T,bool>
na IEqualityComparer<T,T>
.
Przykład
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);
}
}
Pozwala to na napisanie następujących
var distinctValues = myCustomerList
.Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-10 19:41:43
Rozwiązanie skrótu
myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-24 19:47:16
To zrobi co chcesz, ale nie wiem o wydajności:
var distinctValues =
from cust in myCustomerList
group cust by cust.CustomerId
into gcust
select gcust.First();
Przynajmniej to nie jest gadatliwe.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-06 14:41:59
Oto prosta metoda rozszerzenia, która robi to, czego potrzebuję...
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);
}
}
Szkoda, że nie wstawili takiej metody do frameworka, ale Hej ho.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-01 20:19:15
Coś, czego używałem, co dobrze dla mnie działało.
/// <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);
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-19 14:15:48
Wszystkie rozwiązania, które tutaj widziałem, polegają na wybraniu już porównywalnego pola. Jeśli jednak trzeba porównać w inny sposób, To rozwiązanie tutaj wydaje się działać ogólnie, dla czegoś takiego jak:
somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-14 15:07:26
Możesz użyć 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);
}
}
Przykład użycia :
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);
Źródło:
https://stackoverflow.com/a/5969691/206730
Korzystanie z Ieequalitycomparer dla Unii
Czy Mogę określić w wierszu mój jawny komparator typu?
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:36
Trudnym sposobem na to jest użycie rozszerzenia Aggregate()
, używając słownika jako akumulatora z key-property wartości jako kluczy:
var customers = new List<Customer>();
var distincts = customers.Aggregate(new Dictionary<int, Customer>(),
(d, e) => { d[e.CustomerId] = e; return d; },
d => d.Values);
I GroupBy-style rozwiązanie jest za pomocą ToLookup()
:
var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-07 21:43:47
Take another way:
var distinctValues = myCustomerList.
Select(x => x._myCaustomerProperty).Distinct();
Sekwencja zwraca różne elementy porównując je według właściwości '_myCaustomerProperty'.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-12 07:56:12
Zakładam, że masz liczbę mnogą, a w twoim przykładzie chciałbyś, aby c1 i c2 odnosiły się do dwóch elementów na tej liście?
Wierzę, że można to osiągnąć poprzez samo dołączenie var distinctResults = from c1 in myList dołącz do c2 w myList na
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-19 14:10:07
Jeśli Distinct()
nie daje unikalnych wyników, spróbuj tego:
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);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-07 16:09:59
System Microsoftu.Pakiet interaktywny ma wersję Distinct, która pobiera klucz selektora lambda. Jest to faktycznie takie samo rozwiązanie jak rozwiązanie Jona Skeeta, ale może być pomocne dla ludzi, aby wiedzieć i sprawdzić resztę biblioteki.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-16 05:38:53
Oto jak możesz to zrobić:
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);
}
}
Ta metoda pozwala na jej użycie poprzez podanie jednego parametru, jak .MyDistinct(d => d.Name)
, ale pozwala również określić warunek having jako drugi parametr, jak tak:
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. Pozwala to również na określenie innych funkcji, takich jak na przykład .LastOrDefault(...)
.
Jeśli chcesz ujawnić tylko warunek, możesz mieć go jeszcze prostsze, implementując go jako:
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));
}
W tym przypadku zapytanie tylko wygląd:
var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name,
y => y.Name.Contains("1") || y.Name.Contains("2")
);
N. B. tutaj wyrażenie jest prostsze, ale uwaga .MyDistinct2
używa .FirstOrDefault(...)
w domyśle.
Uwaga: powyższe przykłady używają następującej klasy demo
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"}
};
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-26 15:16:49
IEnumerable
rozszerzenie lambda:
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);
}
}
Użycie:
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());
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-11 20:14:49