Group a collection and take first 5 from each group - c#

I have a collection. I need to group the collection by "A" property. And I have to sort each group by"B" property. Then select first 5 from each group.
Can anyone suggest a LINQ query for this?
The way I tried is not working.
(from item in Recipes
orderby item.Rating descending
group item by item.MainCategory).Take(5)
The query should return IEnumerable<IGrouping<string, myrecipetype>>

You are taking first five groups. Instead you need to select first five items from each group:
from item in Recipes
orderby item.Rating descending
group item by item.MainCategory into g
select g.Take(5)
UPDATE:
from item in Recipes
orderby item.Rating descending
group item by item.MainCategory into g
select g.Take(5).GroupBy(item => item.MainCategory).First()

Edit: In your case, with sorting added (after the OP was updated):
Recipes.GroupBy(recipy => recipy.MainCategory)
.Select(recipyGroup => recipyGroup.OrderBy(recipy => recipy.Rating)
.Take(5))

Related

Replacing ElementAt in a LINQ query to cover multiple elements

I want to retrieve a list of Orders based on a multiple Location lists.
For context: a Group can have a multiple Locations (one-to-many) and an Order has one Location (many-to-one). I want to get all the Orders of a Group based on the Locations it has.
The following works for the first element in the list of Groups:
List<Order> orders = _context.Orders
.Where(o => groups.ElementAt(0).Locations.Contains(o.Location))
.ToList()
I want to change it such that it will not only check the first Group element, but all of them. Help is appreciated.
As groups is only a variable, you can use SelectMany to flatten the nested hierarchy before running the query:
var locations = groups.SelectMany(x => x.Locations).ToArray();
This basically changes a nested hierarchy
G1
L11
L12
G2
L21
L22
to a flat list of locations:
L11
L12
L21
L22
After that, you can use the locations in the query:
List<Order> orders = _context.Orders
.Where(o => locations.Contains(o.Location))
.ToList()
If the groups can contain duplicate locations, you could also add a Distinct after the SelectMany.
Is this what you are looking for?
var result = orders
.Where(o => groups.All(f =>
f.Locations.Contains(o.Location))).ToList();

How to join a list of lists in a unique list using Linq

I have an List of objects that have a List of long. I need to get all long of all objects and join it to a unique list.
How to do this using System.Linq?
This is my code actually (with this code I get a List of List of long)
var result = LIST1.Select(x => x.LIST2.Select(y => y.Id).ToList()).Tolist();
This flattens your list and then does a distinct on it
LIST1.SelectMany(a => a.LIST2.Select(b => b.Id)).Distinct();
Use selectMany instead of Select
var result = LIST1.SelectMany(x => x.LIST2.Select(y => y.Id)).Tolist();
Just dont get it if you need to join the lists of objects or you need to union all, but for join you could use:
var result = (from Item1 in LIST1
join Item2 in LIST2
on Item1.Id equals Item2.Id
select new { Item1, Item2 }).ToList();

Why is iterating a group different than iterating a collection in linq

