我在C#中有一个小日历工具,我试图弄清楚如何从一个DateTime对象数组转换到另一个。以下是详细信息:
我从DateTime对象的集合
开始 IEnumerable<DateTime> slots = GetSlots();
其中每个DateTime表示可用插槽的开始时间(想想日历中的空位)所有插槽都是30分钟这是给定的。例如:
var slots = new List<DateTime>()
slots.Add(DateTime.Today + new TimeSpan(5,00, 0));
slots.Add(DateTime.Today + new TimeSpan(9,00, 0));
slots.Add(DateTime.Today + new TimeSpan(9,30, 0));
slots.Add(DateTime.Today + new TimeSpan(10,00, 0));
slots.Add(DateTime.Today + new TimeSpan(10,30, 0));
slots.Add(DateTime.Today + new TimeSpan(11,00, 0));
slots.Add(DateTime.Today + new TimeSpan(16,30, 0));
在上面的例子中,它意味着我是自由的:
因为我从集合中的项目中抽出时间作为开始时间,只需添加30分钟即可,这被视为免费插槽。
我现在需要占用更大的时间窗口(让我们使用2小时),并找出我有多少2小时的免费插槽,所以我现在需要将这个日期数组和“合并”成更大的存储桶。鉴于更大的桶是2小时(120分钟),我想要一个像这样的功能
IEnumerable<DateTime> aggregateArray = MergeIntoLargerSlots(slots, 120);
我基本上必须循环上面的插槽阵列并“合并”每个旁边排列的项目以制作更大的存储桶。如果任何合并项目长达2小时,那么它应该显示为结果数组中的条目。使用上面的示例,生成的aggregateArray将在集合中有2个具有时间的项目:
注意: 30分钟“块”是最小的间隔所以不需要包括9:05到11:05作为例子
因此,考虑到之前的数组,我在当天有两个2小时的免费时间窗口
我正在努力弄清楚这个MergeIntoLargerSlots函数是如何工作的,所以我希望得到一些关于如何解决这个问题的建议。
答案 0 :(得分:4)
这仅适用于半小时的间隔,如果需要,您可以找出让其他人工作的方法。
public List<DateTime> MergeIntoLargerSlots(List<DateTime> slots, int minutes)
{
int count = minutes/30;
List<DateTime> retVal = new List<DateTime>();
foreach (DateTime slot in slots)
{
DateTime end = slot.AddMinutes(minutes);
if (slots.Where(x => x >= slot && x < end).Count() == count)
{
retVal.Add(slot);
}
}
return retVal;
}
以下是我解决问题的方法的简要说明;我接受了会议记录和老虎机列表。我添加分钟以获得结束时间,这给了我范围。在那里,我使用Where
运算符来生成IEnumerable<DateTime>
和slots
,其中包含该范围内的广告位。我将结果与我从count
得到的minutes/slotLength
变量进行比较,如果数字匹配则你有必要的插槽。对于您的样本数据,上午9点Where
的结果将包含4个值; 9,9:10,10:10:30,计数是4,120 / 30 == 4,因此被添加到retVal
。 9:30也是如此,没有其他时间会被退回。
答案 1 :(得分:0)
假设您的原始列表已排序(如果不是,请将其设置为),您可以遍历原始列表并检查相邻项是否是连续的(即开始时间是否恰好为30分钟) )。始终跟踪当前连续时隙系列中的第一项 - 一旦达到其中四个(连续4个30分钟时段加上可能的两小时时段;其他时段大小显然需要不同因素),保存一个新的将两小时时间段放入结果列表中,并更新对当前连续项目序列开头的引用。
未经测试,请将此视为伪代码:
var twoHourSlots = new List<DateTime>();
int consecutiveSlotsCount = 0;
DateTime? previousSlot;
foreach (DateTime smallSlotStart in slots) {
if (previousSlot.HasValue) {
if (smallSlotStart - previousSlot.Value == new TimeSpan(0, 30, 0)) {
consecutiveSlotsCount++;
} else {
consecutiveSlotsCount = 0;
}
}
if (consecutiveSlotsCount == 4) {
twoHourSlots.Add(smallSlotStart - new TimeSpan(1, 30, 0));
consecutiveSlots = 0;
previousSlot = null;
} else {
previousSlot = smallSlotStart;
}
}
有些注意事项:
DateTime
值上使用算术运算符。查看docs了解详情;他们做了便利的事情,并且经常让你自动使用TimeSpan
值。TimeSpan
constructor that takes hours, minutes and seconds。这就是三个数字的含义。previousSlot
,这是一个跟踪最后一个插槽(与当前插槽进行比较)的变量,为DateTime?
(再次,如果您检查docs不确定什么是可空类型)。这是因为在foreach
循环的第一次迭代中,没有先前的槽要查看,循环必须表现不同。previousSlot
设置为null
,因为找到的2小时广告位的最后30分钟广告位不应计入下一个广告位2小时的插槽。答案 2 :(得分:0)
Evan打败了我,并用一个更少的循环做到了,但这是我的解决方案:
private List<DateTime> MergeArray(List<DateTime> slots, int minutes)
{
var segments = minutes / InitialSegment;
var validSegments = new List<DateTime>();
foreach (var slot in slots.OrderBy(x => x))
{
var validSegment = true;
for (var i = 0; i < segments-1; i++)
{
var next = slot.AddMinutes(InitialSegment * (i + 1));
if (slots.All(x => x != next))
{
validSegment = false;
break;
}
}
if (validSegment)
validSegments.Add(slot);
}
return validSegments;
}
答案 3 :(得分:0)
我会创建一个TimeInterval
课程,因为你可以用它做很多其他有趣的事情。
public sealed class TimeInterval
{
public DateTime Start { get; private set; }
public DateTime End { get { return Start.AddMinutes(Duration); } }
public double Duration { get; private set; }
public TimeInterval(DateTime start, int duration)
{
Start = start;
Duration = duration;
}
public IEnumerable<TimeInterval> Merge(TimeInterval that)
{
if(that.Start >= this.Start && that.Start <= this.End)
{
if(that.End > this.End)
Duration += (that.Duration - (this.End - that.Start).TotalMinutes);
yield return this;
}
else
{
yield return this;
yield return that;
}
}
}
这是一个O(n)合并算法,适用于任意大小的间隔(以分钟为单位)。
//the `spans` parameter must be presorted
public IEnumerable<TimeInterval> Merge(IEnumerable<TimeInterval> spans, int duration)
{
var stack = new Stack<TimeInterval>();
stack.Push(spans.First());
foreach (var span in spans.Skip(1))
foreach(var interval in stack.Pop().Merge(span)) //this enumeration is guaranteed to have either one element or two elements.
stack.Push(interval);
return from interval in stack where interval.Duration >= duration select interval;
}