简体   繁体   English

创建 ILookup

[英]Creating ILookups

I've got an ILookup generated by some complicated expression.我有一个由一些复杂表达式生成的 ILookup。 Let's say it's a lookup of people by last name.假设这是按姓氏查找人员。 (In our simplistic world model, last names are unique by family) (在我们简单的世界中 model,姓氏在家庭中是唯一的)

ILookup<string, Person> families;

Now I've got two queries I'm interested in how to build.现在我有两个对如何构建感兴趣的查询。

First, how would I filter by last name?首先,我将如何按姓氏过滤?

var germanFamilies = families.Where(family => IsNameGerman(family.Key));

But here, germanFamilies is an IEnumerable<IGrouping<string, Person>> ;但是在这里, germanFamilies是一个IEnumerable<IGrouping<string, Person>> if I call ToLookup() on it, I'd best bet would get an IGrouping<string, IGrouping<string, Person>> .如果我对其调用ToLookup() ,我最好打赌会得到一个IGrouping<string, IGrouping<string, Person>> If I try to be smart and call SelectMany first I'd end up with the computer doing a lot of unnecessary work.如果我尝试变得聪明并首先调用SelectMany ,我最终会看到计算机做了很多不必要的工作。 How would you convert this enumeration into a lookup easily?您如何轻松地将此枚举转换为查找?

Second, I'd like to get a lookups of adults only.其次,我只想查找成年人。

var adults = families.Select(family =>
         new Grouping(family.Key, family.Select(person =>
               person.IsAdult())));

Here I'm faced with two problems: the Grouping type doesn't exist (except as an internal inner class of Lookup ), and even if it did we'd have the problem discussed above.在这里,我面临两个问题: Grouping类型不存在(除了Lookup的内部内部 class),即使存在,我们也会遇到上面讨论的问题。

So, apart from implementing the ILookup and IGrouping interfaces completely, or make the computer do silly amounts of work (regrouping what has already been grouped), is there a way to alter existing ILookups to generate new ones that I missed?那么,除了完全实现 ILookup 和 IGrouping 接口,或者让计算机做大量愚蠢的工作(重新组合已经分组的内容)之外,是否有办法改变现有的 ILookups 以生成我错过的新的?

(I'm going to assume you actually wanted to filter by last name, given your query.) (我假设您实际上想要根据您的查询按姓氏过滤。)

You can't modify any implementation of ILookup<T> that I'm aware of. 您无法修改我所知道的ILookup<T>任何实现。 It's certainly possible to implement ToLookup with an immutable lookup , as you're clearly aware :) 使用不可变查找实现ToLookup当然是可能的,因为你清楚地意识到:)

What you could do, however, is to change to use a Dictionary<string, List<Person>> : 但是,你可以做的是改为使用Dictionary<string, List<Person>>

var germanFamilies = families.Where(family => IsNameGerman(family.Key))
                             .ToDictionary(family => family.Key,
                                           family.ToList());

That approach also works for your second query: 这种方法也适用于您的第二个查询:

var adults = families.ToDictionary(family => family.Key,
                                   family.Where(person => persion.IsAdult)
                                         .ToList());

While that's still doing a bit more work than we might think necessary, it's not too bad. 虽然这仍然做的比我们想象的必要多做一些工作,这不是太糟糕了。

EDIT: The discussion with Ani in the comments is worth reading. 编辑:在评论中与Ani的讨论值得一读。 Basically, we're already going to be iterating over every person anyway - so if we assume O(1) dictionary lookup and insertion, we're actually no better in terms of time-complexity using the existing lookup than flattening: 基本上,我们已经要对每个人进行迭代了 - 所以如果我们假设O(1)字典查找和插入,那么使用现有的查找而不是扁平化,我们在时间复杂度方面实际上并没有更好:

var adults = families.SelectMany(x => x)
                     .Where(person => person.IsAdult)
                     .ToLookup(x => x.LastName);

In the first case, we could potentially use the existing grouping, like this: 在第一种情况下,我们可能会使用现有的分组,如下所示:

// We'll have an IDictionary<string, IGrouping<string, Person>>
var germanFamilies = families.Where(family => IsNameGerman(family.Key))
                             .ToDictionary(family => family.Key);

That is then potentially much more efficient (if we have many people in each family) but means we're using groupings "out of context". 那么这可能会更有效率(如果我们每个家庭中有很多人),但意味着我们正在使用“脱离背景”的分组。 I believe that's actually okay, but it leaves a slightly odd taste in my mouth, for some reason. 我相信这实际上还可以,但由于某种原因,它在我的嘴里留下了一点点奇怪的味道。 As ToLookup materializes the query, it's hard to see how it could actually go wrong though... 由于ToLookup实现了查询,因此很难看出它实际上是如何出错的......

For your first query, what about implementing your own FilteredLookup able to take advantage of coming from another ILookup ? 对于您的第一个查询,如何实现您自己的FilteredLookup能够利用来自另一个ILookup优势?
(thank to Jon Skeet for the hint) (感谢Jon Skeet提示)

