简体   繁体   English

LINQ查询以返回每个类别中价格最高的商品

[英]LINQ query to return highest priced item in each category

This is a follow up to a question I posted a short while ago. 这是我不久前发布的一个问题的跟进。 I got an answer, but I realized that I had simplified my example class to the point that I lost the original intent. 我得到了一个答案,但是我意识到我已经简化了示例类,以至于失去了最初的意图。 Having already accepted an answer on the original question I thought it best to start another. 在已经接受了原始问题的答案之后,我认为最好再开始一个问题。

So, here's my new class: 所以,这是我的新课:

public class Art
{
    public string Type { get; set; }
    public string Name { get; set; }
    public int Price { get; set; }
}

...and here's the list creation: ...这是列表的创建:

public static void Example0000()
{
    List<Art> art = new List<Art>();
    art.Add(new Art() { Price = 45, Type = "painting", Name = "Still Life in Maryland" });
    art.Add(new Art() { Price = 123, Type = "sculpture", Name = "Dying Sheep" });
    art.Add(new Art() { Price = 12, Type = "icon", Name = "Perplexed Smiley" });
    art.Add(new Art() { Price = 460, Type = "sculpture", Name = "Waves on Sand" });
    art.Add(new Art() { Price = 2030, Type = "painting", Name = "Robert in the Morning" });
    art.Add(new Art() { Price = 10, Type = "icon", Name = "Smiley Picking Nose" });
    art.Add(new Art() { Price = 700, Type = "painting", Name = "Birds in Autumn" });
    art.Add(new Art() { Price = 1400, Type = "sculpture", Name = "Holding Hands" });
    art.Add(new Art() { Price = 46, Type = "painting", Name = "Reeling Him In" });
    art.Add(new Art() { Price = 12000, Type = "sculpture", Name = "Old Dog" });
    art.Add(new Art() { Price = 6, Type = "icon", Name = "Hiding Smiley" });
    art.Add(new Art() { Price = 810, Type = "sculpture", Name = "Rhinestone Cowgirl" });
    art.Add(new Art() { Price = 250, Type = "painting", Name = "Upstairs, Downstairs" });
    art.Add(new Art() { Price = 3, Type = "icon", Name = "Dopey Smiley" });
    art.Add(new Art() { Price = 1000, Type = "painting", Name = "Young Love" });
    art.Add(new Art() { Price = 260, Type = "sculpture", Name = "Taking a Spill" });
}

What I want to get is a collection of objects, one for each Type, which have three properties; 我想要得到的是对象的集合,每个对象一个,具有三个属性。 ArtType, ArtName and MostExpensivePrice. ArtType,ArtName和MostExpensivePrice。 For each Type, I want the Name and Price for the highest priced item of that type. 对于每种类型,我都希望获得该类型价格最高的商品的名称和价格。

So my list should look like: 所以我的清单应该像这样:

painting______Robert_in_the_Morning______2030 绘画______Robert_in_the_Morning________2030

sculpture_____Old Dog__________________12000 雕塑_____老狗__________________12000

icon_________Perplexed Smiley______________12 icon_________困惑的笑脸______________12

What would the LINQ look like for that? LINQ会是什么样子? The example that I am starting with looks like this: 我开始的示例如下所示:

var categories4 =
    from a in art
    group a by a.Type into g
    let maxPrice = g.Max(p => p.Price)
    select new { ArtType = g.Key, MostExpensive = g.Where(a => a.Price == maxPrice) };

Does this work for you? 这对您有用吗?

var query =
    art
        .OrderByDescending(x => x.Price)
        .GroupBy(x => x.Type)
        .Select(x => x.First());

I get this result: 我得到这个结果:

查询结果

See the Enumerable.Aggregate() method. 请参见Enumerable.Aggregate()方法。

The other answer given so far only returns the maximum price, which isn't what you're asking for here. 到目前为止,给出的另一个答案仅返回最高价格,这不是您在此处要的价格。 If you use Enumerable.Aggregate() like this: 如果像这样使用Enumerable.Aggregate():

MostExpensive = g.Aggregate((art1, art2) => (art1.Price > art2.Price) ? art1 : art2)

Then your LINQ result will have instances of Art instead of just int, so you can display all of the information for the maximum instead of just the price. 然后,您的LINQ结果将具有Art实例,而不仅仅是int实例,因此您可以显示所有信息,而不仅仅是价格。

EDIT: 编辑:

In case it wasn't obvious from the above, the full expression can then be this: 如果从上面看不清楚,则完整表达式可以是这样:

var artprices =
    from a in art
    group a by a.Type into g
    let mostExpensive = g.Aggregate((art1, art2) => (art1.Price > art2.Price) ? art1 : art2)
    select new { ArtType = g.Key, ArtName = mostExpensive.Name, MostExpensivePrice = mostExpensive.Price };

And you'll have a result with elements having the three values you want. 然后,您将获得具有所需三个值的元素的结果。

EDIT 2: 编辑2:

And finally, being new to the site I can't add comments to the other answers, but I'll point out as objectively as I can that they are both deficient, in different ways. 最后,作为新手,我不能在其他答案中添加评论,但我会尽可能客观地指出它们都是不足的,而且方式不同。

One answer suggests evaluating the Max() method once for every element in the original collection, and then again for every Type value (ie each group). 一个答案建议对原始集合中的每个元素分别评估Max()方法,然后对每个Type值(即每个组)再次评估。 This is a classic O(N^2) scenario, which will perform fine for very small data sets but which will be abysmal for any non-trivial collection of data. 这是一个经典的O(N ^ 2)方案,对于非常小的数据集,它的性能很好,但是对于任何非平凡的数据收集,它都会很糟糕。

