在C#中,将一个日期时间集合聚合并转换为另一个日期时间集合的最佳方法是什么?

在C#中,将一个日期时间集合聚合并转换为另一个日期时间集合的最佳方法是什么?,c#,datetime,collections,C#,Datetime,Collections,我在C#中有一个小小的日历工具,我正在试图弄清楚如何从一个DateTime对象数组转换到另一个DateTime对象数组。详情如下: 我从DateTime对象的集合开始 IEnumerable<DateTime> slots = GetSlots(); IEnumerable slots=GetSlots(); 其中,每个DateTime表示一个可用时段的开始时间(想想日历中的开放时段)所有时段都为30分钟这是给定的。例如: var slots = new List<Dat

我在C#中有一个小小的日历工具,我正在试图弄清楚如何从一个DateTime对象数组转换到另一个DateTime对象数组。详情如下:

我从DateTime对象的集合开始

 IEnumerable<DateTime> slots = GetSlots();
IEnumerable 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));
var slots=新列表()
slots.Add(DateTime.Today+newtimespan(5,00,0));
slots.Add(DateTime.Today+newtimespan(9,00,0));
slots.Add(DateTime.Today+newtimespan(9,30,0));
slots.Add(DateTime.Today+newtimespan(10,00,0));
slots.Add(DateTime.Today+newtimespan(10,30,0));
slots.Add(DateTime.Today+newtimespan(11,00,0));
slots.Add(DateTime.Today+newtimespan(16,30,0));
在上面的例子中,这意味着我是自由的:

  • 从5:00到5:30
  • 从9:00到9:30
  • 从9:30到10:00
  • 从10:00到10:30
  • 从10:30到11:00
  • 从11:00到11:30
  • 从4:30到5:00
  • 因为我从集合中的项目中提取时间作为开始时间,只需添加30分钟,这被认为是一个免费时段

    我现在需要一个更大的时间窗口(让我们使用2小时),并找出我有多少2小时的空闲时间,所以我现在需要将这个日期数组“合并”到更大的存储桶中。考虑到更大的存储空间是2小时(120分钟),我想要一个这样的函数

    IEnumerable<DateTime> aggregateArray = MergeIntoLargerSlots(slots, 120);
    
    IEnumerable aggregateArray=mergeinto大插槽(插槽,120);
    
    基本上,我必须在上面的插槽数组中循环,并“合并”排列在每个插槽旁边的项目,以生成更大的存储桶。如果任何合并项的长度为2小时,则应在结果数组中显示为一个条目。使用上面的示例,生成的aggregateArray在集合中有2个项目,它们的时间为:

    • 上午9点(因为我有一个从上午9点到11点(120分钟)的空闲时间)
    • 上午9:30(因为我有一个从上午9:30-11:30(120分钟)的空闲时间)
    注:30分钟“块”是最小的间隔,因此不需要包括9:05到11:05作为示例

    因此,考虑到前面的阵列,我一天有两个2小时的空闲时间窗口


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

    假设您的原始列表已排序(如果未排序,则按原样排序),您可以循环查看原始列表并检查相邻项是否连续(即开始时间的距离是否正好为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;
        }
    }
    
    var twowhourslots=new List();
    int continuenceslotscont=0;
    日期时间?上一个时隙;
    foreach(插槽中的DateTime smallSlotStart){
    if(上一个slot.HasValue){
    if(smallSlotStart-previousSlot.Value==newtimespan(0,30,0)){
    连续LotusScont++;
    }否则{
    连续LOTSCONT=0;
    }
    }
    如果(连续的LOTSCONT==4){
    添加(smallSlotStart-newtimespan(1,30,0));
    连续批次=0;
    previousSlot=null;
    }否则{
    previousSlot=smallSlotStart;
    }
    }
    
    需要注意的一些事项:

    • 我正在对
      DateTime
      值使用算术运算符。请查看以了解更多信息;它们可以做一些方便的事情,通常可以让您自动处理值
    • 我多次使用a。这就是三个数字的含义
    • 我已经声明了
      previousSlot
      ,一个跟踪最后查看的插槽(与当前插槽进行比较)的变量,称为
      DateTime?
      (如果不确定可为空的类型,请再次检查).这是因为在
      foreach
      循环的第一次迭代中,没有以前的插槽可供查看,循环的行为必须不同
    • 同样地,
      previousSlot
      设置为
      null
      当我们找到一个2小时的时隙时,因为找到的2小时时隙的最后30分钟时隙不应计入下一个可能的2小时时隙
    • 一旦找到四个连续的30分钟时段,则从最后一个时段的开始减去一小时三十分钟。这是因为最后一个30分钟时段开始后的三十分钟将是最终2小时时段的一部分

    这只在半小时的时间间隔内有效,如果需要的话,你可以想出办法让它对其他人有效

    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;   
    }
    
    公共列表合并到更大的插槽(列表插槽,整数分钟)
    {
    整数计数=分钟/30;
    List retVal=新列表();
    foreach(插槽中的日期时间插槽)
    {
    DateTime end=slot.AddMinutes(分钟);
    if(slots.Where(x=>x>=slot&&x
    下面是我解决问题方法的简要说明;我把分钟数和插槽列表都记下来。我加上分钟数得到一个结束时间,这个时间给了我一个范围。从那里,我使用
    Where
    操作符来产生和
    IEnumerable
    来自
    slots
    的插槽。我将结果与
    count我通过执行
    minutes/slotLength
    得到的变量,如果数字匹配,那么您就有了必要的插槽
        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;
        }
    
    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;
            }
        }
    }   
    
    //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;
    }