C# несколько переменных в лямбда-выражении внутри запроса LinQ

Я работаю с календарем медсестры, который состоит из смен:

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

календарь медсестры IEnumerable<IShift>. Из этого я выбираю свои собственные смены (IEnumerable<IShift> selectedShifts) в течение более короткого периода времени для проверки некоторых ограничений.

Я пытаюсь подсчитать несколько условий, например, ночные смены в пятницу:

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

то, с чем я борюсь, - это считать несколько вещей в несколько дней. Например, поздняя смена в пятницу и ранняя смена в следующую Понедельник. Я пробовал это, но VS, похоже, не нравится синтаксис:

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: удалены фигурные скобки в последнем лямбда-выражении.

Edit2: было два комментария о том, что Count не может принимать более одной переменной внутри лямбда-выражения. Как еще я могу сделать то, что мне нужно, используя LINQ?

Edit3: разъяснение проблемы - мне нужно подсчитать смены, которые являются поздними сменами в пятницу, и в то же время существует еще один сдвиг, который Рано утром в следующий понедельник.

6 ответов


вам нужно перекрестно присоединить коллекцию к себе для этой проблемы-по существу вам нужно получить каждый пара комбинаций сдвига и подсчета пар где первый-поздняя пятница, а второй-ранний понедельник.

сначала получите пары:

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

во-вторых, подсчитайте пары, которые соответствуют вашим критериям:

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);

вы можете получить пары более эффективно, если сдвиги уже упорядочены последовательно, и вы только хотите подсчитать смежные смены:

var pairs = selectedShifts.Zip(selectedShifts.Skip(1), Tuple.Create);

Если вы уменьшите входы до lateFridays и earlyMondays перед сопряжением, он должен идти немного быстрее.

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();

кроме того, это сравнение дат плохо для случаев, охватывающих год.


строго говоря, вы можете использовать что-то вроде:

    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)
    );

но с точки зрения производительности было бы лучше определить следующий сдвиг, связав их вместе или с подсписком. Например:

    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);

последний предполагает, что следующая смена-это смена понедельника, а не какая-то смена выходных. С помощью этого последнего метода вы также можете проверить, меньше ли количество дней (или часов) между поздней сменой и следующей сменой, чем сумма "x"


Если я правильно прочитал вопрос, вы просто хотите OR синтаксис. Это даст поздние смены в пятницу и рано в понедельник отдельно:

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

Если вам нравится синтаксис true Linq, вы можете сделать это так.

    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();

другой вариант Он не будет работать лучше, чем перекрестное соединение, так как он делает то же самое, но, возможно, немного более читаемый. Используйте его только в списках в памяти.

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))