我希望能够询问一个对象'在过去的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秒)可能或可能不在集合中
答案 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();
}
这是一种更有效的方法,它利用了您只记录在所有其他事件之后发生的事件的事实。对于每个事件,存储截至该日期的事件数量:
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.binarySearch
,DateTime
实现了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
这应该是一种更快速的方式来做你想做的事。