группировка объектов по временному промежутку - PullRequest
0 голосов
/ 12 марта 2019

У меня есть очень сложная серия запросов linq, которая делает все, что мне нужно, кроме одной вещи.

Сначала позвольте мне объяснить, что это делает.

Эта серия запросов берет данные об игровой конференции и объединяет EventSession.SessionEventName на основе того же gameID и того же SessionDate.

Итак, в представлении это выглядело бы так:

    GameID    | GameName    | Names            | PlayDate     | PlayDuration
    123         Pac-Man       Joe; Jim; Mary     10/1/2018      10:00 AM - 12:30 PM

Так что все это работает нормально, КРОМЕ. Мне нужно добавить это в запрос, и я могуНе могу понять, где это сделать или как это сделать:

Мне нужно добавить условие, которое разделяет строки, если между играми в один и тот же день более 35 минут.

Например, приведенное выше будет выглядеть так, если между игрой Джо, Джима или Мэри будет больше 35 минут.

    EventID | GameID    | GameName    | Names            | PlayDate     | PlayDuration
    -----------------------------------------------------------------------------------------
       1      123         Pac-Man       Joe; Jim           10/1/2018      10:00 AM - 11:00 AM
       2      123         Pac-Man       Mary               10/1/2018      11:40 AM - 12:30 PM

Так что, в основном, он группирует игроков, если тот же GameIDи PlayDate.Тем не менее, если где-нибудь будет промежуток в 35 с лишним минут, он разделится на новую группу строк.

Я не знаю, как это сделать.

Буду признателен за любую ивсе советы.

Спасибо!

Вот мой метод контроллера, который содержит этот волшебный беспорядок кода:

    [HttpPost]
    public async Task<IActionResult> SearchByDate(DateTime? start, DateTime? end)
    {

        // create a list of conferenceGames
        var conferenceGames = await _context.PublisherList
                     .Where(m => m.MakerCatalogId != null
                     && !m.IsBetaStatus).ToListAsync();

        // create a list of conferencePlayers
        var conferencePlayers = await _context.PlayerList
            .Where(p => p.PlayerTypeId == 23
            && p.PlayerStartTime != null
            && p.PlayerEndTime != null)
            .OrderBy(p => p.PlayerStartTime).ToListAsync();

        // create a list of conferenceSponsers
        var conferenceSponsers = await _context.SponserList
            .Where(f => f.SponserDateTimeStart >= start 
            && f.SponserDateTimeStart <= end
            && f.PublisherId != 2000111
            ).ToListAsync();

        // create an IEnumerable of EventSession 
        var query = from cp in conferencePlayers
                    join cs in conferenceSponsers on cp.SponserId equals cs.SponserId
                    join cg in conferenceGames on cs.PublisherId equals cg.PublisherId
                    select new EventSession
                    {
                        Id = cp.PlayerId,
                        GameId = cg.PublisherId,
                        SessionGameName = cg.GameDisplayName,
                        SessionEventName = cp.PlayerDisplayName,
                        SessionDate = cs.SponserDateTimeStart,
                        SessionStartTime = cp.PlayerStartTime.Value.TimeOfDay,
                        SessionEndTime = cp.PlayerEndTime.Value.TimeOfDay
                    };

        // order the results
        var orderedResults = query
            .OrderBy(n => n.GameId)
            .ThenBy(d => d.SessionDate)
            .ThenBy(tsa => tsa.SessionStartTime)
            .ToList();

        // group the List by Date and Game
        List<GroupedEvents> playersGroupList = orderedResults.GroupBy(x => new { x.SessionDate, x.GameId }).Select(group => new GroupedEvents
        {
            GameName = group.Select(n => n.SessionGameName).FirstOrDefault(),
            GameId = group.Select(c => c.GameId).FirstOrDefault().ToString(),
            PlayDate = group.Select(d => d.SessionDate).FirstOrDefault(),
            Names = String.Join(" ; ", group.Select(g => g.SessionEventName).ToArray()),
            PlayDuration = group.Select(g => g.SessionStartTime).First() + " - " + group.Select(g => g.SessionEndTime).Last(),
        }).ToList();

        // return the ordered/grouped list back to the view
        return View(playersGroupList);
    }

