Фильтрация дубликатов из IEnumerable
У меня есть этот код:
class MyObj {
int Id;
string Name;
string Location;
}
IEnumerable<MyObj> list;
Я хочу преобразовать список в словарь следующим образом:
list.ToDictionary(x => x.Name);
но это говорит мне, что у меня есть дубликаты ключей. Как я могу сохранить только первый элемент для каждой клавиши?
Ответы
Ответ 1
Я предполагаю, что самым простым способом было бы группировать по ключу и принимать первый элемент каждой группы:
list.GroupBy(x => x.name).Select(g => g.First()).ToDictionary(x => x.name);
Или вы можете использовать Distinct
, если ваши объекты реализуют IEquatable
для сравнения между собой по ключу:
// I'll just randomly call your object Person for this example.
class Person : IEquatable<Person>
{
public string Name { get; set; }
public bool Equals(Person other)
{
if (other == null)
return false;
return Name == other.Name;
}
public override bool Equals(object obj)
{
return base.Equals(obj as Person);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
...
list.Distinct().ToDictionary(x => x.Name);
Или, если вы не хотите этого делать (возможно, потому, что вы обычно хотите сравнивать для равенства по-другому, поэтому Equals
уже используется) вы можете сделать собственную реализацию IEqualityComparer
только для этого случай:
class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x == null)
return y == null;
if (y == null)
return false;
return x.Name == y.Name;
}
public int GetHashCode(Person obj)
{
return obj.Name.GetHashCode();
}
}
...
list.Distinct(new PersonComparer()).ToDictionary(x => x.Name);
Ответ 2
Вы также можете создать свой собственный метод перераспределения расширенных расширений, который принял Func < > для выбора отдельного ключа:
public static class EnumerationExtensions
{
public static IEnumerable<TSource> Distinct<TSource,TKey>(
this IEnumerable<TSource> source, Func<TSource,TKey> keySelector)
{
KeyComparer comparer = new KeyComparer(keySelector);
return source.Distinct(comparer);
}
private class KeyComparer<TSource,TKey> : IEqualityComparer<TSource>
{
private Func<TSource,TKey> keySelector;
public DelegatedComparer(Func<TSource,TKey> keySelector)
{
this.keySelector = keySelector;
}
bool IEqualityComparer.Equals(TSource a, TSource b)
{
if (a == null && b == null) return true;
if (a == null || b == null) return false;
return keySelector(a) == keySelector(b);
}
int IEqualityComparer.GetHashCode(TSource obj)
{
return keySelector(obj).GetHashCode();
}
}
}
Извинения за неправильное форматирование кода, я хотел уменьшить размер кода на странице. В любом случае, вы можете использовать ToDictionary:
var dictionary = list.Distinct(x => x.Name).ToDictionary(x => x.Name);
Ответ 3
list.Distinct().ToDictionary(x => x.Name);
Ответ 4
Может ли сделать свой собственный, возможно? Например:
public static class Extensions
{
public static IDictionary<TKey, TValue> ToDictionary2<TKey, TValue>(
this IEnumerable<TValue> subjects, Func<TValue, TKey> keySelector)
{
var dictionary = new Dictionary<TKey, TValue>();
foreach(var subject in subjects)
{
var key = keySelector(subject);
if(!dictionary.ContainsKey(key))
dictionary.Add(key, subject);
}
return dictionary;
}
}
var dictionary = list.ToDictionary2(x => x.Name);
Не проверял, но должен работать. (и он должен, вероятно, иметь лучшее имя, чем ToDictionary2: p)
В качестве альтернативы вы можете реализовать метод DistinctBy
, например, например:
public static IEnumerable<TSubject> DistinctBy<TSubject, TValue>(this IEnumerable<TSubject> subjects, Func<TSubject, TValue> valueSelector)
{
var set = new HashSet<TValue>();
foreach(var subject in subjects)
if(set.Add(valueSelector(subject)))
yield return subject;
}
var dictionary = list.DistinctBy(x => x.Name).ToDictionary(x => x.Name);
Ответ 5
Проблема заключается в том, что метод расширения ToDictionary не поддерживает несколько значений с одним и тем же ключом. Одно из решений - написать версию, которая делает и использует это вместо этого.
public static Dictionary<TKey,TValue> ToDictionaryAllowDuplicateKeys<TKey,TValue>(
this IEnumerable<TValue> values,
Func<TValue,TKey> keyFunc) {
var map = new Dictionary<TKey,TValue>();
foreach ( var cur in values ) {
var key = keyFunc(cur);
map[key] = cur;
}
return map;
}
Теперь преобразование в словарь прямолинейно
var map = list.ToDictionaryAllowDuplicateKeys(x => x.Name);
Ответ 6
Следующие действия будут работать, если у вас есть разные экземпляры MyObj с тем же значением для свойства Name. Сначала будет найден первый экземпляр для каждого дубликата (извините за ноту obj - obj2, это всего лишь образец кода):
list.SelectMany(obj => new MyObj[] {list.Where(obj2 => obj2.Name == obj.Name).First()}).Distinct();
EDIT: решение Joren лучше, так как оно не создает ненужных массивов в процессе.