在C#中,将一个DateTime集合聚合和转换为另一个DateTime集合的最佳方法是什么?

时间:2014-01-09 19:10:13

标签: c# datetime collections

我在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));

在上面的例子中,它意味着我是自由的:

  1. 从5:00到5:30
  2. 从9:00到9:30
  3. 从9:30到10:00
  4. 从10:00到10:30
  5. 从10:30 - 11:00
  6. 从11:00到11:30
  7. 从4:30 - 5:00
  8. 因为我从集合中的项目中抽出时间作为开始时间,只需添加30分钟即可,这被视为免费插槽。

    我现在需要占用更大的时间窗口(让我们使用2小时),并找出我有多少2小时的免费插槽,所以我现在需要将这个日期数组和“合并”成更大的存储桶。鉴于更大的桶是2小时(120分钟),我想要一个像这样的功能

    IEnumerable<DateTime> aggregateArray = MergeIntoLargerSlots(slots, 120);
    

    我基本上必须循环上面的插槽阵列并“合并”每个旁边排列的项目以制作更大的存储桶。如果任何合并项目长达2小时,那么它应该显示为结果数组中的条目。使用上面的示例,生成的aggregateArray将在集合中有2个具有时间的项目:

    • 上午9点(因为我从上午9点到11点(120分钟)有一个免费插槽。
    • 上午9:30(因为我在上午9:30-11:30(120分钟)有一个免费插槽。

    注意: 30分钟“块”是最小的间隔所以不需要包括9:05到11:05作为例子

    因此,考虑到之前的数组,我在当天有两个2小时的免费时间窗口

    我正在努力弄清楚这个MergeIntoLargerSlots函数是如何工作的,所以我希望得到一些关于如何解决这个问题的建议。

4 个答案:

答案 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:3​​0,计数是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循环的第一次迭代中,没有先前的槽要查看,循环必须表现不同。
  • 同样,当我们找到一个2小时的广告位时,previousSlot设置为null,因为找到的2小时广告位的最后30分钟广告位不应计入下一个广告位2小时的插槽。
  • 一旦找到四个连续的30分钟的时段,从最后一个开始减去1小时30分钟。这是因为在最后30分钟开始后的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;
}