我有一个从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 :(
}
}
完成后,看起来会像这样:
如果有人可以提供帮助,我将不胜感激。谢谢!
答案 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
。