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

Posted

技术标签:

【中文标题】使用 linq 将列表转换为字典,而不用担心重复【英文标题】:Convert list to dictionary using linq and not worrying about duplicates 【发布时间】:2011-03-20 03:23:09 【问题描述】:

我有一个 Person 对象列表。我想转换为字典,其中键是名字和姓氏(串联),值是 Person 对象。

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

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

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

我知道这听起来很奇怪,但我现在并不真正关心重复名称。如果有多个名字,我只想抓住一个。无论如何我可以在上面编写这段代码,所以它只需要其中一个名称并且不会因重复而爆炸?

【问题讨论】:

重复项(基于密钥),我不确定您要保留它们还是丢失它们?保留它们需要Dictionary&lt;string, List&lt;Person&gt;&gt;(或等效项)。 @Anthony Pegram - 只想保留其中一个。我将问题更新为更明确 你可以在执行 ToDictionary 之前使用 distinct。但是您必须重写 person 类的 Equals() 和 GetHashCode() 方法,以便 CLR 知道如何比较 person 对象 @Sujit.Warrier - 你也可以创建一个相等比较器传递给Distinct 【参考方案1】:

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;

【讨论】:

@LukeH 次要注意:您的两个 sn-ps 不等价:LINQ 变体保留第一个元素,非 LINQ sn-p 保留最后一个元素? @toong:这是真的,绝对值得一提。 (尽管在这种情况下,OP 似乎并不关心他们最终会使用哪个元素。) 对于“第一个值”情况:nonLinq 解决方案执行两次字典查找,但 Linq 执行冗余对象实例化和迭代。两者都不理想。 @SerG 幸好字典查找通常被认为是 O(1) 操作,影响可以忽略不计。【参考方案2】:

这是显而易见的非 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;

【讨论】:

是的,是时候从工作中的 .net 2.0 框架进行更新了……@onof 不难忽略大小写。只需将所有键添加为大写即可。 我如何让这个不区分大小写 或者使用 StringComparer 创建字典,它会忽略大小写,如果这是您需要的,那么您的添加/检查代码并不关心您是否忽略大小写。 回答之前的 cmets,大约 10 年后...忽略大小写,我们应该使用 Dictionary.ContainsKey,比 Dictionary.Keys.Contains 快​​得多(O(1) 而不是 O(N )),并使用不区分大小写的字典。【参考方案3】:

使用 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 才能按预期完成这项工作。 忽略我之前的评论。见***.com/questions/543482/… 如果您想覆盖确定的不同程度,请查看***.com/questions/489258/…【参考方案4】:

这应该适用于 lambda 表达式:

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

【讨论】:

必须是:personList.Distinct().ToDictionary(i =&gt; i.FirstandLastName, i =&gt; i); 这仅在 Person 类的默认 IEqualityComparer 按名字和姓氏比较时才有效,忽略大小写。否则编写这样的 IEqualityComparer 并使用相关的 Distinct 重载。此外,您的 ToDIctionary 方法应该采用不区分大小写的比较器来匹配 OP 的要求。【参考方案5】:

您还可以使用ToLookup LINQ 函数,然后您几乎可以将其与字典互换使用。

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

这实际上将在LukeH's answer 中执行 GroupBy,但会提供 Dictionary 提供的散列。因此,您可能不需要将其转换为字典,只需在需要访问键值时使用 LINQ First 函数即可。

【讨论】:

【参考方案6】:

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

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

【讨论】:

我不敢相信这个答案不是投票最多的。 ToLookupDistinct 等都有可怕的性能影响。这是 AFAICT,唯一一个与原始 ToDictionary 执行速度大致相同的选项 没错。其他答案会进行额外的迭代+额外的内存分配【参考方案7】:

要处理消除重复,实现一个IEqualityComparer&lt;Person&gt; 可以在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);

【讨论】:

【参考方案8】:

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

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

【讨论】:

对不起,忽略我的评论编辑(我找不到删除我的评论编辑的地方)。我只是想添加一个关于使用 g.First() 而不是 g.Select(x => x) 的建议。 “StringComparer.OrdinalIgnoreCase”怎么样?您的示例中的 GroupBy 会忽略它。【参考方案9】:
        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);
        

【讨论】:

我建议你阅读Minimal, Complete and verifiable example来改进你的例子。【参考方案10】:

大多数其他答案的问题是他们使用DistinctGroupByToLookup,这会在后台创建一个额外的字典。同样 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

【讨论】:

【参考方案11】:

使用 LINQ 的 foldLeft 等效功能

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

【讨论】:

【参考方案12】:

从 Carra 的解决方案开始,你也可以这样写:

foreach(var person in personList.Where(el => !myDictionary.ContainsKey(el.FirstAndLastName)))

    myDictionary.Add(person.FirstAndLastName, person);

【讨论】:

并不是说任何人都会尝试使用它,但不要尝试使用它。在迭代集合时修改集合是个坏主意。

以上是关于使用 linq 将列表转换为字典,而不用担心重复的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 linq 将平面列表转换为多级查找?

Linq ToDictionary 匿名与具体类型 [重复]

如何在python中使用pandas将字典列表转换为数据框[重复]

Python。将 2 个列表转换为一个字典对象 [重复]

将字典列表转换为数据框 [重复]

将字典列表转换为数据框时处理缺失值[重复]