Most effective reservation overlap detection in javascript

I have an array of rooms

There may be many reservations in each room.

Reservations have a start and end time.

I can determine if a reservation overlaps another reservation with

res1Start < res2End && res1End > res2Start === overlap

I try to make reservations by deciding that over a circle that take into account a lot of reservations overlapping several other reservations, and then add them to the diagram. like this

my current method does not account for every scenario.

One of them is two reservations that overlap and overlap the same other reservations.

3 overlapping reservations look good

3 overlapping reservations looking good

4th added, which covers the same reservations as the top, and also overlaps the top

4th one added that straddles the same reservations as the top one and also overlaps the top one

, , .

, "" , .

enter image description here

enter image description here

- , → → ,

, , - , , (, 2 , ) , ,

javascript

+25 rep .

+50 rep /, .

: , "" , "", , - , . gantt , gantts . , , .

"" , , . , ,

enter image description here

, , , , , . , , "" 2 , .

?

+4
3

, , . O (n log n + log t) n t , .

"", :

{ 
  time: [ contains the event time ]
  reservation: [ points to a reservation ]
}

, :

{
  room: [ reference to a room record ]
  start: [ start time of the reservation ]
  end: [ end time of the reservation ]
  track: [ initially null; set to 0... to show 
           vertical location of reservation in diagram ]
}

: . , - - time reservation.start. , , .

:

Sort the event array A on a key of time ascending
Let T be an array indexed by "track" in the output graphic of lists of reservations.
Let B be an array of booleans, also indexed by "track", initially with 0 elements.
for each event E in A
  if E is a start event
    Search B for the index I of a "false" entry
    if I is not found
      Append new a new element onto the end of each of B and T
      Set I to be the index of these new elements
    end
    Add E.reservation to the tail of T[I]
    Set E.reservation.track = I
    Set B[I] = true
  else // E is an end event
    Let I = E.reservation.track
    Set B[I] = false
  end

, , start, end track.

:

 Search B for the index I of a "false" entry

, " ". , false I, end T[I] E.time. , .

+2

?

O(N^2), , , , , . , .

//pre-processing:
//group reservations & sort grouped reservations by starting time

var groupedReservations = [
    {id:2,start:1,end:2,overlap_count:0,track:0},
    {id:3,start:2,end:3,overlap_count:0,track:0},
    {id:4,start:2,end:4,overlap_count:0,track:0},
    {id:5,start:2,end:6,overlap_count:0,track:0},    
    {id:6,start:3,end:8,overlap_count:0,track:0},
    {id:7,start:6,end:9,overlap_count:0,track:0},    
];

countOverlaps(groupedReservations);
console.log(groupedReservations);

//populates overlap & track properties
function countOverlaps(reservations) {

    var len = reservations.length;

    //check overlap for reservation combination
    for(var i=0; i<len; i++) {            
        for(var j=i+1; j<len; j++) {
            if(reservations[i].end > reservations[j].start) {
                //if there an overlap increase the counters on both reservations
                reservations[i].overlap_count++;
                reservations[j].overlap_count++;
                //if there an overlap on the same track
                //then change the inner reservation track
                if(reservations[j].track == reservations[i].track)
                    reservations[j].track++;                    
            }
            else break; 
            // break once a non-overlapping reservation is encountered
            // because the array is sorted everything afterwards will also be
            // non-overlapping           
        }
    }    
}

, , ( ). .

//pre-processing:
//group reservations & sort grouped reservations by starting time

var groupedReservations = [
    {id:2,start:1,end:2,overlap:[],track:0},
    {id:3,start:2,end:3,overlap:[],track:0},
    {id:4,start:2,end:4,overlap:[],track:0},
    {id:5,start:2,end:6,overlap:[],track:0},    
    {id:6,start:3,end:8,overlap:[],track:0},
    {id:7,start:6,end:9,overlap:[],track:0},    
];

countOverlaps(groupedReservations);
console.log(groupedReservations);

//populates overlap & track properties
function countOverlaps(reservations) {

    var len = reservations.length;

    //check overlap for reservation combination
    for(var i=0; i<len; i++) {            
        for(var j=i+1; j<len; j++) {
            if(reservations[i].end > reservations[j].start) {
                //if there an overlap, store a reference to the overlap
                reservations[i].overlap.push(reservations[j]);
                reservations[j].overlap.push(reservations[i]);
                //if there an overlap on the same track
                //then change the inner reservation track
                if(reservations[j].track == reservations[i].track)
                    reservations[j].track++;                    
            }
            else break; 
            // break once a non-overlapping reservation is encountered
            // because the array is sorted everything afterwards will also be
            // non-overlapping           
        }
    }    
}
+1

, .

note: .

-test.js

define(["calendar"], function (Calendar) {

    /*
     *       | 09 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 |
     *       |         A    A    B    B    C    C    G    |
     *       |              D    D    D    D    F         |
     *       |    E    E    E                             |
     * */

    var noon = new Date("2014-07-01T12:00:00Z").getTime();
    var hour = 60 * 60 * 1000;

    var reservations = [
        {
            label: "A",
            start: new Date(noon - 2 * hour),
            end: new Date(noon)
        },
        {
            label: "B",
            start: new Date(noon),
            end: new Date(noon + 2 * hour)
        },
        {
            label: "C",
            start: new Date(noon + 2 * hour),
            end: new Date(noon + 4 * hour)
        },
        {
            label: "D",
            start: new Date(noon - hour),
            end: new Date(noon + 3 * hour)
        },
        {
            label: "E",
            start: new Date(noon - 3 * hour),
            end: new Date(noon)
        },
        {
            label: "F",
            start: new Date(noon + 3 * hour),
            end: new Date(noon + 4 * hour)
        },
        {
            label: "G",
            start: new Date(noon + 4 * hour),
            end: new Date(noon + 5 * hour)
        }
    ];

    var calendar = new Calendar(reservations);

    describe("a simple calendar", function () {

        it("which has 7 reservations", function () {
            expect(calendar.getReservations().length).toEqual(7);
        });

        it("and 6 overlaps: AD,AE,DE,BD,CD,CF", function () {
            expect(calendar.getOverlaps().length).toEqual(6);
            var overlapLabels = [];
            calendar.getOverlaps().forEach(function (overlap) {
                var overlapLabel;
                reservationLabels = [
                    overlap[0].getLabel(),
                    overlap[1].getLabel()
                ];
                reservationLabels.sort();
                overlapLabel = reservationLabels.join("");
                overlapLabels.push(overlapLabel);
            });
            overlapLabels.sort();
            expect(overlapLabels.join(",")).toEqual("AD,AE,BD,CD,CF,DE");
        });

    });

});

calendar.js

define(function () {

    var Calendar = function (options) {
        this.reservations = options;
        this.overlapFinder = new OverlapFinder();
    };

    Calendar.prototype = {
        constructor: Calendar,
        getReservations: function () {
            return this.reservations;
        },
        getOverlaps: function () {
            return this.overlapFinder.getOverlaps(this.reservations);
        }
    };


    var OverlapFinder = function () {
    };

    OverlapFinder.prototype = {
        constructor: OverlapFinder,
        getOverlaps: function (reservations) {
            this.overlaps = [];
            this.openReservations = {};
            this.createEvents(reservations).forEach(function (event) {
                var reservation = event.getReservation();
                if (event instanceof ReservationStart)
                    this.startReservation(reservation);
                else {
                    this.endReservation(reservation);
                    this.extractOverlapsFromOpenReservations(reservation);
                }
            }.bind(this));
            return this.overlaps;
        },
        createEvents: function (reservations) {
            var events = [];
            reservations.forEach(function (reservation) {
                events.push(
                    new ReservationStart(reservation),
                    new ReservationEnd(reservation)
                );
            });
            events.sort(function (a, b) {
                return a.getTime() - b.getTime();
            });
            return events;
        },
        startReservation: function (reservation) {
            this.openReservations[reservation.getId()] = reservation;
        },
        endReservation: function (reservation) {
            delete(this.openReservations[reservation.getId()]);
        },
        extractOverlapsFromOpenReservations: function (reservation) {
            for (var id in this.openReservations) {
                if (!this.openReservations.hasOwnProperty(id))
                    continue;
                var openReservation = this.openReservations[id];
                if (reservation.getEndTime() > openReservation.getStartTime())
                    this.overlaps.push([reservation, openReservation]);
            }
        }
    };


    var nextReservationId = 1;

    var Reservation = function (options) {
        this.options = options;
        this.id = nextReservationId++;
    };

    Reservation.prototype = {
        constructor: Reservation,
        getId: function () {
            return this.id;
        },
        getStartTime: function () {
            return this.options.start;
        },
        getEndTime: function () {
            return this.options.end;
        },
        getLabel: function () {
            return this.options.label;
        }
    };


    var ReservationEvent = function (reservation) {
        this.reservation = reservation;
    };
    ReservationEvent.prototype = {
        constructor: ReservationEvent,
        getReservation: function () {
            return this.reservation;
        }
    };


    var ReservationStart = function (reservation) {
        ReservationEvent.apply(this, arguments);
    };
    ReservationStart.prototype = Object.create(ReservationEvent.prototype);
    ReservationStart.prototype.constructor = ReservationStart;
    ReservationStart.prototype.getTime = function () {
        return this.reservation.getStartTime();
    };


    var ReservationEnd = function (reservation) {
        ReservationEvent.apply(this, arguments);
    };
    ReservationEnd.prototype = Object.create(ReservationEvent.prototype);
    ReservationEnd.prototype.constructor = ReservationEnd;
    ReservationEnd.prototype.getTime = function () {
        return this.reservation.getEndTime();
    };


    return function (reservationOptionsList) {
        var calendarOptions = [];
        reservationOptionsList.forEach(function (reservationOptions) {
            calendarOptions.push(new Reservation(reservationOptions));
        });
        return new Calendar(calendarOptions);
    };
});
0

Source: https://habr.com/ru/post/1543961/


All Articles