JavaScript Bug in Timeline Gap-Detection Algorithm

I have an algorithm that examines an array of Event (StartTime,EndTime) on a timeline that runs from Midnight->Midnight to check for any gaps. The algorithm works, but there is one configuration where it doesn't work.

Before I show the code, here's the specific case where it doesn't work: When I have a Midnight-ending Event with the same StartTime as another event which does not end at midnight. The problem here is that I add an erroneous GAP to my result, a GAP (Midnight,Midnight). It shouldn't be there in my result.

enter image description here

Now the code: All Events (including GAP Events) have a (StartTime,EndTime). I am receiving an incoming array of normal proposedEvents, and my job is to return a filled-out array of 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;

The problem here is somewhere in the main loop. After I sort by StartTime, the two adjoining events can be either in Position 0 or 1, depending on chance. Suppose that by chance I have a neighbor in the +1 position that I will examine. In that case, I fall into the ELSE, and satisfy the condition if (endTimeCurrent < 1440) because that event's End Time is not 1440 (Midnight)!

So how do I fix this?

Answer:1

This is what I understood -

  1. There are 'events' in the 24 hour window.

  2. Events have start time and end time.

  3. If there are no events then its a 'GAP'.

  4. A 'GAP' is also considered an event.

  5. There can be concurrent events (two atleast from the diagram that you have given).

  6. With regard the positions '0' and '1' I'm assuming they refer to the blue and red section in the figure in your question.

The problem statement is that you're trying to identify 'GAP' times and list them out. You've bulit the above algorithm to do that. From what I've gathered, it (identification of gaps) does not work for concurrent events (in one of the positions) since the overlap is not handled.

One way to handle overlapping events would be as follows - identify which of the events are smaller in duration and remove that event.

Also its not clear why you consider 1440 as midnight. It refers to 2:20 PM does it not ?

Regards,

Ravindra

Answer:2
  • I define a task as (something which has a start date and an end date)
  • I redefine an event as something which occured : a task started or ended

What you may want to do is travel along t, and sort your events accordingly (be they start task but also task end)

Consider the following timeline

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

Where O stands for a starting task, and X for ending

This is very similar to checking whether your html is valid: If you encounter an opening tag but presently there is no open tag ongoing, then you have a gap.

So algorithm be like:

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

You can handle the eventual gap at the beginning and at the start

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 } ]
Answer:3

I have two ajax calls that are using GET, each one having a different success function. I want to wait for the response of both requests and the execution of both success functions and then I need to ...

I have two ajax calls that are using GET, each one having a different success function. I want to wait for the response of both requests and the execution of both success functions and then I need to ...

  1. wait execution ajax javascript
  2. wait for ajax execution

I am using an object to store state and the object looks like this: { "CPC": 0, "NDP": 0, "LPC": 0, "GPC": 1, "PPC": 2 } Where all the key values are an enumerator of a generic type <T&...

I am using an object to store state and the object looks like this: { "CPC": 0, "NDP": 0, "LPC": 0, "GPC": 1, "PPC": 2 } Where all the key values are an enumerator of a generic type <T&...

  1. total number of objects in an area
  2. total number of objects in active directory
  3. total number of objects in a database
  4. total number of objects in s3 bucket
  5. total number of objects expired tsm
  6. total number of objects in a set
  7. total number of objects in a database 32 768
  8. total number of objects in a database access
  9. total number of objects expired
  10. total number of standard objects in salesforce
  11. find total number of objects in active directory
  12. get total number of objects in json
  13. total number of methods in object class in java
  14. tsm total number of objects inspected
  15. total number of objects inspected
  16. total number of objects encrypted
  17. the total number of objects

I'm trying to call a rest service from jQuery using the code below $.ajax({ type: verb, url: url, headers: header, crossDomain: true, xhrFields: { withCredentials: true ...

I'm trying to call a rest service from jQuery using the code below $.ajax({ type: verb, url: url, headers: header, crossDomain: true, xhrFields: { withCredentials: true ...

  1. php send arabic characters

I am new to coding and trying to get this problem right. I tried calling the index of the arrays but it prints out columns. Out dev environment is REPL.it, the virtual machine uses node v10.16.0 and ...

I am new to coding and trying to get this problem right. I tried calling the index of the arrays but it prints out columns. Out dev environment is REPL.it, the virtual machine uses node v10.16.0 and ...

  1. array make first element