Lookup<TKey, TElement>是什么意思?

194
MSDN的解释如下:

Lookup<TKey, TElement>类似于Dictionary<TKey, TValue>,不同之处在于Dictionary<TKey, TValue>将键映射到单个值,而Lookup<TKey, TElement>将键映射到值的集合。

我觉得这个解释并不是特别有用。那么Lookup是用来做什么的?

6个回答

253
它是一个 IGrouping 和字典的混合体。它可以让你通过一个键将项目分组,但是可以通过这个键以有效的方式访问它们(而不仅仅是迭代所有项目,这就是 GroupBy 所能做的)。例如,你可以使用 .NET 类型构建一个按命名空间查找的查找表... 然后非常容易地获取特定命名空间中的所有类型。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;

public class Test
{
    static void Main()
    {
        // Just types covering some different assemblies
        Type[] sampleTypes = new[] { typeof(List<>), typeof(string), 
                                     typeof(Enumerable), typeof(XmlReader) };

        // All the types in those assemblies
        IEnumerable<Type> allTypes = sampleTypes.Select(t => t.Assembly)
                                               .SelectMany(a => a.GetTypes());

        // Grouped by namespace, but indexable
        ILookup<string, Type> lookup = allTypes.ToLookup(t => t.Namespace);

        foreach (Type type in lookup["System"])
        {
            Console.WriteLine("{0}: {1}", 
                              type.FullName, type.Assembly.GetName().Name);
        }
    }
}

通常情况下,我会在正常的代码中使用var来声明大多数变量。


3
如果它拥有两全其美的优点,那为什么还需要使用词典呢? - Kyle Baran
18
因为对于真正的键值对集合来说,每个键只有一个值,所以这样做是没有意义的。 - Jon Skeet
17
Lookup<,>是一个简单的不可变集合(例如没有Add方法),其用途有限。此外,它不是一般意义上的集合,如果对不存在的键进行查找,则会返回空序列而不是异常。这在特定情境下才有意义,例如与linq一起使用。这也解释了为什么Microsoft没有提供公共构造函数给这个类。 - nawfal
1
阅读答案顺序为jwg -> bobbymcr -> jonskeet。 - Soner from The Ottoman Empire
1
小心jwg的回答,@SonerfromTheOttomanEmpire建议阅读。它误解了ILookup,然后给出了误导性的建议。 - ANeves

80

从一个角度来看,Lookup<TKey, TElement> 类似于 Dictionary<TKey, Collection<TElement>>。基本上,通过相同的键可以返回零个或多个元素的列表。

namespace LookupSample
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    class Program
    {
        static void Main(string[] args)
        {
            List<string> names = new List<string>();
            names.Add("Smith");
            names.Add("Stevenson");
            names.Add("Jones");

            ILookup<char, string> namesByInitial = names.ToLookup((n) => n[0]);

            // count the names
            Console.WriteLine("J's: {0}", namesByInitial['J'].Count()); // 1
            Console.WriteLine("S's: {0}", namesByInitial['S'].Count()); // 2
            Console.WriteLine("Z's: {0}", namesByInitial['Z'].Count()); // 0, does not throw
        }
    }
}

2
查找结果中是否可能有零个元素?如何获取它?(据我所知,查找是公开不可变的,我认为 ToLookup 不会有效地发明键。) - Jon Skeet
10
从技术上讲,是的,因为查找操作会对于不存在的键返回一个空集合(我编辑了我的帖子并添加了一个代码示例来说明这一点)。 - bobbymcr
阅读答案顺序为jwg -> bobbymcr -> jonskeet。 - Soner from The Ottoman Empire
非常干净和有帮助的答案,我希望它被选中。 - mins

40
< p >使用< code >Lookup的一种用途是翻转 Dictionary

假设您的电话簿已实现为一个Dictionary,其中许多(唯一)名称作为键,每个名称关联一个电话号码。但具有不同名称的两个人可能共享相同的电话号码。对于不关心两个键对应于相同值的Dictionary来说,这不是问题。

现在你想要一种查找给定电话号码所属者的方法。您可以构建一个Lookup,将所有KeyValuePairs从您的Dictionary以相反的方式添加,以将值作为键,键作为值。现在您可以查询电话号码,并获取拥有该电话号码的所有人的姓名列表。使用相同数据构建Dictionary会丢失数据(或失败,取决于如何执行),因为执行以下操作:

dictionary["555-6593"] = "Dr. Emmett Brown";
dictionary["555-6593"] = "Marty McFly";

这意味着第二个条目覆盖了第一个 - 文档不再列出。

尝试以稍微不同的方式编写相同数据:

dictionary.Add("555-6593", "Dr. Emmett Brown");
dictionary.Add("555-6593", "Marty McFly");

第二行会抛出异常,因为你不能在Dictionary中添加已经存在的键。

[当然,你可能想使用其他单个数据结构来进行双向查找等操作。这个例子意味着每次更改后者,都必须从前者重新生成Lookup。但对于某些数据来说,这可能是正确的解决方案。]


2
答案对于理解概念至关重要。+1。阅读答案的顺序是jwg->bobbymcr->jonskeet。 - Soner from The Ottoman Empire
jwg,你误解了Lookup。Lookup<_, Person>的等价物不是Dictionary<_, Person>,而是Dictionary<_, Bag<Person>>。从一个ILookup<PhoneId, Person>中,myLookup["555-6593"]的结果不是一个人“Dr. Emmett Brown”,而是一组人{ "Dr. Emmett Brown" }!因此,在访问您的等效字典时,您不能执行dictionary.Add("555-6593", "Dr. Emmett Brown");,因为它需要一个列表;您必须获取或创建该列表,然后将其添加到列表中。 - ANeves
也许这样更简单:允许多个人拥有相同电话的Dic<Person, Phone>的反向字典不是像你的答案假设的Dic<Phone, Person>,而是Dic<Phone, Bag<Person>>。而且这个Dic<Phone, Bag<Person>> 确实具有与Lookup<Phone, Person>相同的数据格式和数据关系。 - ANeves

19

我之前没有成功使用过,但以下是我的尝试:

Lookup<TKey, TElement> 的行为与没有唯一约束条件的表上的(关系)数据库索引基本相同。在与其他相同的地方使用它。


6

我想你可以这样解释:假设你正在创建一个数据结构来保存电话簿的内容。你想按姓氏和名字排序。在这里使用字典会很危险,因为许多人可能有相同的姓名。因此,字典始终最多只能映射到单个值。

查找将可能映射到多个值。

Lookup["Smith"]["John"] 将是一个大小为十亿的集合。


你的回答激发了我的后续问题"如何使用多个索引进行ToLookup()?"。我该如何复制这样的多索引查找?您能否使用其他示例或参考来回答它,其中可以使用 Lookup["Smith"]["John"] - Fulproof

3

补充说明:
ToLookup是立即执行,并会将结果缓存到内存中。但是,GroupBy是延迟执行的,不会缓存分组结果,每次调用时都会重新进行分组。
如果您需要重复访问“已分组的固定数据”,则应选择ToLookUp以获取LookUp实例。特别是当数据量大或多次访问数据时,使用GroupBy会导致严重的性能问题-ToLookUp以使用更多内存为代价,缓存的分组结果将为您的代码提供更好的性能。

顺便说一下:LookUp有时可以用作“EasyDictionary”,因为它不会在不存在的键上抛出异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接