public static ILookup<TKey, TElement> ToFilteredLookup<TKey, TElement>(this ILookup<TKey, TElement> lookup, Func<IGrouping<TKey, TElement>, bool> filter)
{
    return new FilteredLookup<TKey, TElement>(lookup, filter);
}

With FilteredLookup class being: 使用FilteredLookup类:

internal sealed class FilteredLookup<TKey, TElement> : ILookup<TKey, TElement>
{
    int count = -1;
    Func<IGrouping<TKey, TElement>, bool> filter;
    ILookup<TKey, TElement> lookup;

    public FilteredLookup(ILookup<TKey, TElement> lookup, Func<IGrouping<TKey, TElement>, bool> filter)
    {
        this.filter = filter;
        this.lookup = lookup;
    }

    public bool Contains(TKey key)
    {
        if (this.lookup.Contains(key))
            return this.filter(this.GetGrouping(key));
        return false;
    }

    public int Count
    {
        get
        {
            if (count >= 0)
                return count;
            count = this.lookup.Where(filter).Count();
            return count;
        }
    }

    public IEnumerable<TElement> this[TKey key]
    {
        get
        {
            var grp = this.GetGrouping(key);
            if (!filter(grp))
                throw new KeyNotFoundException();
            return grp;
        }
    }

    public IEnumerator<IGrouping<TKey, TElement>> GetEnumerator()
    {
        return this.lookup.Where(filter).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    private IGrouping<TKey, TElement> GetGrouping(TKey key)
    {
        return new Grouping<TKey, TElement>(key, this.lookup[key]);
    }
}

and Grouping: 和分组:

internal sealed class Grouping<TKey, TElement> : IGrouping<TKey, TElement>
{
    private readonly TKey key;
    private readonly IEnumerable<TElement> elements;

    internal Grouping(TKey key, IEnumerable<TElement> elements)
    {
        this.key = key;
        this.elements = elements;
    }

    public TKey Key { get { return key; } }

    public IEnumerator<TElement> GetEnumerator()
    {
        return elements.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

So basically your first query will be: 所以基本上你的第一个查询将是:

var germanFamilies = families.ToFilteredLookup(family => IsNameGerman(family.Key));

This allows you to avoid re-flattening-filtering-ToLookup, or creating a new dictionary (and so hashing keys again). 这允许您避免重新展平 - 过滤 - ToLookup,或创建新词典(以及再次使用哈希键)。

For the second query the idea will be similar, you should just create a similar class not filtering for the whole IGrouping but for the elements of the IGrouping . 对于第二个查询的想法会是相似的,你应该建立一个类似的类不过滤整个IGrouping但对于的元素IGrouping

Just an idea, maybe it could not be faster than other methods :) 只是一个想法,也许它不会比其他方法更快:)

The Lookup creates an index with a Key type and a value type generic indexer. Lookup 创建一个具有 Key 类型和值类型通用索引器的索引。 You can added to a lookup and remove from a lookup by using concat for add and iterrate and removing the key items in a temp list then rebuilding the lookup.您可以添加到查找并从查找中删除,方法是使用 concat 进行添加和迭代,并删除临时列表中的关键项,然后重建查找。 The look up then works like a dictionary by retrieving the value type by a key.然后通过键检索值类型,查找就像字典一样工作。

public async Task TestILookup()
        {

        //Lookup<TKey,TElement>
        List<Product> products = new List<Product>
        {
        new Product {ProductID=1,Name="Kayak",Category="Watersports",Price=275m},
        new Product {ProductID=2,Name="Lifejacket", Category="Watersports",Price=48.95m},
        new Product {ProductID=3,Name="Soccer Ball", Category="Soccer",Price=19.50m},
        new Product {ProductID=4,Name="Corner Flag", Category="Soccer",Price=34.95m}
         };

        //create an indexer
        ILookup<int, Product> lookup = (Lookup<int,Product>) products.ToLookup(p=>p.ProductID,p=>p);

        Product newProduct = new Product { ProductID = 5, Name = "Basketball", Category = "Basketball", Price = 120.15m };

            lookup = lookup.SelectMany(l => l)
                    .Concat(new[] { newProduct })
                    .ToLookup(l => l.ProductID, l=>l);

        foreach (IGrouping<int, Product> packageGroup in lookup)
        {
            // Print the key value of the IGrouping.
            output.WriteLine("ProductID Key {0}",packageGroup.Key);
            // Iterate through each value in the IGrouping and print its value.
            foreach (Product product in packageGroup)
                output.WriteLine("Name {0}", product.Name);
        }

        Assert.Equal(lookup.Count(), 5);

    }

public class Product
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }

output: output:

ProductID Key 1
Name Kayak
ProductID Key 2
Name Lifejacket
ProductID Key 3
Name Soccer Ball
ProductID Key 4
Name Corner Flag
ProductID Key 5
Name Basketball

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM