English 中文(简体)
寻找一个有效布局日历活动横幅的算法
原标题:
  • 时间: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”事件日。

有什么想法吗?

最佳回答

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

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    
问题回答

在这种情况下,我认为你最好先确保你的数据被正确组织,然后再进行渲染。我知道你想要一次性完成,但我认为结果会更好。

例如,将数据整理成所需的日期行,并尽可能地组织活动,从最长的活动开始(不需要首先显示,但需要首先组织),然后再到最短的活动。这将使您能够相应地呈现输出,不浪费任何空间,并避免那些“e”事件天。另外,然后:

 bbb
aa cc

或者

aa cc
 bbb

won t matter because x and y can always go on either side of bbb 或者 even between aa and cc

我希望这对您有所帮助。





相关问题
热门标签