寻求一种有效布局日历活动横幅的算法

发布于 2024-07-11 03:53:37 字数 1096 浏览 9 评论 0原文

我正在寻找一种算法来有效地放置全天/多天活动横幅,就像 Outlook 或 Google 日历中的月视图一样。 我有许多带有开始日期和结束日期的事件,按增加的开始(然后结束)日期排序(或您喜欢的任何其他顺序,我正在从数据库表中收集事件)。 我想最大限度地减少平均使用的垂直空间量,因为在活动横幅之后,我需要放置当天的其他活动(这些活动总是在给定日期的横幅之后)。 因此,举例来说,如果我有两个事件,一个 1/10-1/11 和一个 1/11-1/15,我更愿意这样安排它们(每列都是一天):

 bbbbb
aa

而不是这样:

aa
 bbbbb

因为当我仅添加当天的事件(x、y 和 z)时,我可以这样做(我更喜欢第一个,不想要第二个):

 bbbbb    vs.    aa
aa xyz            bbbbb
                    xyz

但这并不像首先放置较长的事件那么简单,因为对于 1/10-1/11、1/13-1/14 和 1/11-1/13,我想要:

aa cc
 bbb

而不是:

 bbb
aa cc

因为这将允许事件 x 和 y:

aa cc    vs.     bbb
xbbby           aa cc
                x   y

当然我更愿意一次性完成此操作。 对于数据结构,我当前使用从日期到列表的映射,其中对于事件的每一天,我将事件添加到相应的列表中。 因此,为期三天的事件会出现在三个列表中,每个列表都位于地图中的某一天下。 这是将结果转换为视觉输出的便捷结构,但我也对其他数据结构持开放态度。 我目前正在使用贪婪算法,我只是按顺序添加每个事件,但这可能会产生不需要的工件,例如:

aa ccc          
 bbbbb
    dd
     eeeeeeeeeeeeeeeee

这在大多数“e”事件日中浪费了很多空间。

有任何想法吗?

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):

 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):

 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:

aa cc
 bbb

as opposed to:

 bbb
aa cc

because this would allow for events x and 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.

Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

双手揣兜 2024-07-18 03:53:38

这是一个可能的解决方案的高级草图(使用星期几整数而不是完整的日期)。 该接口:

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

}

必须实现才能使用下面layout方法中表达的算法。

此外,具体类必须实现 Comparable 来创建自然顺序,其中较长的事件先于较短的事件。 (下面的演示的示例实现使用了长度降序、开始日期升序、标签升序的顺序。)

layout 方法采用 IEvent 实例的集合并返回一个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;
}

每行都是通过选择最长的剩余事件并逐步选择与当前行的先前选择的事件兼容的所有附加事件(按上述顺序)组成的。 效果是所有事件都尽可能向上“漂浮”而不会发生碰撞。

以下演示显示了您问题中的两个场景,以及一组随机创建的事件。

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    

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.)

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.

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    
他不在意 2024-07-18 03:53:38

我认为在这种情况下,最好先确保数据组织正确,然后再渲染。 我知道你想要一次通过,但我认为结果会好得多。

例如,将数据组织成给定日期所需的行,并以尽可能最好的方式组织事件,从最长的事件开始(不需要首先显示,但确实需要组织)首先)并向下移动到最短的事件。 这将使您能够相应地渲染输出,而不浪费任何空间,并避免那些“e”事件日。 另外,那么:

 bbb
aa cc

or

aa cc
 bbb

并不重要,因为 xy 始终可以位于 bbb 的任一侧,甚至位于 aa< 之间/code> 和 cc

我希望这对您有所帮助。

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:

 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

I hope you find this helpful.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文