【问题标题】:Bug in Timeline Gap-Detection Algorithm时间线间隙检测算法中的错误
【发布时间】:2019-10-18 20:19:30
【问题描述】:

我有一个算法,它在从 Midnight->Midnight 运行的时间线上检查 Event (StartTime,EndTime) 的数组,以检查是否存在任何间隙。该算法有效,但有一种配置无效。

在我展示代码之前,这里是它不起作用的特定情况:当我有一个午夜结束事件,其 StartTime 与另一个未在午夜结束的事件相同。这里的问题是我在结果中添加了错误的 GAP,GAP (Midnight,Midnight)它不应该出现在我的结果中。

现在代码:所有事件(包括 GAP 事件)都有一个 (StartTime,EndTime)。我收到一个正常的proposedEvents 传入数组,我的工作是返回一个填充的preparedGapEvents 数组。

function schedulerPrepareGapEvents(proposedEvents) {

var preparedGapEvents = [];

// First sort proposed events by StartTime
proposedEvents.sort(sortObjectsByStartTime); // sort by StartTime of the event

// If there are no proposed events, exit immediately with an empty result
if (proposedEvents.length == 0) {
    return preparedGapEvents;
}   

// Manually add first gap, if it exists: Sorted Proposed Event #1 not starting at 12:00am
var startTimeFirst = getTimestampFromString(proposedEvents[0].startTime);
if (startTimeFirst > 0) {
    preparedGapEvents.push({"gapEventID" : "event-GAP" + generateUniqueID(), 
                            "gapEventColor" : globalGapEventColor, 
                            "gapEventStartTimestamp" : 0, 
                            "gapEventEndTimestamp" : startTimeFirst});      
}

// Initially lastMaxEndTime is the End Time of 1st Event
var lastMaxEndTime = getTimestampFromString(proposedEvents[0].endTime); 

// Main Event Traversal Loop
jQuery.each(proposedEvents, function(index, item) {     

    // Get the current proposed event's StartTime/EndTime in the loop
    var startTimeCurrent = getTimestampFromString(item.startTime);
    var endTimeCurrent = getTimestampFromString(item.endTime);

    // Next neighboring proposed event
    var nextEvent = proposedEvents[index+1];

    // If Next Proposed Event exists
    if (nextEvent != null) {
        var startTimeNext = getTimestampFromString(nextEvent.startTime);
        var endTimeNext = getTimestampFromString(nextEvent.endTime);

        if (startTimeNext > lastMaxEndTime) { // Gap detected! 
            preparedGapEvents.push({"gapEventID" : "event-GAP" + generateUniqueID(), 
                                    "gapEventColor" : globalGapEventColor,
                                    "gapEventStartTimestamp" : lastMaxEndTime, 
                                    "gapEventEndTimestamp" : startTimeNext});               
        }

        // Keep track of the current MAX EndTime: either this new event's EndTime or the previous Max EndTime
        lastMaxEndTime = Math.max(lastMaxEndTime, endTimeNext);
    }
    else {
        // Last Proposed Event (no next neighbor): its End Time must be at the 24-hr END mark, otherwise a gap
        if (endTimeCurrent < 1440) {
            preparedGapEvents.push({"gapEventID" : "event-GAP" + generateUniqueID(), 
                                    "gapEventColor" : globalGapEventColor,
                                    "gapEventStartTimestamp" : lastMaxEndTime, 
                                    "gapEventEndTimestamp" : 1440});
        }
    }
}); 

return preparedGapEvents;

这里的问题出在主循环的某个地方。在我按StartTime 排序后,两个相邻的事件可以在位置 0 或 1,取决于机会。假设碰巧我有一个处于 +1 位置的邻居,我将对其进行检查。在这种情况下,我属于 ELSE,并且满足条件if (endTimeCurrent &lt; 1440),因为该事件的结束时间不是 1440(午夜)!

那么我该如何解决这个问题?

【问题讨论】:

    标签: javascript algorithm sorting


    【解决方案1】:

    这是我理解的——

    1. 24 小时窗口中有“事件”。

    2. 事件有开始时间和结束时间。

    3. 如果没有事件,则为“GAP”。

    4. “GAP”也被视为一个事件。

    5. 可能有并发事件(您给出的图表中至少有两个)。

    6. 关于位置“0”和“1”,我假设它们指的是您问题中图中的蓝色和红色部分。

    问题陈述是您正在尝试识别“GAP”时间并将其列出。 您已经构建了上述算法来做到这一点。 根据我收集到的信息,它(识别间隙)不适用于并发事件(在其中一个位置),因为没有处理重叠。

    处理重叠事件的一种方法如下 - 确定哪些事件的持续时间更短,然后删除该事件。

    还不清楚为什么您将 1440 视为午夜。 它指的是下午 2:20 不是吗?

    问候,

    拉文德拉

    【讨论】:

    • 嗨 - 处理重叠,不是问题。午夜 = 1440 分钟。 (不是 14:40);下午 2:20 将 14*60+20 分钟。我对“0/1”的引用不是图片,而是我检查下一个邻居的代码。问题是我添加了一个无关的 GAP 事件 (Midnight,Midnight) - 换句话说,(1440,1440) - 当我有上面的布局时。
    • 您可以在 else 块中尝试 if (endTimeCurrent &lt; 1440 &amp;&amp; lastMaxEndTime != 1140)...
    • 更正 - 在 else 块中尝试:if (endTimeCurrent &lt; 1440 &amp;&amp; lastMaxEndTime != 1440)...
    【解决方案2】:
    • 我将task 定义为(具有start 日期和end 日期的东西)
    • 重新定义 event 为发生的事情:任务开始或结束

    您可能想要做的是沿着 t 移动,并相应地对您的 事件 进行排序(无论它们开始任务还是结束任务)

    考虑以下时间线

    ---------------->t
     O----X
       O-----X
       O------------X
    

    其中O代表开始任务,X代表结束

    这与检查你的 html 是否有效非常相似:如果你遇到一个开始标签,但目前没有打开标签正在进行,那么你就有一个间隙。

    所以算法是这样的:

    events = []
    forall tasks as start, end:
        events.push({start: true, at: start}, {start:false, at:end})
    
    sort the events as:
        first by t time
        in case two events have the same t,
        opening events should come before closing ones
    forall events:
        when a start task: 
            if openTagCounter == 0 //and different than init
                gaps.push(lastClosedAt, task.start)
            openTagCounter++
        when a end task:
            openTagCounter--
            if openTagCounter == 0
                lastClosedAt = task.end
    

    你可以在开始和开始时处理最终的差距

    if startDay < events[0].start
        gaps.push(guess what)
    if last(events).end < endDay
        gaps.push(same)
    

    function getGaps(startDay, endDay, tasks){
        let events = tasks.flatMap(t=>{
            return [{ref: t, at: t.start, start:true},{ref: t, at: t.end, start:false}]
        },[]);
    
        events.sort((a,b)=>{
            if(a.at!=b.at) return a.at-b.at;
            return a.start?-1:0;
        });
    
        let gaps = [];
        let lastClosed = 0;//lastClosed only truthy if start of a gap
        let anyOpened = 0;
        events.forEach(ev=>{
            if(ev.start){
                anyOpened++;
                if(lastClosed){
                    gaps.push({start: lastClosed, end: ev.at});
                    lastClosed = false;
                }
            }else{
                anyOpened--;
                if(anyOpened == 0){
                    lastClosed = ev.at;
                }
            }
        });
    
        //finally handle the first gap, and the last gap
        if(startDay != events[0].at){
            gaps.push({start: startDay, end: events[0].at})
        }
        if(endDay != events[events.length-1].at){
            gaps.push({start: events[events.length-1].at, end: endDay})   
        }
        return gaps;
    }
    console.log(getGaps(0, 24, [
        {start:23, end:23.5},
        {start:23, end:24},
    ]))
    //[ { start: 0, end: 23 } ]
    console.log(getGaps(0, 24, [
        {start:23, end:24},
        {start:23, end:23.5},
    ]))
    //[ { start: 0, end: 23 } ]
    console.log(getGaps(0, 24, [
        {start:23, end:23.7},
        {start:23, end:23.5},
    ]))
    //[ { start: 0, end: 23 }, { start: 23.7, end: 24 } ]
    console.log(getGaps(0, 24, [
        {start:23, end:23.5},
        {start:23.5, end:23.7},
        {start:23, end:23.5},
        {start:23.5, end:23.7},
    ]))
    //[ { start: 0, end: 23 }, { start: 23.7, end: 24 } ]

    【讨论】:

      【解决方案3】:

      这是另一种方法...它通过仅检查startTime 是否大于lastMaxEndTime 并相应地添加间隙来简化schedulerPrepareGapEvents 函数主循环。然后,一旦循环完成,该函数会检查lastMaxEndTime 和午夜(1440 分钟)之间是否存在间隙。

      // Filler functions to support testing...
      var uid = 1;
      function generateUniqueID() { return uid++};
      function getTimestampFromString(x) {return x};
      var globalGapEventColor = 123;
      
      // Function to seek gaps between events.
      function schedulerPrepareGapEvents(proposedEvents) {
      
        // First sort proposed events by StartTime
        proposedEvents.sort((a, b) => getTimestampFromString(a.startTime) - getTimestampFromString(b.startTime)); // sort by StartTime of the event
      
        // Initialize lastMaxEndTime to the beginning of the time interval
        var lastMaxEndTime = 0;
        var preparedGapEvents = [];
      
        // Main Event Traversal Loop
        for (var index = 0; index < proposedEvents.length; index++) {
      
          var item = proposedEvents[index];
      
          // Get the current proposed event's StartTime/EndTime in the loop
          var startTimeCurrent = getTimestampFromString(item.startTime);
          var endTimeCurrent = getTimestampFromString(item.endTime);
      
          // If the startTime is greater than the last max end time, add as a gap.
          if (lastMaxEndTime < startTimeCurrent) { // Gap detected! 
              preparedGapEvents.push({"gapEventID" : "event-GAP" + generateUniqueID(), 
                                      "gapEventColor" : globalGapEventColor,
                                      "gapEventStartTimestamp" : lastMaxEndTime, 
                                      "gapEventEndTimestamp" : startTimeCurrent});               
          }
      
          lastMaxEndTime = Math.max(lastMaxEndTime, endTimeCurrent);
        }
      
        // Finally, add any gap up to midnight.
        if (lastMaxEndTime < 1440) {
            preparedGapEvents.push({"gapEventID" : "event-GAP" + generateUniqueID(), 
                                    "gapEventColor" : globalGapEventColor,
                                    "gapEventStartTimestamp" : lastMaxEndTime, 
                                    "gapEventEndTimestamp" : 1440});
        }
              
        return preparedGapEvents;
      }
      
      // Run test...
      console.log("Test 1:");
      var pe0 = [{startTime: 22*60, endTime: 24*60}, {startTime: 22*60, endTime: 23*60}];
      console.log( schedulerPrepareGapEvents( pe0 ) );
      
      console.log("Test 2:");
      var pe1 = [{startTime: 22*60, endTime: 23*60}, {startTime: 22*60, endTime: 24*60}];
      console.log( schedulerPrepareGapEvents( pe1 ) );

      希望这会有所帮助...

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-13
        • 2012-10-18
        • 2013-04-18
        • 2020-03-14
        相关资源
        最近更新 更多