自x'以来实施'事件的有效方式在Java中

时间:2014-06-12 14:35:27

标签: java algorithm optimization

我希望能够询问一个对象'在过去的x秒内发生了多少事件'其中x是一个参数。

e.g。在过去120秒内发生了多少事件..

我接近的方式是根据发生的事件数量而线性的,但是想要了解实现这一要求的最有效方式(空间和时间)是什么?

public class TimeSinceStat {
private List<DateTime> eventTimes = new ArrayList<>();

public void apply() {
            eventTimes.add(DateTime.now());
           }


public int eventsSince(int seconds) {
    DateTime startTime = DateTime.now().minus(Seconds.seconds(seconds));
    for (int i = 0; i < orderTimes.size(); i++) {
        DateTime dateTime = eventTimes.get(i);
        if (dateTime.compareTo(startTime) > 0)
            return eventTimes.subList(i, eventTimes.size()).size();
    }
    return 0;
}

(PS - 我使用JodaTime作为日期/时间表示)

编辑:

此算法的关键是查找最近x秒内发生的所有事件;准确的开始时间(例如现在 - 30秒)可能或可能不在集合中

4 个答案:

答案 0 :(得分:4)

DateTime存储在TreeSet中,然后使用tailSet获取最新活动。这使您不必通过迭代(O(n))而是通过搜索(O (log n))来找到起始点。

TreeSet<DateTime> eventTimes;

public int eventsSince(int seconds) {
    return eventTimes.tailSet(DateTime.now().minus(Seconds.seconds(seconds)), true).size();
}

当然,您也可以在排序列表中进行二进制搜索,但这可以帮到您。

修改

如果担心多个事件可能发生在同一个DateTime,您可以采用与番石榴SortedMultiset完全相同的方法:

TreeMultiset<DateTime> eventTimes;

public int eventsSince(int seconds) {
    return eventTimes.tailMultiset(
        DateTime.now().minus(Seconds.seconds(seconds)),
        BoundType.CLOSED
    ).size();
}

编辑x2

这是一种更有效的方法,它利用了您只记录在所有其他事件之后发生的事件的事实。对于每个事件,存储截至该日期的事件数量:

SortedMap<DateTime, Integer> eventCounts = initEventMap();

public SortedMap<DateTime, Integer> initEventMap() {
    TreeMap<DateTime, Integer> map = new TreeMap<>();
    //prime the map to make subsequent operations much cleaner
    map.put(DateTime.now().minus(Seconds.seconds(1)), 0);
    return map;
}

private long totalCount() {
    //you can handle the edge condition here
    return eventCounts.getLastEntry().getValue();
}

public void logEvent() {
    eventCounts.put(DateTime.now(), totalCount() + 1);
}

然后从日期开始计算是非常有效的,只需取总数并减去之前之前发生的事件的数量。

public int eventsSince(int seconds) {
    DateTime startTime = DateTime.now().minus(Seconds.seconds(seconds));
    return totalCount() - eventCounts.lowerEntry(startTime).getValue();
}

这消除了低效的迭代。它是一个恒定的时间查找和O(log n)查找。

答案 1 :(得分:3)

如果您是从头开始实施数据结构,并且数据未按排序顺序排列,那么您需要构建一个平衡的order statistic tree(另请参阅code here)。这只是一个常规的平衡树,树的大小以节点本身维护的每个节点为根。

大小字段可以有效地计算树中任何键的“等级”。你可以通过在树中输入两个O(log n)探针来获得所需的范围查询,以获得最小和最大范围值的等级,最后得出它们的差异。

建议的树和集尾操作很棒,除了尾部视图需要时间来构建,即使你只需要它们的大小。渐近复杂度与OST相同,但OST完全避免了这种开销。如果表现非常具有批判性,那么差异可能是有意义的。

当然,我肯定会首先使用标准库解决方案,只有在速度不合适时才考虑使用OST。

答案 2 :(得分:0)

由于DateTime已经实现了Comparable界面,我建议您将数据存储在TreeMap中,然后您可以使用TreeMap#tailMap获取DateTime的子树1}}发生在所需的时间内。

根据您的代码:

public class TimeSinceStat {
    //just in case two or more events start at the "same time"
    private NavigableMap<DateTime, Integer> eventTimes = new TreeMap<>();
    //if this class needs to be used in multiple threads, use ConcurrentSkipListMap instead of TreeMap

    public void apply() {
        DateTime dateTime = DateTime.now();
        Integer times = eventTimes.contains(dateTime) != null ? 0 : (eventTimes.get(dateTime) + 1);
        eventTimes.put(dateTime, times);
    }

    public int eventsSince(int seconds) {
        DateTime startTime = DateTime.now().minus(Seconds.seconds(seconds));
        NavigableMap<DateTime, Integer> eventsInRange = eventTimes.tailMap(startTime, true);
        int counter = 0;
        for (Integer time : eventsInRange.values()) {
            counter += time;
        }
        return counter;
    }
}

答案 3 :(得分:0)

假设列表已排序,您可以进行二进制搜索。 Java Collections已经提供Collections.binarySearchDateTime实现了Comparable(根据JodaTime JavaDoc)。 binarySearch将返回所需值的索引(如果它存在于列表中),否则返回最大值的索引小于所需的值(翻转符号)。所以,您需要做的就是(在eventsSince方法中):

// find the time you want.
int index=Collections.binarySearch(eventTimes, startTime);
if(index < 0) index = -(index+1)-1; // make sure we get the right index if startTime isn't found
// check for dupes
while(index != eventTimes.size() - 1 && eventTimes.get(index).equals(eventTimes.get(index+1))){
    index++;
}
// return the number of events after the index
return eventTimes.size() - index; // this works because indices start at 0

这应该是一种更快速的方式来做你想做的事。