In the query below it is easy to see how we are basically looping through sampleOrders and evaluating each order i.e. we can access the properties of ord:
var o = (
from ord in sampleOrders
select ord.Quantity
In the query below we are grouping orders by product into orderGroups. orderGroups is conceptually a "list of lists" where each list has a key and a property which contains associated orders. The preceeding description is very loose of course - the point is that if I iterate the collection I would expect to receive an object with a key property and a property that contains the orders. This behavior seems to be consistent with the first query above. But in fact each group is an order. It appears by iterating the list of groups that we bypass the group itself and in fact iterate each order.
Why is this grouping behavior different from the behavior in the first query above?
var o = (
from ord in sampleOrders
group ord by ord.Product into orderGroups
from grp in orderGroups
select grp // grp is an order, not a group of orders
BTW, Please note I am not asking how to access the groups..... my question is related to the concept and the syntax.
Here is how to access each group.... I am not asking how to do this:
var o = (
from ord in sampleOrders
group ord by ord.Product into orderGroups
select new {orderGroups.Key, SumQty=orderGroups.Sum(x => x.Quantity)}
Edit: Working example per request
public static void LinqTest()
{
List<Order> sampleOrders = new List<Order>()
{
new Order{Product="A", Quantity = 3},
new Order{Product="A", Quantity = 4},
new Order{Product="B", Quantity = 5},
new Order{Product="C", Quantity = 6},
};
var orders =
from ord in sampleOrders
group ord by ord.Product into orderGroups
select orderGroups;
foreach (var grp in orders) // here is the outer list
{
Console.WriteLine("Product: " + grp.Key.ToString());
// here is the inner list hence the term "list of lists"
// it is in fact a hierarchical object however this fact is irrelelvent to my question
//
foreach (Order ord in grp)
Console.WriteLine("Qty: " + ord.Quantity);
}
Console.WriteLine();
Console.WriteLine("following listing is grouped orders but we see each order not groups");
var orders2 =
from ord in sampleOrders
group ord by ord.Product into orderGroups
from grp in orderGroups
select grp;
foreach(Order ord2 in orders2)
Console.WriteLine("Product: " + ord2.Product); // each item returned is an order not a group of orders
Console.ReadKey();
}
}
class Order
{
public string Product { get; set; }
public int Quantity { get; set; }
}
EDIT 2:
Here is another stab at clarifying my question:
In the first query above "from x in listOfx" gives you an object, x, which is the same object that listOfx is composed of.
In the group query, "from x in listOfxGroups" gives you an object which is different than the objects that compose listOfxGroups. listOfxGroups is in fact a list of groups... but when we iterate it we get x not "group of x".
from ord in sampleOrders
group ord by ord.Product into orderGroups
from grp in orderGroups
select grp
At a glance, one might think that this translates to this:
sampleOrders.GroupBy(ord => ord.Product)
.Select(grp => grp); // grp is an `IGrouping<,>`
But in actuality, it translates to this:
sampleOrders.GroupBy(ord => ord.Product)
.SelectMany(orderGroup => orderGroup); // orderGroup is an `IGrouping<,>`
.Select(grp => grp); // grp is an Order
This is how LINQ statements are translated: after the initial from keyword, any remaining froms produce a SelectMany. For example:
A query expression with a second from clause followed by a select clause
from x1 in e1
from x2 in e2
select v
is translated into
( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => v )
C# 5 language spec, section 7.16.2.4
from a in x
from b in a
select b
Now, the lambda expression in SelectMany must return an IEnumerable<T> (which it does, because IGrouping<TKey, TValue> is also an IEnumerable<TValue>), and SelectMany flattens out all of the IEnumerable<T>s into one big IEnumerable<T>, which means that the individual items in it are Orders, in this case.
Update
Just to help clarify, imagine the following:
var orderGroups = // orderGroups is plural: IEnumerable<IGrouping<,>>
from ord in sampleOrders
group ord by ord.Product;
var o =
from orderGroup in orderGroups // orderGroup is singular: IGrouping<,>
from item in orderGroup // `item` instead of `grp`
select item;
This is effectively what you produce with your into clause, but with different variable names.
var o =
from ord in sampleOrders
group ord by ord.Product into orderGroup
from item in orderGroup
select item; // item is an order, not a group of orders
orderGroups is conceptually a "list of lists"
No, it's not. It's conceptually a single group, not a group of groups. When you say:
from grp in orderGroups
grp now represents one item in the group (in this case, an order), not one group, as you're iterating all of the items in one group.
If you selected out the group, then the entire query would represent a list of groups (or a list of lists).
var listOfGroups = from order in sampleOrders
group order by order.Product into orderGroup
select orderGroup;
Looking at Microsoft's documenation for the group caluse.
The group clause returns a sequence of IGrouping objects that contain zero or more items that match the key value for the group
so if you wanted a group returned your code might look like this:
var o = (
from ord in sampleOrders
group ord by ord.Product // Should return a group.
Since you're using the into the keyword, you're going to get an order object because you used the' select keyword .

Linq groupby with where clause

I am attempting to group my items and have a where clause included and I am not quite sure where to put my items.
here is what i have so far:
#{
var trust = new trusteeEntities();
var gen = (from g in trust.Documents
where g.doc_type == "Minutes"
orderby g.meeting_date descending
group g by g.meeting_date into f
select g);
foreach (var f in gen)
{
<div class="documents">
<span class="date">#string.Format("{0:MMMM d, yyyy}", f.meeting_date)</span>
<p>#f.title</p>
</div>
}
}
You have to order the items after the grouping as GroupBy does not keep order. Moreover, you select the wrong items. To select the groups use select f instead of select g.
from g in trust.Documents
where g.doc_type == "Minutes"
group g by g.meeting_date into f // Groups the items g into groups called g
orderby f.Key descending // Orders the groups by their key (which corresponds to g.meeting_date)
select f // Selects the group
I also would highly recommend you to rename your variables:
from document in trust.Documents
where document.doc_type == "Minutes"
group document by document.meeting_date into documentGroup // Groups the items g into groups called g
orderby documentGroup.Key descending // Orders the groups by their key (which corresponds to document.meeting_date)
select documentGroup // Selects the group
To show the groups (not sure of that part because I've never written ASP.NET code nor HTML code):
foreach (var documentGroup in gen)
{
<div class="documents">
<span class="date">#string.Format("{0:MMMM d, yyyy}", documentGroup.Key)</span>
foreach (var document in documentGroup)
{
<p>#f.title</p>
}
</div>
}
Update
Given the code in the foreach, I think you don't need to group the documents by their date. If so, the Linq query is:
from document in trust.Documents
where document.doc_type == "Minutes"
orderby document.meeting_date descending
select document

Linq getting a list from another list

I have two collections: one is Items And Another is ActiveItems
The only intersection between these two collection is Name
I want a list with Linq from Items where the Items names are in the ActiveItems with that name
I wrote this code is there a better idea:
Items.Where(i => ActiveItems.Count(v=> v.Name==i.Name) > 0)
I would probably create a set of the names from ActiveItems and then use that:
var activeNames = new HashSet<string>(activeItems.Select(x => x.Name));
var itemsWithActiveNames = items.Where(x => activeNames.Contains(x.Name))
.ToList();
Another option is to use a join, e.g. with a query expression:
var query = from activeItem in activeItems
join item in items on activeItem.Name equals item.Name
select item;
Note that this will give duplicate item values if there are multiple ActiveItem values with the same name. Another alternative join, which doesn't have this problem but is a bit clumsier:
var query = from item in items
join activeItem in activeItems
on item.Name equals activeItem.Name
into g
where g.Any()
select item;
Note that all of these will avoid the O(N * M) check for names - they'll all use hash tables behind the scenes, to give an O(N + M) complexity.
Items.where(i => ActiveItems.Any(a => i.Name == a.Name))
var results = from i1 in collection1.Items
join i2 in collection2.ActiveItems on i1.Name equals i2.Name
select i2.Name;
Using a join:
from item in Items
join active in ActiveItems on item.Name equals active.Name
select item

Categories