遍历IEnumerable并按日期和时间跨度分组

时间:2018-11-12 21:47:01

标签: c# linq

我有一个从LINQ查询创建的IEnumerable对象。

数据看起来像这样:

Id        EventName        EventDate        EventStart      EventEnd
--------------------------------------------------------------------
1        StoryTime          4/6/2018        8:00            8:45        
2        Baking             4/6/2018        8:55            9:30
3        Cooking            4/7/2018        7:45            9:50
4        Comprehension      4/8/2018        9:05            10:10
5        WindDown           4/8/2018        10:25           10:55
6        Naptime            4/8/2018        11:00           11:30
7        Play               4/8/2018        13:50           14:20
8        Smarts             4/8/2018        14:30           16:00
9        StoryTime          4/9/2018        9:30            12:05
10       FunTime            4/10/2018       14:10           16:10

我需要遍历IEnumerable并通过检查日期和时间来检查数据。我想将同一天的事件分组,并且事件的EventStart时间距上一个活动EventEnd时间不超过30分钟。

逻辑很强硬。我一直在尝试不同的方法,但找不到任何方法。

这是我到目前为止所到之处:

// loop through IEnumerable object created with linq query
foreach (var e in eventResults)
{
    // set current interation current date
    DateTime? currentDate = e.EventDate;

    // make sure we are only checking time span differences in the same day
    while (e.EventDate == currentDate)
    {
         int currentId = e.Id;
         DateTime? currentStartTime = e.EventStart;
         DateTime? currentEndTime = e.EventEnd; 

        // stuck -- not sure where to go with my logic  :(
    }
}

完成后,看起来会像这样:

  • 4月6日,StoryTime +烘焙:8:00-9:30
  • 4/7上,烹饪:7:45-9:50
  • 在4/8上,理解力+结束时间+小憩时间:9:05-11:30
  • 在4/8上,Play + Smarts:13:50-16:00
  • 4/9,故事时间:9:30-12:05
  • 在4/10上,娱乐时间:14:10-16:10

如果有人可以提供帮助,我将不胜感激。谢谢!

3 个答案:

答案 0 :(得分:3)

以下是按日期分组和Aggregate来每天创建动态子分组(实际上是列表)的一些linq方法:

var eventResults = new[]
{
    new EventItem(1, "StoryTime", new DateTime(2018, 4, 6), new TimeSpan(8, 0, 0), new TimeSpan(8, 45, 0)),
    new EventItem(2, "Baking", new DateTime(2018, 4, 6), new TimeSpan(8,55, 0), new TimeSpan(9, 30, 0)),
    new EventItem(3, "Cooking", new DateTime(2018, 4, 7), new TimeSpan(7,45, 0), new TimeSpan(9, 50, 0)),
    new EventItem(4, "Comprehension", new DateTime(2018, 4, 8), new TimeSpan(9, 5, 0), new TimeSpan(10,10, 0)),
    new EventItem(5, "WindDown", new DateTime(2018, 4, 8), new TimeSpan(10,25, 0), new TimeSpan(10,55, 0)),
    new EventItem(6, "Naptime", new DateTime(2018, 4, 8), new TimeSpan(11,0, 0), new TimeSpan(11,30, 0)),
    new EventItem(7, "Play", new DateTime(2018, 4, 8), new TimeSpan(13,50,0), new TimeSpan(14,20, 0)),
    new EventItem(8, "Smarts", new DateTime(2018, 4, 8), new TimeSpan(14,30,0), new TimeSpan(16, 0, 0)),
    new EventItem(9, "StoryTime", new DateTime(2018, 4, 9), new TimeSpan(9,30, 0), new TimeSpan(12, 5, 0)),
    new EventItem(10, "FunTime", new DateTime(2018, 4, 10), new TimeSpan(14,10,0), new TimeSpan(16,10, 0)),
};
var groups = eventResults
    .GroupBy(x => x.EventDate)
    .SelectMany(g => g.OrderBy(x => x.EventStart)
        .Aggregate(new List<List<EventItem>> { new List<EventItem>() }, (l, e) =>
        {
            if ((e.EventStart - l.Last().Select(x => x.EventEnd).DefaultIfEmpty(e.EventStart).Last()).TotalMinutes <= 30)
            {
                l.Last().Add(e);
            }
            else
            {
                l.Add(new List<EventItem> { e });
            }
            return l;
        })
        .Select(x =>
        new
        {
            Date = g.Key,
            activities = x
        }))
    .OrderBy(x => x.Date).ThenBy(x => x.activities.First().EventStart);

foreach (var item in groups)
{
    var activities = string.Join(" + ", item.activities.Select(x => x.EventName));
    Console.WriteLine($"On {item.Date}, {activities}: {item.activities.First().EventStart} - {item.activities.Last().EventEnd}");
}

关键点是,当条目相距小于X时,对分组进行分组的要求不是合适的分组条件。因此,每天需要对(分类的)条目进行某种迭代。

