javascript中最有效的预留重叠检测

时间:2014-06-10 08:19:36

标签: javascript algorithm

我有一系列房间

每个房间都可以有很多预订

预订有开始和结束时间。

我可以通过

检测预订是否与其他预订重叠
res1Start < res2End && res1End > res2Start === overlap

我正在尝试接受预订,决定哪一个多圈,考虑多个预订重叠多个其他预订,然后将它们堆叠在一个图表中。像这样

我当前的方法并不考虑每个场景。

一个是两个保留,彼此重叠并与完全相同的其他保留重叠。

3个重叠预订看起来不错

3 overlapping reservations looking good

第四个补充说,跨越与顶部相同的保留,也重叠顶部

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

你可以看到第4个从顶部添加了一个额外的边距单位,然后是必要的。

如果新的重叠预留应该重叠而不增加行的“深度”,则深度仍然增加

enter image description here

enter image description here

我的问题是迭代每个房间的最有效方法是什么 - &gt;迭代那个房间里的每个预订 - &gt;将它与该房间中的每个其他预订仅进行一次比较,并记录每个房间的重叠次数

我正在寻找的结果是每个预订的变量,它告诉我相对于其包含的行应该有多高(保留重叠其他2个应该是行的高度的三分之一)和每个预留的变量告诉我从行的顶部到预订顶部的距离应该是多少,以便它在行上有自己的空间

解决方案的每一步都应该可以使用javascript

+25代表可扩展的解决方案。

+50 rep,以获得用于解决方案的技术/模式的完整解释。

更新:感谢您的建议,'贪婪'解决方案非常适合将任务放入正确的“轨道”,如果您不关心行的高度增加,那么这种方法就是您所需要的。然而,在一个甘特的情况下,可能会有多个重叠,甘特排可能会变得不合理地高。为了解决这个问题,我需要调整每个任务相对于其重叠的其他任务的高度。

这可以通过“贪婪”解决方案轻松完成,每次冲突都会降低任务高度。这不包括的情况如下

enter image description here

所有任务都正确放置,但最左边的任务不知道它重叠的任务与许多其他任务重叠,因此它不会降低其高度。右上方的任务放置在不知道其下方的“轨道”2中有重叠任务的情况下,因此它会在一个高度减少时错过,并在视觉上重叠其下方的任务。

有没有办法让每个任务都知道每个重叠的重叠而没有第二次传递数据?

3 个答案:

答案 0 :(得分:3)

如果我正确理解你的问题,这里有一个简单的贪婪调度程序应该可以正常工作。通过仔细的实施,对于n个预订和时间表中所需的t轨道将是O(n log n + log 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 ]
}

events数组包含每个预留的两个记录:一个用于其开始时间,一个用于结束。请注意,您可以通过将其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

完成此操作后,可以使用startendtrack字段在图表中绘制所有预订。

您可以在此行中使用不同的搜索选项来控制图表的外观:

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

我相信你想要“最合适”。这意味着找到跟踪false的{​​{1}}条目,其中列表I中最后一次预订的end时间最接近T[I]。您可能会想出更好的其他启发式方法。

答案 1 :(得分:1)

  

有没有办法让每个任务都知道每个任务的重叠而没有第二次传递数据?

以下代码为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'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           
        }
    }    
}

要跟踪重叠的重叠,而不是存储计数,您可以在数组中存储重叠预留的引用,这样您就可以获得重叠的重叠(尽可能深)。可以通过获得重叠数组的长度来检索重叠计数。

//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'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           
        }
    }    
}

答案 2 :(得分:0)

正如吉恩所说,事件并不复杂。

注意:这与事件采购类似。

<强>日历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);
    };
});