使用linq转换列表到字典,不用担心重复

我有一个Person对象列表。我想转换为Dictionary,其中键是第一个和最后一个名字(连接),值是Person对象。

问题是我有一些重复的人,所以这爆炸如果我使用以下代码:

private Dictionary<string, Person> _people = new Dictionary<string, Person>();


_people = personList.ToDictionary(
e => e.FirstandLastName,
StringComparer.OrdinalIgnoreCase);

我知道这听起来很奇怪,但我现在真的不关心重复的名字。如果有多个名字,我只想取一个。有没有什么方法可以让上面的代码只取其中一个名字而不会重复?

192352 次浏览

LINQ的解决方案:

// Use the first value in group
var _people = personList
.GroupBy(p => p.FirstandLastName, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);


// Use the last value in group
var _people = personList
.GroupBy(p => p.FirstandLastName, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.Last(), StringComparer.OrdinalIgnoreCase);

如果你更喜欢非linq解决方案,那么你可以这样做:

// Use the first value in list
var _people = new Dictionary<string, Person>(StringComparer.OrdinalIgnoreCase);
foreach (var p in personList)
{
if (!_people.ContainsKey(p.FirstandLastName))
_people[p.FirstandLastName] = p;
}


// Use the last value in list
var _people = new Dictionary<string, Person>(StringComparer.OrdinalIgnoreCase);
foreach (var p in personList)
{
_people[p.FirstandLastName] = p;
}

下面是一个明显的非linq解决方案:

foreach(var person in personList)
{
if(!myDictionary.ContainsKey(person.FirstAndLastName))
myDictionary.Add(person.FirstAndLastName, person);
}

如果你不介意总是添加最后一个,你可以避免这样的双重查找:

foreach(var person in personList)
{
myDictionary[person.FirstAndLastName] = person;
}
要处理消除重复,实现一个可以在Distinct()方法中使用的IEqualityComparer<Person>,然后获取字典将很容易。 鉴于:< / p >
class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x.FirstAndLastName.Equals(y.FirstAndLastName, StringComparison.OrdinalIgnoreCase);
}


public int GetHashCode(Person obj)
{
return obj.FirstAndLastName.ToUpper().GetHashCode();
}
}


class Person
{
public string FirstAndLastName { get; set; }
}

拿出你的字典:

List<Person> people = new List<Person>()
{
new Person() { FirstAndLastName = "Bob Sanders" },
new Person() { FirstAndLastName = "Bob Sanders" },
new Person() { FirstAndLastName = "Jane Thomas" }
};


Dictionary<string, Person> dictionary =
people.Distinct(new PersonComparer()).ToDictionary(p => p.FirstAndLastName, p => p);

使用Distinct()和不分组的linq -解决方案是:

var _people = personList
.Select(item => new { Key = item.Key, FirstAndLastName = item.FirstAndLastName })
.Distinct()
.ToDictionary(item => item.Key, item => item.FirstFirstAndLastName, StringComparer.OrdinalIgnoreCase);

我不知道它是否比LukeH的解决方案更好,但它同样有效。

这应该适用于lambda表达式:

personList.Distinct().ToDictionary(i => i.FirstandLastName, i => i);

您可以创建一个类似于ToDictionary()的扩展方法,区别在于它允许重复。喜欢的东西:

    public static Dictionary<TKey, TElement> SafeToDictionary<TSource, TKey, TElement>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
IEqualityComparer<TKey> comparer = null)
{
var dictionary = new Dictionary<TKey, TElement>(comparer);


if (source == null)
{
return dictionary;
}


foreach (TSource element in source)
{
dictionary[keySelector(element)] = elementSelector(element);
}


return dictionary;
}

在本例中,如果存在重复值,则最后一个值胜出。

你也可以使用ToLookup LINQ函数,然后你几乎可以与Dictionary互换使用。

_people = personList
.ToLookup(e => e.FirstandLastName, StringComparer.OrdinalIgnoreCase);
_people.ToDictionary(kl => kl.Key, kl => kl.First()); // Potentially unnecessary

这实际上是在LukeH的回答中执行GroupBy,但会给出Dictionary提供的哈希值。因此,您可能不需要将其转换为Dictionary,但只要在需要访问键的值时使用LINQ First函数即可。

从Carra的解开始,你也可以把它写成:

foreach(var person in personList.Where(el => !myDictionary.ContainsKey(el.FirstAndLastName)))
{
myDictionary.Add(person.FirstAndLastName, person);
}
        DataTable DT = new DataTable();
DT.Columns.Add("first", typeof(string));
DT.Columns.Add("second", typeof(string));


DT.Rows.Add("ss", "test1");
DT.Rows.Add("sss", "test2");
DT.Rows.Add("sys", "test3");
DT.Rows.Add("ss", "test4");
DT.Rows.Add("ss", "test5");
DT.Rows.Add("sts", "test6");


var dr = DT.AsEnumerable().GroupBy(S => S.Field<string>("first")).Select(S => S.First()).
Select(S => new KeyValuePair<string, string>(S.Field<string>("first"), S.Field<string>("second"))).
ToDictionary(S => S.Key, T => T.Value);


foreach (var item in dr)
{
Console.WriteLine(item.Key + "-" + item.Value);
}

如果我们想要返回字典中的所有Person(而不是只有一个Person),我们可以:

var _people = personList
.GroupBy(p => p.FirstandLastName)
.ToDictionary(g => g.Key, g => g.Select(x=>x));
大多数其他答案的问题是他们使用DistinctGroupByToLookup,这在引子下创建了一个额外的Dictionary。同样ToUpper创建额外的字符串。 这就是我所做的,除了一个变化:

之外,几乎完全复制了微软的代码
    public static Dictionary<TKey, TSource> ToDictionaryIgnoreDup<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer = null) =>
source.ToDictionaryIgnoreDup(keySelector, i => i, comparer);


public static Dictionary<TKey, TElement> ToDictionaryIgnoreDup<TSource, TKey, TElement>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer = null)
{
if (keySelector == null)
throw new ArgumentNullException(nameof(keySelector));
if (elementSelector == null)
throw new ArgumentNullException(nameof(elementSelector));
var d = new Dictionary<TKey, TElement>(comparer ?? EqualityComparer<TKey>.Default);
foreach (var element in source)
d[keySelector(element)] = elementSelector(element);
return d;
}

因为索引器上的set会导致它添加键,所以它不会抛出,也只会进行一次键查找。你也可以给它一个IEqualityComparer,例如StringComparer.OrdinalIgnoreCase

使用LINQ的等价foldLeft功能

persons.Aggregate(new Dictionary<string,Person>(StringComparer.OrdinalIgnoreCase),
(acc, current) => {
acc[current.FirstAndLastName] = current;
return acc;
});