在这里,我没有特别提倡在传统循环上使用Aggregate。这就是我决定编写此代码的方式。对循环进行编码可能对初学者更友好(更易于阅读)。

答案 1 :(得分:1)

我不确定您将如何处理LINQ中的 entire 问题,但是,如果您找到了一种方法,那么它的可读性可能会降低,因此,恕我直言,最好使用部分LINQ-基于方法:

var flattened = new List<Event>();
for (int i = events.Count - 1; i > 0; i--)
{
    events.TimeOffset = events[i].EventStart - events[i - 1].EventEnd;
}

foreach (var grouping in events.GroupBy(g => g.EventName))
{
    var compressed = grouping.First();
    foreach (var ev in grouping)
    {
        if (ev.TimeOffset?.Minutes > 30 ?? false)
        {
            // In this case, we have flattened as far as we can
            // Add the event and start over
            flattened.Add(compressed);
            compressed = new Event
            {
                Id = ev.Id,
                EventName = ev.EventName,
                EventDate = ev.EventDate,
                EventStart = ev.EventStart,
                EventEnd = ev.EventEnd
            };
        }
        else
        {
            compressed.Name = $"{compressed.Name} + {ev.Name}";
            compressed.EventEnd = ev.EventEnd;
        }
    }
}

请注意,在这种情况下,我向Event添加了一个名为TimeOffset的字段,但是您可以使用字典来完成同样的事情。

答案 2 :(得分:1)

使用基于GroupByWhile的扩展名ScanPair进行分组,而布尔值是true,则基于public static class IEnumerableExt { // TKey combineFn((TKey Key, T Value) PrevKeyItem, T curItem): // PrevKeyItem.Key = Previous Key // PrevKeyItem.Value = Previous Item // curItem = Current Item // returns new Key public static IEnumerable<(TKey Key, T Value)> ScanPair<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 prevkv = (seedKey, srce.Current); while (srce.MoveNext()) { yield return prevkv; prevkv = (combineFn(prevkv, srce.Current), srce.Current); } yield return prevkv; } } } // bool testFn(T prevItem, T curItem) // returns groups by sequential matching bool public static IEnumerable<IGrouping<int, T>> GroupByWhile<T>(this IEnumerable<T> src, Func<T, T, bool> testFn) => src.ScanPair(1, (kvp, cur) => testFn(kvp.Value, cur) ? kvp.Key : kvp.Key + 1) .GroupBy(kvp => kvp.Key, kvp => kvp.Value); } 的APL扫描运算符的实现(如聚合,但返回中间结果),该运算符在时间:

data

您可以根据自己的规则轻松地将var ts = TimeSpan.FromMinutes(30); var grouped = data .OrderBy(r => r.EventDate) .ThenBy(r => r.EventStart) .GroupByWhile((p,n) => p.EventDate.Date == n.EventDate.Date && n.EventStart-p.EventEnd < ts); 分组:

IEnumerable<List<EventResult>>

如果您需要.Select(rg => rg.ToList()) ,则可以添加

data

最后,假设原始IEnumerable<EventResult>的类型为require 'test_helper' class ResourcesControllerTest < ActionDispatch::IntegrationTest include Devise::Test::IntegrationHelpers setup do @recording = recordings(:first_event_rec_1) @user= users(:non_admin) end test "request index not signed in" do #@user.centre= centres(:wales) get recording_resources_url(recording_id: @recording.id), xhr: true assert_redirected_to new_user_session_url(format: 'js') end test "request index by non admin" do sign_in @user @user.centre= centres(:wales) get recording_resources_url(recording_id: @recording.id), xhr: true assert_response :forbidden assert_equal "app cont You don't have access to this resource.<BR> Are you logged into your admin sections?", flash[:error] end ################################################################################################################################ ################################################################################################################################ test "request index by full welsh admin but signed into german admin centre" do @user= users(:welsh_admin) @user.centre= centres(:germany) sign_in @user get recording_resources_url(recording_id: @recording.id), xhr: true assert_response :forbidden assert_equal "app cont You don't have access to this resource.<BR> Are you logged into your admin sections?", flash[:error] end test "request index by full welsh admin" do @user= users(:welsh_admin) @user.centre= centres(:wales) sign_in @user get recording_resources_url(recording_id: @recording.id), xhr: true assert_response :success end test "request index by full welsh admin without public role" do @user= users(:welsh_admin) @user.centre= centres(:wales) sign_in @user @user.assignments.where(centre_id:2, role_id: 3).destroy_all get recording_resources_url(recording_id: @recording.id), xhr: true assert_response :forbidden assert_equal "app cont You don't have access to this resource.<BR> Are you logged into your admin sections?", flash[:error] end ####################################################################################################################### ####################################################################################################################### test "request index by full german admin but signed into welsh admin centre " do @user= users(:german_admin) @user.centre= centres(:wales) sign_in @user get recording_resources_url(recording_id: @recording.id), xhr: true assert_response :forbidden assert_equal "app cont You don't have access to this resource.<BR> Are you logged into your admin sections?", flash[:error] end end