使用linq将列表转换为字典,而不用担心重复


163

我有一个Person对象的列表。我想转换为词典,其中键是名和姓(连接),值是Person对象。

问题是我有一些重复的人,因此如果使用以下代码,它会爆炸:

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

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

我知道这听起来很怪异,但现在我真的不在乎重复的名称。如果有多个名称,我只想取一个。无论如何,我可以在上面编写这段代码,使它只使用名称之一,而不会重复吗?


1
重复项(基于密钥),不确定是否要保留还是丢失?保留它们将需要Dictionary<string, List<Person>>(或等效)。
Anthony Pegram

@Anthony Pegram-只想保留其中一个。我将问题更新为更明确
leora

很好,您可以在执行ToDictionary之前使用distinct。但您必须为人员类重写Equals()和GetHashCode()方法,以便CLR知道如何比较人员对象
Sujit.Warrier

@ Sujit.Warrier-您也可以创建一个相等比较器以传递给Distinct
Kyle Delaney

Answers:


70

这是显而易见的非linq解决方案:

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

206
多数民众赞成在2007年:)
leora

3
不能忽略大小写的情况
2010年

是的,大约是在工作时,我们从.net 2.0框架进行更新... @onof很难忽略大小写。只需以大写形式添加所有键。
卡拉

我如何使这种情况不敏感
leora 2010年

11
或者使用StringComparer创建字典,该字典将忽略大小写,如果那是您所需要的,那么添加/检查代码并不关心是否忽略大小写。
Binary Worrier,2010年

423

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;
}

2
+1非常优雅(我会尽快投票-今天不再有投票:))
2010年

6
@LukeH次要说明:您的两个片段不相等:LINQ变体保留第一个元素,非LINQ片段保留最后一个元素?
toong 2013年

4
@toong:是的,绝对值得一提。(尽管在这种情况下,OP似乎并不关心它们最终指向哪个元素。)
LukeH 2013年

1
对于“第一个值”情况:nonLinq解决方案执行两次字典查找,但是Linq执行冗余的对象实例化和迭代。两者都不理想。
SerG

@SerG值得庆幸的是,字典查找通常被认为是O(1)操作,并且影响可忽略不计。
MHollis

42

使用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的解决方案更好,但它也能正常工作。


您确定可行吗?Distinct如何比较您创建的新引用类型?我认为您需要将某种IEqualityComparer传递给Distinct才能按预期进行此工作。
Simon Gillbee 2014年

5
忽略我以前的评论。见stackoverflow.com/questions/543482/...
西蒙Gillbee

如果你想覆盖有明显区别决心退房stackoverflow.com/questions/489258/...
詹姆斯麦克马洪

30

这应该适用于lambda表达式:

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

2
它必须是:personList.Distinct().ToDictionary(i => i.FirstandLastName, i => i);
Gh61

4
仅当Person类的默认IEqualityComparer按名字和姓氏进行比较(忽略大小写)时,这才起作用。否则,编写这样的IEqualityComparer并使用相关的Distinct重载。同样,您的ToDIctionary方法应采用不区分大小写的比较器,以符合OP的要求。

13

您还可以使用ToLookupLINQ函数,然后可以将其与Dictionary互换使用。

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

这实际上将完成LukeH的答案中的GroupBy ,但将给出Dictionary提供的哈希。因此,您可能不需要将其转换为Dictionary,而仅First在需要访问键的值时使用LINQ 函数。


8

您可以创建类似于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; 
    }

在这种情况下,如果重复,则最后一个值获胜。


7

要消除重复项,请实现IEqualityComparer<Person>可在该Distinct()方法中使用的,然后轻松获取字典。鉴于:

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);

2
        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);
        }


2

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

var _people = personList
.GroupBy(p => p.FirstandLastName)
.ToDictionary(g => g.Key, g => g.Select(x=>x));

1
抱歉,请忽略我的评论编辑(我无法找到删除我的评论编辑的位置)。我只想添加有关使用g.First()而不是g.Select(x => x)的建议。
亚历克斯75

1

与大多数其他答案的问题是,他们使用DistinctGroupBy或者ToLookup,它的引擎盖下创建一个额外的字典。同样,ToUpper创建额外的字符串。这就是我所做的,几乎是Microsoft代码的完全相同的副本,但有一个更改:

    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;
    }

因为在索引器上进行设置会导致它添加密钥,所以它不会抛出,并且只会执行一次密钥查找。您也可以给它一个IEqualityComparer例如StringComparer.OrdinalIgnoreCase


0

从Carra的解决方案开始,您还可以将其编写为:

foreach(var person in personList.Where(el => !myDictionary.ContainsKey(el.FirstAndLastName)))
{
    myDictionary.Add(person.FirstAndLastName, person);
}

3
并不是说有人会尝试使用它,但不要尝试使用它。在迭代集合时修改集合是一个坏主意。
kidmosey
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.