algorithm - most efficient reservation overlap detection in javascript - Stack Overflow

I have an array of roomsEach room can have many reservationsReservations have a start and end time.I ca

I have an array of rooms

Each room can have many reservations

Reservations have a start and end time.

I can detect if a reservation overlaps another reservation with

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

I am trying trying to take the reservations, decide which over lap which, take into account multiple reservations overlapping multiple other reservations and then stack them in a diagram. like this

my current method does not account for every scenario.

One being two reservations that overlap each other and overlap the exact same other reservations.

3 overlapping reservations looking good

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

You can see that the 4th one gets one extra unit of margin from the top added then is necessary.

also if the new overlapping reservation should overlap without increasing the 'depth' of the row the depth is still increased

My question is what is the most efficient way to iterate through each room -> iterate through each reservation in that room -> pare it to each other reservation in that room only once and keep record of how many overlaps in each room

The result I am looking for is a variable for each reservation that tells me how tall it should be relative to its containing row (reservation that overlaps 2 others should be a third the height of the row) and a variable for each reservation telling me how much distance from the top of the row to the top of the reservation should be so that it has its own space on the row

each step of the solution should be possible with javascript

+25 rep for a scalable solution.

+50 rep for a full explanation of the techniques / patterns used to e to the solution.

Update: thanks you for the advice the 'greedy' solution works great for getting the tasks into the correct 'track' and if you are not concerned with the height of your row increasing then this method is all you would need. however in a gantt situation were there could be multiple overlaps the gantts row could bee unreasonably high. To bat this I need to adjust the height of each task relative to how many other tasks it overlaps.

this can be done with the 'greedy' solution fairly easily, everytime there is a conflict decrease both tasks heights. the scenario that this does not cover is the following

All the tasks are placed correctly but the task on the far left is not aware that the task it overlaps overlaps with a number of other tasks so it does not decrease its height enough. the task on the top right gets placed without knowing there is an overlapping task in 'track' 2 below it so it misses out on one height decrease and visually overlaps the task below it.

Is there a way to make each task aware of each of its overlaps' overlaps without a second pass over the data?

I have an array of rooms

Each room can have many reservations

Reservations have a start and end time.

I can detect if a reservation overlaps another reservation with

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

I am trying trying to take the reservations, decide which over lap which, take into account multiple reservations overlapping multiple other reservations and then stack them in a diagram. like this

my current method does not account for every scenario.

One being two reservations that overlap each other and overlap the exact same other reservations.

3 overlapping reservations looking good

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

You can see that the 4th one gets one extra unit of margin from the top added then is necessary.

also if the new overlapping reservation should overlap without increasing the 'depth' of the row the depth is still increased

My question is what is the most efficient way to iterate through each room -> iterate through each reservation in that room -> pare it to each other reservation in that room only once and keep record of how many overlaps in each room

The result I am looking for is a variable for each reservation that tells me how tall it should be relative to its containing row (reservation that overlaps 2 others should be a third the height of the row) and a variable for each reservation telling me how much distance from the top of the row to the top of the reservation should be so that it has its own space on the row

each step of the solution should be possible with javascript

+25 rep for a scalable solution.

+50 rep for a full explanation of the techniques / patterns used to e to the solution.

Update: thanks you for the advice the 'greedy' solution works great for getting the tasks into the correct 'track' and if you are not concerned with the height of your row increasing then this method is all you would need. however in a gantt situation were there could be multiple overlaps the gantts row could bee unreasonably high. To bat this I need to adjust the height of each task relative to how many other tasks it overlaps.

this can be done with the 'greedy' solution fairly easily, everytime there is a conflict decrease both tasks heights. the scenario that this does not cover is the following

All the tasks are placed correctly but the task on the far left is not aware that the task it overlaps overlaps with a number of other tasks so it does not decrease its height enough. the task on the top right gets placed without knowing there is an overlapping task in 'track' 2 below it so it misses out on one height decrease and visually overlaps the task below it.

Is there a way to make each task aware of each of its overlaps' overlaps without a second pass over the data?

Share Improve this question edited Jun 14, 2014 at 11:49 Mark asked Jun 10, 2014 at 8:19 MarkMark 3,2176 gold badges40 silver badges78 bronze badges 14
  • 1 You could group all reservations by their id first; that would cut down some useless iterations. – Ja͢ck Commented Jun 10, 2014 at 8:24
  • Try a database with range support: youtube./watch?v=XIcOf7r0dG4 (just an advice) – inf3rno Commented Jun 12, 2014 at 19:12
  • 1 This is a classic optimization problem from Introduction to Algorithms class. Wee Interval Scheduling – Benjamin Gruenbaum Commented Jun 12, 2014 at 19:20
  • 1 I think you should build an interval tree: en.wikipedia/wiki/Interval_tree , but ofc I know few of algorithms... Btw, the process you are looking for, is called range searching... – inf3rno Commented Jun 12, 2014 at 22:14
  • 1 Same question here: programmers.stackexchange./questions/149197/… And here is a long description of 1d range searching: wwwisg.cs.uni-magdeburg.de/ag/lehre/WS1112/GDS/slides/S8.pdf I asked the WebGL part of it here: programmers.stackexchange./questions/244860/… , maybe I get an answer... – inf3rno Commented Jun 12, 2014 at 22:49
 |  Show 9 more ments

3 Answers 3

Reset to default 4 +50

If I understand your problem correctly, here is a simple greedy scheduler that ought to work fine. With careful implementation it will be O(n log n + log t) for n reservations and t tracks required in the schedule.

Compute an array of "events" that are records of the form:

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

I am assuming a reservation looks like this:

{
  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 ]
}

The events array contains two records for each reservation: one for its start time and one for the end. Note that you can determine the kind of an event - start or end - after it's created by paring its time field with its reservation.start. If they match, it's a start event, else an end event.

After the array is created, this algorithm will greedily assign reservations to tracks:

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

When this finishes running, all the reservations can be drawn in the diagram using their start, end, and track fields.

You can control the appearance of the diagram with different choices of the search in this line:

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

I believe you want "best fit." This means find the false entry of the track I where the end time of the last reservation in list T[I] is closest to E.time. You may e up with other heuristics you like better.

Is there a way to make each task aware of each of its overlaps without a second pass over the data?

The code below is O(N^2), so it's probably not an optimal solution but I'm posting it anyway in case it's helpful. It uses a nested loop for every reservation bination to count the # of overlaps and to assign tracks greedily.

//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 bination
    for(var i=0; i<len; i++) {            
        for(var j=i+1; j<len; j++) {
            if(reservations[i].end > reservations[j].start) {
                //if there's an overlap increase the counters on both reservations
                reservations[i].overlap_count++;
                reservations[j].overlap_count++;
                //if there's an overlap on the same track
                //then change the inner reservation's 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           
        }
    }    
}

To keep track of the overlaps of overlaps, instead of storing the count, you can store a reference to the overlapped reservations in an array which will let you get the overlaps of overlaps (as deep as needed). The overlap count can be retrieved by getting the overlap array's length.

//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 bination
    for(var i=0; i<len; i++) {            
        for(var j=i+1; j<len; j++) {
            if(reservations[i].end > reservations[j].start) {
                //if there's an overlap, store a reference to the overlap
                reservations[i].overlap.push(reservations[j]);
                reservations[j].overlap.push(reservations[i]);
                //if there's an overlap on the same track
                //then change the inner reservation's 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           
        }
    }    
}

As Gene said, it is not so plicated with events.

note: This is similar to event sourcing.

calendar-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);
    };
});

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745500432a4630389.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信