Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# multiple variables in lambda expression inside LinQ query

Tags:

c#

lambda

linq

I am working with a Nurse calendar that consists of Shifts:

public interface IShift
{
    ShiftType ShiftType { get; } //enum {Day, Early, Late, Night}
    DateTime Day { get; }
    bool IsNightShift();
    bool IsWeekendShift();
}

The Nurse calendar is IEnumerable<IShift>. From that, I am selecting my own shifts (IEnumerable<IShift> selectedShifts) for a shorter period of time to check for some constraints.

I am trying to count multiple conditions, for example, Night Shifts on Friday:

var countFridayNight = selectedShifts.Count(s => s.IsNightShift() 
    && s.Day.DayOfWeek == DayOfWeek.Friday);

What I am struggling with is to count multiple things on multiple days. For example Late shift on Friday and Early shift the next Monday. I have tried this, but VS doesn't seem to like the syntax:

var countFridayLateAndMondayEarly =
                    selectedShifts.Count(
                        (r, s) =>  s.ShiftType == ShiftType.Late && s.Day.DayOfWeek == DayOfWeek.Friday 
                            && r.Day.DayOfWeek == DayOfWeek.Monday && r.Day.AddDays(-2).DayOfYear == s.Day.DayOfYear && r.ShiftType == ShiftType.Early
                            );

Edit: Removed curly braces in the last lambda expression.

Edit2: There were two comments saying that Count can't take more than one variable inside the lambda expression. How else can I do what I need to using LINQ?

Edit3: Clarification of the problem - I need to count the shifts that are Late shifts on Friday and at the same time, there exists another shift that is Early the next Monday.

like image 782
Dracke Avatar asked May 10 '17 14:05

Dracke


3 Answers

You need to cross-join the collection to itself for this problem - essentially you need to get every pair of shift combinations and count the pairs where the first is a late Friday and the second is an early Monday.

First get the pairs:

var pairs = selectedShifts.SelectMany(s => selectedShifts, Tuple.Create);

Secondly, count the pairs that match your criteria:

var count = pairs.Count(pair => 
       pair.Item1.ShiftType == ShiftType.Late 
    && pair.Item1.Day.DayOfWeek == DayOfWeek.Friday 
    && pair.Item2.Day.DayOfWeek == DayOfWeek.Monday 
    && pair.Item2.Day.AddDays(-2).DayOfYear == pair.Item1.Day.DayOfYear 
    && pair.Item2.ShiftType == ShiftType.Early);

You can get the pairs more efficiently if the shifts are already ordered sequentially, and you only want to count adjacent shifts:

var pairs = selectedShifts.Zip(selectedShifts.Skip(1), Tuple.Create);
like image 187
Tim Rogers Avatar answered Sep 21 '22 21:09

Tim Rogers


If you reduce the inputs to lateFridays and earlyMondays before pairing, it should go a little faster.

var lateFridays = selectedShifts
  .Where(s => s.ShiftType == ShiftType.Late && s.Day.DayOfWeek == DayOfWeek.Friday)
  .ToList();

var earlyMondays = selectedShifts
  .Where(r => r.Day.DayOfWeek == DayOfWeek.Monday && r.ShiftType == ShiftType.Early)
  .ToList();

var matchingPairs = lateFridays.SelectMany(friday => earlyMondays, Tuple.Create)
  .Where(t => t.Item2.Day.AddDays(-2).DayOfYear == t.Item1.Day.DayOfYear);

var count = matchingPairs.Count();

Also, this date comparison is bad for year straddling cases.

like image 36
Amy B Avatar answered Sep 21 '22 21:09

Amy B


Strictly speaking, you can use something like:

    var cnt = selectedShifts.Count(shift =>
            shift.Day.DayOfWeek == DayOfWeek.Friday && shift.ShiftType==ShiftType.Late
                && selectedShifts.Any(next =>next.Day == shift.Day.AddDays(3) && next.ShiftType == ShiftType.Early)
    );

But performance wise it might be better to determine the next shift by linking them together or with a sub list. For example:

    var lst= selectedShifts.ToList(); //Assuming already ordered, otherwise add an 'OrderBy' before the 'ToList'
    var cnt = lst.Where((shift,index)=> shift.Day.DayOfWeek == DayOfWeek.Friday && shift.ShiftType==ShiftType.Late 
        && index < lst.Count-1 && lst[index+1].Day == shift.Day.AddDays(3) && lst[index+1].ShiftType == ShiftType.Early);

The latter assumes that the next shift is the monday shift and not some weekend shift. With that last method, you could also check if the amound of days (or hours) between the late shift and the next shift is smaller than an 'x' amount

like image 42
Me.Name Avatar answered Sep 23 '22 21:09

Me.Name