Я не уверен, насколько это эффективно или производительно, но я думаю, что LINQ может преобразовать его в SQL, поэтому, если вы используете базу данных, это может привести к увеличению объема вычислений на сервере.
Сначала сгруппируйте записи по значкам:
var p1 = from p in punches
group p by p.Badge into pg
select new {
Badge = pg.Key,
Punches = pg.OrderBy(p => p.Time)
};
Затем, для каждой группы записей значка, просмотрите все записи "IN" и сопоставьте их с записью "OUT", если она существует:
var p2 = p1.SelectMany(pg => pg.Punches.Where(p => p.Dir == "IN")
.Select(p => new {
pg.Badge,
TimeIn = p.Time,
TimeOut = pg.Punches.Where(po => po.Dir == "OUT" && po.Time > p.Time)
.FirstOrDefault().Time
}));
Наконец, закажите результат:
var ans = p2.OrderBy(bio => bio.Badge).ThenBy(bio => bio.TimeIn);
Так как LINQ to SQL распространяет нули автоматически, я думаю, что это обработает отсутствующий удар "OUT" для "IN", но не потерянный "OUT" удар.
Другая возможность - использовать Select
с двумя параметрами для группировки записей перфорации в парах, но это работает только с LINQ to Objects, поэтому, если вы не отфильтруете данные перед обработкой, все миллионы записей будут вытянуты в память.
Для полноты вот попытка:
var p2 = p1.AsEnumerable()
.SelectMany(pg => pg.Punches.Select((p, i) => (p, i))
.GroupBy(pi => pi.i / 2, pi => pi.p)
.Select(pp => new {
pg.Badge,
TimeIn = pp.Where(p => p.Dir == "IN").FirstOrDefault()?.Time,
TimeOut = pp.Where(p => p.Dir == "OUT").FirstOrDefault()?.Time
}));
Ничего из этого не будет работать очень хорошо, если ваши удары плохо упорядочены, например вам не хватает начального «IN».