Правильно, поэтому у меня есть перечисляемый и желаю получить от него отличные значения.
Используя System.Linq
, конечно, существует метод расширения, называемый Distinct
. В простом случае его можно использовать без параметров, например:
var distinctValues = myStringList.Distinct();
Ну и хорошо, но если у меня есть перечисляемые объекты, для которых мне нужно указать равенство, единственная доступная перегрузка:
var distinctValues = myCustomerList.Distinct(someEqualityComparer);
Аргумент сопоставления равенства должен быть экземпляром IEqualityComparer<T>
. Я могу это сделать, конечно, но это несколько многословно и, ну, cludgy.
То, что я ожидал бы, это перегрузка, которая будет принимать лямбда, скажем, Func < T, T, bool > :
var distinctValues
= myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);
Кто-нибудь знает, существует ли такое расширение, или какое-то эквивалентное обходное решение? Или я что-то упускаю?
В качестве альтернативы, есть способ указать IEqualityComparer встроенный (embarass me)?
Обновление
Я нашел ответ Андерса Хейлсберга на сообщение на форуме MSDN по этому вопросу. Он говорит:
Проблема, с которой вы столкнетесь, заключается в том, что при сравнении двух объектов они должны иметь одинаковое возвращаемое значение GetHashCode (или хэш-таблица, используемая внутренне с помощью Distinct, будет работать неправильно). Мы используем IEqualityComparer, потому что он совместим с пакетами реализации Equals и GetHashCode в один интерфейс.
Я полагаю, что это имеет смысл.
Ответ 1
IEnumerable<Customer> filteredList = originalList
.GroupBy(customer => customer.CustomerId)
.Select(group => group.First());
Ответ 2
Мне кажется, что вы хотите DistinctBy
из MoreLINQ. Затем вы можете написать:
var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);
Здесь вырезанная версия DistinctBy
(без проверки недействительности и без возможности указать свой собственный сопоставитель ключей):
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;
}
}
}
Ответ 3
Нет такой перегрузки для этого метода расширения. Я обнаружил, что это разочаровывает себя в прошлом, и поэтому я обычно пишу вспомогательный класс для решения этой проблемы. Целью является преобразование a Func<T,T,bool>
в IEqualityComparer<T,T>
.
Пример
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);
}
}
Это позволяет вам написать следующие
var distinctValues = myCustomerList
.Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));
Ответ 4
Обернуть вещи вверх. Я думаю, что большинство людей, которые пришли сюда, как я, хотят, чтобы простейшее решение было без использования каких-либо библиотек и с наилучшей производительностью.
(Принятая группа по методу для меня, я думаю, является излишним с точки зрения производительности.)
Вот простой метод расширения, использующий интерфейс IEqualityComparer, который также работает для нулевых значений.
Применение:
var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();
Код метода расширения
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();
}
}
Ответ 5
Это сделает то, что вы хотите, но я не знаю о производительности:
var distinctValues =
from cust in myCustomerList
group cust by cust.CustomerId
into gcust
select gcust.First();
По крайней мере, это не многословно.
Ответ 6
Сокращенное решение
myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());
Ответ 7
Вот простой метод расширения, который делает то, что мне нужно...
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);
}
}
Стыдно, что они не выпекали в рамку отличный метод, подобный этому, но hey ho.
Ответ 8
Что-то я использовал, который хорошо работал у меня.
/// <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);
}
}
Ответ 9
Все решения, которые я видел здесь, зависят от выбора уже сопоставимого поля. Если нужно сравнить по-другому, тем не менее, это решение здесь, похоже, работает в целом, для чего-то вроде:
somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()
Ответ 10
Вы можете использовать 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);
}
}
Пример использования:
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);
Источник:
fooobar.com/questions/13247/...
Использование IEqualityComparer for Union
Могу ли я указать мой встроенный компаратор встроенного типа?
Ответ 11
Я предполагаю, что у вас есть IEnumerable, и в вашем делете делегата вы хотели бы, чтобы c1 и c2 ссылались на два элемента в этом списке?
Я считаю, что вы могли бы добиться этого с помощью самостоятельного присоединения
var distinctResults = from c1 в myList join c2 в myList на
Ответ 12
Если Distinct()
не дает уникальных результатов, попробуйте следующее:
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);
Ответ 13
Microsoft System.Interactive package имеет версию Distinct, которая принимает ключевой селектор lambda. Это фактически то же самое, что и решение Jon Skeet, но это может быть полезно для людей, чтобы узнать, и проверить остальную библиотеку.
Ответ 14
IEnumerable
расширение лямбда:
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);
}
}
Использование:
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());
Ответ 15
Сложный способ сделать это - использовать расширение Aggregate()
, используя словарь в качестве аккумулятора с ключами-значениями в качестве ключей:
var customers = new List<Customer>();
var distincts = customers.Aggregate(new Dictionary<int, Customer>(),
(d, e) => { d[e.CustomerId] = e; return d; },
d => d.Values);
И решение в стиле GroupBy использует ToLookup()
:
var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());
Ответ 16
Вот как вы можете это сделать:
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);
}
}
Этот метод позволяет вам использовать его, указав один параметр, например .MyDistinct(d => d.Name)
, но также позволяет указать условие наличия в качестве второго параметра следующим образом:
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. Это также позволит вам указать другие функции, например, .LastOrDefault(...)
.
Если вы хотите выставить только это условие, вы можете сделать это еще проще, выполнив его как:
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));
}
В этом случае запрос будет выглядеть так:
var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name,
y => y.Name.Contains("1") || y.Name.Contains("2")
);
N.B. Здесь выражение проще, но примечание .MyDistinct2
использует .FirstOrDefault(...)
неявно.
Примечание. В приведенных выше примерах используется следующий демонстрационный класс
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"}
};
Ответ 17
Иметь другой способ:
var distinctValues = myCustomerList.
Select(x => x._myCaustomerProperty).Distinct();
Последовательность возвращает отдельные элементы, сравнивая их по свойству "_myCaustomerProperty".