linq 创建ILookup

i86rm4rw  于 2022-12-06  发布在  其他
关注(0)|答案(3)|浏览(163)

我有一个由一些复杂的表达式生成的ILookup。假设它是一个按姓氏查找的人。(在我们简单的世界模型中,姓氏按家庭是唯一的)

ILookup<string, Person> families;

现在我有两个查询,我对如何构建感兴趣。

  • 首先,如何按姓氏进行筛选 *
var germanFamilies = families.Where(family => IsNameGerman(family.Key));

但在这里,germanFamilies是一个IEnumerable<IGrouping<string, Person>>;如果我对它调用ToLookup(),我最好得到一个IGrouping<string, IGrouping<string, Person>>。如果我试图聪明地首先调用SelectMany,我将以计算机做许多不必要的工作而告终。如何轻松地将此枚举转换为查找?

  • 第二,我只想找成人 *
var adults = families.Select(family =>
         new Grouping(family.Key, family.Select(person =>
               person.IsAdult())));

这里我面临着两个问题:Grouping类型并不存在(除了作为Lookup的内部类),即使它存在,我们也会遇到上面讨论的问题。
那么,除了完全实现ILookup和IGrouping接口,或者让计算机做大量愚蠢的工作(对已经分组的内容进行重新分组)之外,有没有办法修改现有的ILookup以生成我错过的新的ILookup?

lmvvr0a8

lmvvr0a81#

(我假设您实际上希望根据查询按姓氏进行过滤。)
你不能修改我所知道的ILookup<T>的任何实现。当然,你也清楚地知道,implement ToLookup with an immutable lookup是可以修改的:)
但是,您 * 可以 * 做的是改为使用Dictionary<string, List<Person>>

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

该方法也适用于第二个查询:

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

虽然这仍然比我们认为有必要做的工作多了一点,但也不算太坏。
编辑:评论中与Ani的讨论值得一阅读。基本上,我们已经在每个人身上进行了迭代--所以如果我们假设字典查找和插入的时间复杂度为O(1),那么实际上,使用现有的查找方法在时间复杂度方面并不比扁平化方法好多少:

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

在第一种情况下,我们可能会使用现有的分组,如下所示:

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

这可能会更有效(如果我们每个家庭都有很多人),但这意味着我们在“脱离上下文”地使用分组。我相信这实际上是好的,但出于某种原因,它在我嘴里留下了一点奇怪的味道。由于ToLookup具体化了查询,很难看到它实际上是如何出错的...

qpgpyjmq

qpgpyjmq2#

对于您的第一个查询,如何实现您自己的FilteredLookup,以便能够利用来自另一个ILookup的查询?
(感谢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);
}

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

和分组:

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

因此,基本上您的第一个查询将是:

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

这使您可以避免重新flattening-filtering-ToLookup,或者创建一个新的字典(从而再次散列键)。
对于第二个查询,想法是类似的,您应该只创建一个类似的类,而不是过滤整个IGrouping,而是过滤IGrouping的元素。
只是一个想法,也许它不可能比其他方法更快:)

f8rj6qna

f8rj6qna3#

查阅会建立具有索引键类型和实值类型一般索引子的索引。您可以使用concat进行新增和重复,并移除缓存清单中的索引键项目,然后重建查阅,以新增至查阅或从查阅中移除。然后查阅会像字典一样,依索引键撷取实值类型。

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

输出:

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

相关问题