I'm looking for an algorithm to efficiently place all-day/multi-day event banners, much like the month view in Outlook or Google Calendar. I have a number of events with a begin and end date, ordered by increasing begin (and then end) date (or any other order you please, I'm gathering events from a database table). I would like to minimize the average amount of vertical space used up, because after the event banners I will need to place other events just for that day (these always come after the banners for a given date). So, for example, if I had two events, one 1/10-1/11 and one 1/11-1/15, I would prefer to arrange them like so (each column is a single day):
我正在寻找一种算法来有效地放置全天/多日活动横幅,就像Outlook或Google日历中的月视图一样。我有许多具有开始和结束日期的事件,通过增加开始(然后结束)日期(或任何其他您喜欢的顺序,我从数据库表中收集事件)来排序。我想最大限度地减少用完的垂直空间的平均数量,因为在事件横幅之后我将需要在当天放置其他事件(这些事件总是在给定日期的横幅之后)。所以,例如,如果我有两个事件,一个1 / 10-1 / 11和一个1 / 11-1 / 15,我宁愿像这样安排它们(每列是一天):
bbbbb
aa
and not like:
而不是像:
aa
bbbbb
because when I add the events just for the day (x, y, and z), I can do this (I would prefer the first, do not want the second):
因为当我只为当天(x,y和z)添加事件时,我可以这样做(我更喜欢第一个,不想要第二个):
bbbbb vs. aa
aa xyz bbbbb
xyz
But it isn't as simple as placing the longer events first, because with 1/10-1/11, 1/13-1/14, and 1/11-1/13, I would want:
但它并不像首先放置较长的事件那么简单,因为1 / 10-1 / 11,1 / 13-1 / 14和1 / 11-1 / 13,我希望:
aa cc
bbb
as opposed to:
而不是:
bbb
aa cc
because this would allow for events x and y:
因为这会允许事件x和y:
aa cc vs. bbb
xbbby aa cc
x y
And of course I would prefer to do this in one pass. For the data structure, I'm currently using a map from date to list, where for each day of an event I add the event to the corresponding list. So a three-day event appears in three lists,each one under one of the days in the map. This is a convenient structure for transforming the result into visual output, but I'm open to other data structures as well. I'm currently using a greedy algorithm, where I just add each event in order, but that can produce unwanted artifacts like:
当然,我宁愿一次性做到这一点。对于数据结构,我目前正在使用从日期到列表的地图,对于事件的每一天,我将事件添加到相应的列表中。因此,为期三天的活动会出现在三个列表中,每个列表都在地图中的一天内。这是一个方便的结构,用于将结果转换为可视输出,但我也对其他数据结构开放。我目前正在使用贪婪算法,我只是按顺序添加每个事件,但这会产生不需要的伪像,如:
aa ccc
bbbbb
dd
eeeeeeeeeeeeeeeee
This wastes a lot of space for most of the "e" event days.
这大多数“e”事件日都浪费了很多空间。
Any ideas?
2 个解决方案
#1
Here is a high-level sketch of one possible solution (using day-of-week integers instead of full-blown dates). This interface:
以下是一个可能解决方案的高级草图(使用星期几整数而不是完整日期)。这个界面:
public interface IEvent {
public abstract int getFirst(); // first day of event
public abstract int getLast(); // last day of event
public abstract int getLength(); // total number of days
public abstract char getLabel(); // one-char identifier
// true if this and that have NO days in common
public abstract boolean isCompatible(IEvent that);
// true if this is is compatible with all events
public abstract boolean isCompatibleWith(Collection<IEvent> events);
}
must be implemented to use the algorithm expressed in the layout
method below.
必须实现使用下面布局方法中表示的算法。
In addition, the concrete class must implement Comparable
to create a natural order where longer events precede shorter events. (My sample implementation for the demo below used an order of descending length, then ascending start date, then ascending label.)
此外,具体类必须实现Comparable以创建自然顺序,其中较长事件在较短事件之前。 (我下面演示的示例实现使用了一个降序长度的顺序,然后是升序开始日期,然后是升序标签。)
The layout
method takes a collection of IEvent
instances and returns a Map
that assigns to each row in the presentation the set of events that can be shown in that row.
layout方法接受IEvent实例的集合,并返回一个Map,该Map为演示文稿中的每一行分配可以在该行中显示的事件集。
public Map<Integer,Set<IEvent>> layout(Collection<IEvent> events) {
Set<IEvent> remainingEvents = new TreeSet<IEvent>(events);
Map<Integer,Set<IEvent>> result = new TreeMap<Integer,Set<IEvent>>();
int day = 0;
while (0 < remainingEvents.size()) {
Set<IEvent> dayEvents = new TreeSet<IEvent>();
for(IEvent e : remainingEvents) {
if (e.isCompatibleWith(dayEvents)) {
dayEvents.add(e);
}
}
remainingEvents.removeAll(dayEvents);
result.put(day, dayEvents);
++day;
}
return result;
}
Each row is composed by selecting the longest remaining event and progressively selecting all additional events (in the order described above) that are compatible with previously-selected events for the current row. The effect is that all events "float" upward as far as possible without collision.
通过选择最长的剩余事件并逐步选择与当前行的先前选择的事件兼容的所有附加事件(按照上述顺序)来组成每一行。结果是所有事件尽可能“浮动”而没有碰撞。
The following demo shows the two scenarios in your question, along with a randomly-created set of events.
以下演示显示了您问题中的两个方案,以及随机创建的一组事件。
Event collection:
x(1):4
b(5):2..6
y(1):5
a(2):1..2
z(1):6
Result of layout:
0 -> {b(5):2..6}
1 -> {a(2):1..2, x(1):4, y(1):5, z(1):6}
Visual presentation:
bbbbb
aa xyz
Event collection:
x(1):1
b(3):2..4
a(2):1..2
c(2):4..5
y(1):5
Result of layout:
0 -> {b(3):2..4, x(1):1, y(1):5}
1 -> {a(2):1..2, c(2):4..5}
Visual presentation:
xbbby
aa cc
Event collection:
f(2):1..2
h(2):1..2
d(4):1..4
e(4):2..5
c(1):6
a(2):5..6
g(4):2..5
b(2):0..1
Result of layout:
0 -> {d(4):1..4, a(2):5..6}
1 -> {e(4):2..5, b(2):0..1, c(1):6}
2 -> {g(4):2..5}
3 -> {f(2):1..2}
4 -> {h(2):1..2}
Visual presentation:
ddddaa
bbeeeec
gggg
ff
hh
#2
I think in a situation like this, you're much better off making sure your data is organized properly first and then rendering it. I know you want a single pass, but I think the results would be alot better.
我认为在这样的情况下,最好先确保数据正确组织然后再渲染。我知道你想要一次通过,但我认为结果会更好。
For instance, organize the data into the lines you'll need to have for a given day and organize the events the best way possible, starting with the longest events (don't need to be displayed first, but they do need to be organized first) and moving down to the shortest events. This will allow you to render your output accordingly not wasting any space, and avoiding those "e" event days. Additionally, then:
例如,将数据组织到您需要的特定日期的行中,并以最佳方式组织事件,从最长的事件开始(不需要先显示,但需要组织它们)首先)并向下移动到最短的事件。这将允许您相应地渲染输出而不会浪费任何空间,并避免那些“e”事件日。另外,那么:
bbb
aa cc
or
aa cc
bbb
won't matter because x
and y
can always go on either side of bbb
or even between aa
and cc
无关紧要,因为x和y总是可以在bbb的两边,甚至在aa和cc之间
I hope you find this helpful.
我希望你觉得这有帮助。
#1
Here is a high-level sketch of one possible solution (using day-of-week integers instead of full-blown dates). This interface:
以下是一个可能解决方案的高级草图(使用星期几整数而不是完整日期)。这个界面:
public interface IEvent {
public abstract int getFirst(); // first day of event
public abstract int getLast(); // last day of event
public abstract int getLength(); // total number of days
public abstract char getLabel(); // one-char identifier
// true if this and that have NO days in common
public abstract boolean isCompatible(IEvent that);
// true if this is is compatible with all events
public abstract boolean isCompatibleWith(Collection<IEvent> events);
}
must be implemented to use the algorithm expressed in the layout
method below.
必须实现使用下面布局方法中表示的算法。
In addition, the concrete class must implement Comparable
to create a natural order where longer events precede shorter events. (My sample implementation for the demo below used an order of descending length, then ascending start date, then ascending label.)
此外,具体类必须实现Comparable以创建自然顺序,其中较长事件在较短事件之前。 (我下面演示的示例实现使用了一个降序长度的顺序,然后是升序开始日期,然后是升序标签。)
The layout
method takes a collection of IEvent
instances and returns a Map
that assigns to each row in the presentation the set of events that can be shown in that row.
layout方法接受IEvent实例的集合,并返回一个Map,该Map为演示文稿中的每一行分配可以在该行中显示的事件集。
public Map<Integer,Set<IEvent>> layout(Collection<IEvent> events) {
Set<IEvent> remainingEvents = new TreeSet<IEvent>(events);
Map<Integer,Set<IEvent>> result = new TreeMap<Integer,Set<IEvent>>();
int day = 0;
while (0 < remainingEvents.size()) {
Set<IEvent> dayEvents = new TreeSet<IEvent>();
for(IEvent e : remainingEvents) {
if (e.isCompatibleWith(dayEvents)) {
dayEvents.add(e);
}
}
remainingEvents.removeAll(dayEvents);
result.put(day, dayEvents);
++day;
}
return result;
}
Each row is composed by selecting the longest remaining event and progressively selecting all additional events (in the order described above) that are compatible with previously-selected events for the current row. The effect is that all events "float" upward as far as possible without collision.
通过选择最长的剩余事件并逐步选择与当前行的先前选择的事件兼容的所有附加事件(按照上述顺序)来组成每一行。结果是所有事件尽可能“浮动”而没有碰撞。
The following demo shows the two scenarios in your question, along with a randomly-created set of events.
以下演示显示了您问题中的两个方案,以及随机创建的一组事件。
Event collection:
x(1):4
b(5):2..6
y(1):5
a(2):1..2
z(1):6
Result of layout:
0 -> {b(5):2..6}
1 -> {a(2):1..2, x(1):4, y(1):5, z(1):6}
Visual presentation:
bbbbb
aa xyz
Event collection:
x(1):1
b(3):2..4
a(2):1..2
c(2):4..5
y(1):5
Result of layout:
0 -> {b(3):2..4, x(1):1, y(1):5}
1 -> {a(2):1..2, c(2):4..5}
Visual presentation:
xbbby
aa cc
Event collection:
f(2):1..2
h(2):1..2
d(4):1..4
e(4):2..5
c(1):6
a(2):5..6
g(4):2..5
b(2):0..1
Result of layout:
0 -> {d(4):1..4, a(2):5..6}
1 -> {e(4):2..5, b(2):0..1, c(1):6}
2 -> {g(4):2..5}
3 -> {f(2):1..2}
4 -> {h(2):1..2}
Visual presentation:
ddddaa
bbeeeec
gggg
ff
hh
#2
I think in a situation like this, you're much better off making sure your data is organized properly first and then rendering it. I know you want a single pass, but I think the results would be alot better.
我认为在这样的情况下,最好先确保数据正确组织然后再渲染。我知道你想要一次通过,但我认为结果会更好。
For instance, organize the data into the lines you'll need to have for a given day and organize the events the best way possible, starting with the longest events (don't need to be displayed first, but they do need to be organized first) and moving down to the shortest events. This will allow you to render your output accordingly not wasting any space, and avoiding those "e" event days. Additionally, then:
例如,将数据组织到您需要的特定日期的行中,并以最佳方式组织事件,从最长的事件开始(不需要先显示,但需要组织它们)首先)并向下移动到最短的事件。这将允许您相应地渲染输出而不会浪费任何空间,并避免那些“e”事件日。另外,那么:
bbb
aa cc
or
aa cc
bbb
won't matter because x
and y
can always go on either side of bbb
or even between aa
and cc
无关紧要,因为x和y总是可以在bbb的两边,甚至在aa和cc之间
I hope you find this helpful.
我希望你觉得这有帮助。