1 Ответ

1 голос
/ 12 марта 2019

Используя некоторые методы расширения, это можно сделать довольно понятно.

Во-первых, вариант Aggregate, который является версией оператора сканирования APL, перемещается вдоль IEnumerable, возвращая промежуточные результаты, ноэтот вариант объединяет пару за один раз, текущий и предыдущий элемент:

// TKey combineFn((TKey Key, T Value) CurKeyItem, T nextItem):
// CurKeyItem.Key = Current Key
// CurKeyItem.Value = Current Item
// NextItem = Next Item
// returns (Key, Current Item)
public static IEnumerable<(TKey Key, T Value)> ScanToPairs<T, TKey>(this IEnumerable<T> src, TKey seedKey, Func<(TKey Key, T Value), T, TKey> combineFn) {
    using (var srce = src.GetEnumerator())
        if (srce.MoveNext()) {
            var curkv = (seedKey, srce.Current);

            while (srce.MoveNext()) {
                yield return curkv;
                curkv = (combineFn(curkv, srce.Current), srce.Current);
            }
            yield return curkv;
        }
}

Объяснение: ScanToPairs проходит через IEnumerable, начиная с первого и второго значений и значения seedKey.Он передает ValueTuple, содержащий текущий ключ и текущий элемент и (отдельно) следующий элемент, в combFn и возвращает ValueTuple ключа, текущего элемента.Итак, первый результат - (seedKey, FirstItem).Второй результат будет (комбинироватьFn ((seedKey, FirstItem), SecondItem), SecondItem).И т. Д.

Затем оператор GroupBy, который группирует путем тестирования пар с помощью логической тестовой функции:

// bool testFn(T prevItem, T curItem)
// returns groups by runs of matching bool
public static IEnumerable<IGrouping<int, T>> GroupByPairsWhile<T>(this IEnumerable<T> src, Func<T, T, bool> testFn) =>
    src.ScanToPairs(1, (kvp, cur) => testFn(kvp.Value, cur) ? kvp.Key : kvp.Key + 1)
       .GroupBy(kvp => kvp.Key, kvp => kvp.Value);

Объяснение: При использовании метода ScanToPairs этот метод группируетIEnumerable в кортежи, где ключ представляет собой целое число, начиная с 1, представляющего номер прогона true testFn, является результатом сравнения предыдущего элемента с текущим элементом.Как только все серии были пронумерованы, они сгруппированы вместе с GroupBy в группы элементов, которые относятся к серии.

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

var playersGroupList = orderedResults.GroupBy(x => new { x.SessionDate, x.GameId })
                                     .SelectMany(g => g.GroupByPairsWhile((p, c) => c.SessionStartTime-p.SessionEndTime <= TimeSpan.FromMinutes(35)))
                                     .Select(group => new GroupedEvents {
                                         GameName = group.Select(n => n.SessionGameName).FirstOrDefault(),
                                         GameId = group.Select(c => c.GameId).FirstOrDefault().ToString(),
                                         PlayDate = group.Select(d => d.SessionDate).FirstOrDefault(),
                                         Names = String.Join("; ", group.Select(g => g.SessionEventName).ToArray()),
                                         PlayDuration = group.Select(g => g.SessionStartTime).First() + " - " + group.Select(g => g.SessionEndTime).Last(),
                                     })
                                     .ToList();

Таким образом, SelectMany берет каждую группу для данного SessionDate, иразбивает их на группы, где каждый участник находится менее чем в 35 минутах от следующего.Из-за SelectMany все подгруппы повышаются до групп конечного результата.Итак, теперь у вас есть группы, в каждой из которых есть серия сеансов, где от SessionEndTime до следующего SessionStartTime меньше 35 минут.Обратите внимание, что запуск закончится в конце дня независимо от того, поэтому, если у вас могут быть запуски, которые идут в полночь, вам нужно будет изменить группировку.

Примечание: Если это возможно для сеансов, начинающихся св то же время, чтобы иметь разные длительности (т.е. время окончания), вам нужно добавить ThenBy(tsa => tsa.SessionEndTime) к вашей orderedResults сортировке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...