Two other answers suggest sorting the elements in each individual group. 另外两个答案建议对每个单独的组中的元素进行排序。 This is better, but still requires the memory and performance overhead of the sort. 这样比较好,但是仍然需要那种内存和性能开销。 A typical sort is O(N log N), which is much better than O(N^2), but still not as good as the linear O(N) that using Aggregate() gets you. 一个典型的排序是O(N log N),它比O(N ^ 2)好得多,但仍然不如使用Aggregate()获得的线性O(N)好。 Again, no problem at all with small data sets, but a non-trivial collection will result in a noticeable performance decrease as compared to the more efficient approach. 同样,使用较小的数据集完全没有问题,但与更有效的方法相比,非平凡的收集将导致性能显着下降。

Hope that helps! 希望有帮助!

Well, you got the first part right, you're just a little off with the second part. 好吧,您正确设置了第一部分,而第二部分仅差一点。 The first thing that you need to do is understand what GroupBy method returns. 您需要做的第一件事是了解GroupBy方法返回什么。 GroupBy essentially returns a list of lists (array of arrays or enumerable of enumerables). GroupBy本质上返回一个列表列表(数组的数组或可枚举的枚举)。

Using a type declared as yours is: 使用声明为您的类型的类型是:

public class Art
{
    public string Type { get; set; }
    public string Name { get; set; }
    public int Price { get; set; }
}

With this data: 使用此数据:

List<Art> art = new List<Art>()
{
    new Art() { Price = 45, Type = "painting", Name = "Still Life in Maryland" }),
    new Art() { Price = 123, Type = "sculpture", Name = "Dying Sheep" }),
    new Art() { Price = 12, Type = "icon", Name = "Perplexed Smiley" }),
    new Art() { Price = 460, Type = "sculpture", Name = "Waves on Sand" });,
    new Art() { Price = 2030, Type = "painting", Name = "Robert in the Morning" }),
    new Art() { Price = 10, Type = "icon", Name = "Smiley Picking Nose" }),
    new Art() { Price = 700, Type = "painting", Name = "Birds in Autumn" }),
    new Art() { Price = 1400, Type = "sculpture", Name = "Holding Hands" }),
    new Art() { Price = 46, Type = "painting", Name = "Reeling Him In" }),
    new Art() { Price = 12000, Type = "sculpture", Name = "Old Dog" }),
    new Art() { Price = 6, Type = "icon", Name = "Hiding Smiley" }),
    new Art() { Price = 810, Type = "sculpture", Name = "Rhinestone Cowgirl" }),
    new Art() { Price = 250, Type = "painting", Name = "Upstairs, Downstairs" }),
    new Art() { Price = 3, Type = "icon", Name = "Dopey Smiley" }),
    new Art() { Price = 1000, Type = "painting", Name = "Young Love" }),
    new Art() { Price = 260, Type = "sculpture", Name = "Taking a Spill" })
}

Grouping a list of art objects results in something that looks like this: 将艺术品列表进行分组会导致如下所示:

IEnumerable<IGrouping<string, Art>> groupedByType = art.GroupBy(a => a.Type);

Where each IGrouping<string, Art> contains a list of Art where each piece in a list has the same Type . 其中每个IGrouping<string, Art>包含一个Art列表,其中列表中的每个作品都具有相同的Type Taking this to the second step, we just need to select the maximum price from each group: 进行第二步,我们只需要从每个组中选择最高价格:

IEnumerable<Art> maxFromEachGroup = groupedByType
    // Take a single piece of art from each group
    .Select(group =>
        // Get the maximum piece of art by ordering from largest to smallest 
        // and taking the first
        group.OrderByDescending(a => a.Price).First() 
    );

Now you have a list of Art where the most expensive piece from each group is contained. 现在,您有了一份Art清单,其中包含每个组中最昂贵的Art The thing to be aware of with Max is that it returns the value of the maximum price compared to returning the piece of Art with the largest price. 使用Max需要注意的是,与返回最大价格的Art相比,它返回​​最大价格的值。 As such, the entire expression in LINQ expression form is: 这样,LINQ表达式形式的整个表达式为:

var maxFromEachGroup = from a in art
                       group a by a.Type into g
                       select (from a in g orderby a.Price descending select a).First();

You are almost there. 你快到了。 Remove the let as it's not needed and do a Max on the Price property: 删除不需要的let并在Price属性上做一个Max

var categories4 = from a in art
                  group a by a.Type into g
                  select new
                  {
                      ArtType = g.Key, 
                      MostExpensive = g.Max(a => a.Price)
                  };

The complicated part is getting the name of the art as it's not part of the grouping. 复杂的部分是艺术的名称,因为它不是分组的一部分。 First you need to decide what happens if 2 items of art with the same type have the same maximum price? 首先,您需要确定如果两件相同类型的艺术品具有相同的最高价格会怎样? Assuming you don't care, this will do the job: 假设您不在乎,这将完成以下工作:

var categories4 = from a in art
                  group a by a.Type into g
                  select new
                  {
                      ArtType = g.Key, 
                      Name = g.Where(a => a.Price == g.Max(b => b.Price))
                              .Select(a => a.Name).First(),
                      MostExpensive = g.Max(a => a.Price)
                  };

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

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