Reputation: 163
class order {
Guid employeeId;
DateTime time;
}
I need to filter a list of orders into 4 lists based on the time range. 0-9AM to 1st list, 9AM-2PM to 2nd, 2-6PM to 3rd and 6-12PM to a 4th list.
I am curious if this can be achieved using lambda expressions in a efficient way? otherwise what would be the best way to split the list?
Upvotes: 0
Views: 3348
Reputation: 460158
This should work:
var orders = list.OrderBy(o => o.time);
var first = orders.TakeWhile(o => o.time.TimeOfDay.TotalHours <= 9);
var second = orders.SkipWhile(o => o.time.TimeOfDay.TotalHours <= 9)
.TakeWhile(o => o.time.TimeOfDay.TotalHours <= 14);
var third = orders.SkipWhile(o => o.time.TimeOfDay.TotalHours <= 14)
.TakeWhile(o => o.time.TimeOfDay.TotalHours <= 18);
var fourth = orders.SkipWhile(o => o.time.TimeOfDay.TotalHours <= 18);
Here's another, maybe more efficient, more flexible and concise approach which uses Enumerable.GroupBy
:
var groups = list.Select(o => new
{
Order = o,
DayPart = o.time.TimeOfDay.TotalHours <= 9 ? 1
: o.time.TimeOfDay.TotalHours > 9 && o.time.TimeOfDay.TotalHours <= 14 ? 2
: o.time.TimeOfDay.TotalHours > 14 && o.time.TimeOfDay.TotalHours <= 18 ? 3 : 4
})
.GroupBy(x => x.DayPart)
.OrderBy(g => g.Key);
var first = groups.ElementAt(0);
var second = groups.ElementAt(1);
// ...
Upvotes: 3
Reputation: 2521
It's important to use the DateTime.TimeOfDay.TotalHours
property, which will return the time represented by whole and fractional hours.
var endTimes = new List<int>() { 9, 14, 18, 24 };
var results = orders.GroupBy(o => endTimes.First(t => o.time.TimeOfDay.TotalHours < t))
.OrderBy(g => g.Key);
Upvotes: 1
Reputation: 3022
I'm in OSX right now so I can't test the solution, but I'd probably add a property to my order class to calculate the group. I feel like your order would reasonably be concerned with this. So, you could have something like this:
class order {
Guid employeeId;
DateTime time;
public int Group { get { return /* check hours of day to group /} }
}
Then, it should be as easy as orders.GroupBy(o => o.Group)
;
If you don't feel like your order should know about the groups, you could make another method where you feel it's more important to define the group. Then you could still say orders.GroupBy(o => GetGroupNumber(o))
.
If you still need help next time I'm in Windows, I'll write a snippet for you.
EDIT:
I've noticed several of the other answers to this question recommend executing a Where or a Skip-Take strategy (with the overhead of a sort) on the original list for each child list you want to create.
My concern is that there is a performance detriment on large sets. For example, the four .Where evaluations will execute the comparisons on all of your objects four times despite the fact that the groups are mutually exclusive.
I don't know how many data you have, but for your sake I hope it's a LOT of orders :). In any event, I'd probably try to do the grouping and comparisons in one iteration like I recommended. If you don't like that solution, I'd recommend you iterate over the list yourself and built your sets without linq to objects.
Just my two cents.
Upvotes: 1
Reputation: 38478
Most readable way would be to use a named function to do the grouping and pass it as a delegate to the GroupBy()
var orderGroups = orders.GroupBy(GetOrderGroup)
private int GetOrderGroup(order o)
{
//implement your groups
}
Upvotes: 2
Reputation: 14938
This should do the trick:
var first = orders.Where(o => o.time.Hour >= 0 && o.time.Hour < 9);
var second = orders.Where(o => o.time.Hour >= 9 && o.time.Hour < 14);
var third = orders.Where(o => o.time.Hour >= 14 && o.time.Hour < 18);
var fourth = orders.Where(o => o.time.Hour >= 18 && o.time.Hour < 24);
Upvotes: 1