7

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.

11
  • @J.Steen I have tried that before, it is not working either. It says it's incompatible type. (there was a suggestion to remove the curly brackets, it's not working). Commented May 10, 2017 at 14:34
  • 2
    You should remove the curly braces anyway, because otherwise you need to return a value from the statement scope that you've created (in effect, a full anonymous method instead of a lambda function with implicit return). That notwithstanding, the Count method's predicate doesn't take two parameters as far as I know. Commented May 10, 2017 at 14:37
  • 5
    What are the r and s parameters? The .Count() extension method takes a predicate - it should have one parameter that is the type of your collection item, and should return bool - whether to include it into the count or not. I.e. if selectedShifts is an IEnumerable<IShift> then count should take a single IShift and return a bool. Commented May 10, 2017 at 14:40
  • @RonaldZarīts the r and s parameters are there so I can actually select multiple days inside the .Count() which is clearly not working Commented May 10, 2017 at 14:47
  • 1
    Why don't you try using Select() to filter the IEnumerable and then use Count()? Commented May 10, 2017 at 14:51

6 Answers 6

5

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);
Sign up to request clarification or add additional context in comments.

7 Comments

Probably not the most efficient solution, but looks like it should get the job done.
Doesn't this assume that the source is ordered and that there can't be another shift in-between a Friday Late and a Monday Early?
@MattBurland The first solution doesn't assume it's ordered, but yes, it will also count the pairs that have a shift in-between. I don't know if that's what Dracke wants or not.
@TimRogers: yeah, I just scratched up test code and it does produce all possible pairs, which is actually a pretty clever short cut for doing that.
@FilipCordas: No. Although each pair will turn up twice. Only the pair where the first member is Friday and the second is Monday will be counted. The reversed pair will not.
|
3

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.

2 Comments

Yeah, except earlyMondays will get re-enumerated for every element of lateFridays. Consider calling ToArray(). Plus the first parameter to SelectMany should be a lambda.
Thanks @TimRogers
1

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

Comments

0

If i read the question correctly you just want the OR syntax. This will give late shifts friday AND early on Monday separately:

var countFridayLateAndMondayEarly =
                    selectedShifts.Count(
                        shift => (shift.ShiftType == ShiftType.Late && shift.Day.DayOfWeek == DayOfWeek.Friday)
|| // or 
                            (shift.Day.DayOfWeek == DayOfWeek.Monday && shift.ShiftType == ShiftType.Early
                            ));

4 Comments

I'm not sure this will work in OP's case, this will only give shifts that are late fridays or early mondays BUT that aren't necessarily directly after each other as OP's criteria states. Though I guess if OP is guaranteeing the shifts are put into the stack in date order then it will work.
No, that is not it. 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.
@Dracke I'm not sure how you're going to do that when there's no manner of saying that one Friday is before a Monday. You might need to add a date field or something like that to be able to say that this Monday is directly after this Friday, otherwise they're just a jumble of random Mondays and Fridays right?
@Dracke - put this comment in your question - this is the missing piece of information. Don't have time to reply, but I'm sure someone will.
0

If you like true Linq syntax you can do it like this.

    var matchingPairs = from lateFriday in
                             (from r in selectedShifts where r.Day.DayOfWeek == DayOfWeek.Monday && r.ShiftType == ShiftType.Early select r)
                        from earlyMonday in
                             (from s in selectedShifts where s.Day.DayOfWeek == DayOfWeek.Monday && s.ShiftType == ShiftType.Early select s)
                        where earlyMonday.Day.AddDays(-2).DayOfYear == lateFriday.Day.DayOfYear
                        select new { lateFriday, earlyMonday };

var count = matchingPairs.Count();

Comments

0

Another variant It won't perform any better than the cross-join since it does the same thing but is perhaps a bit more readable. Only use it on lists in memory.

selectedShifts.Where(x => x.Day.DayOfWeek == DayOfWeek.Friday && x.ShiftType == ShiftType.Late && 
  list.Any(y => x.Day.AddDays(3) == y.Day && y.ShiftType == ShiftType.Early))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.