【问题标题】:Seeking an algorithm to efficiently layout calendar event banners寻求一种算法来有效地布局日历事件横幅
【发布时间】:2008-12-30 13:51:51
【问题描述】:

我正在寻找一种算法来有效地放置全天/多天事件横幅,就像 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”事件的大部分时间里浪费了 很多 空间。

有什么想法吗?

【问题讨论】:

  • 我不太明白示例 1&2 和 3&4 之间的区别。例如5 - 我看不到任何其他安排事件的方式会占用更少的垂直空间。因为无论如何 b/c/d/e 中没有两个可以在同一行,所以至少需要 4 行。
  • 你说得对,我的意图根本不清楚。我刚刚做了一些修改,希望能更清楚。
  • 对于#5,问题是短事件总是低于长事件,因此底部有“e”意味着在任何“e”天添加短事件将增加五分之一排。如果 e 在顶行,则可以将短事件添加到除“d”天之外的任何一天,而无需使用额外的行。

标签: algorithm calendar minimize


【解决方案1】:

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

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    

【讨论】:

    【解决方案2】:

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

    例如,将数据组织到特定日期所需的行中,并尽可能以最佳方式组织事件,从最长的事件开始(不需要首先显示,但它们确实需要首先组织起来),然后下到最短的事件。这将允许您相应地呈现您的输出而不浪费任何空间,并避免那些“e”事件日。另外,那么:

     bbb
    aa cc
    

    或

    aa cc
     bbb
    

    没关系,因为x 和y 总是可以在bbb 的任一侧,甚至在aa 和cc 之间

    希望对您有所帮助。

    【讨论】:

    • 我可能不得不做不止一次的传球,如果不可避免的话,这也是可以的。放置事件横幅后,x、y 和 z(特定日期内发生的事件)的位置是固定的。必须放置在相应的日期,并且必须放置在当天的活动横幅之后。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-11
    • 2013-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多