【问题标题】:Maximum occurrence of any event in time range时间范围内任何事件的最大发生次数
【发布时间】:2016-03-20 20:47:31
【问题描述】:

我有收集时间戳,例如10:18:07.490,11:50:18.251,其中第一个是事件的开始时间,第二个是结束时间。我需要找到一个在 24 小时内发生最多事件的范围。这些事件以毫秒的精度发生。

我所做的是以毫秒为单位划分 24 小时,并在每毫秒附加事件,然后找到发生最大事件的范围。

LocalTime start = LocalTime.parse("00:00");
LocalTime end = LocalTime.parse("23:59");


for (LocalTime x = start; x.isBefore(end); x = x.plus(Duration.ofMillis(1))) {
        for (int i = 0; i < startTime.size(); i++) {
        if (startTime.get(i).isAfter(x) && endTime.get(i).isBefore(x))
            // add them to list;
        }

    }

这当然不是一个好方法,它占用了太多内存。我怎样才能以适当的方式做到这一点?有什么建议吗?

【问题讨论】:

  • 为什么这样的计算要少 4GB 内存。只有 8.64e7 个元素。
  • 为什么要将它们添加到列表中?
  • 你的条件 start > x 和 end

标签: java algorithm


【解决方案1】:

寻找第一期最大并发事件的解决方案:

如果您愿意使用第三方库,这可以通过jOOλ 的窗口函数以 SQL 风格“相对简单”地实现。这个想法和amit's answer中解释的一样:

System.out.println(
    Seq.of(tuple(LocalTime.parse("10:18:07.490"), LocalTime.parse("11:50:18.251")),
           tuple(LocalTime.parse("09:37:03.100"), LocalTime.parse("16:57:13.938")),
           tuple(LocalTime.parse("08:15:11.201"), LocalTime.parse("10:33:17.019")),
           tuple(LocalTime.parse("10:37:03.100"), LocalTime.parse("11:00:15.123")),
           tuple(LocalTime.parse("11:20:55.037"), LocalTime.parse("14:37:25.188")),
           tuple(LocalTime.parse("12:15:00.000"), LocalTime.parse("14:13:11.456")))
       .flatMap(t -> Seq.of(tuple(t.v1, 1), tuple(t.v2, -1)))
       .sorted(Comparator.comparing(t -> t.v1))
       .window(Long.MIN_VALUE, 0)
       .map(w -> tuple(
           w.value().v1,
           w.lead().map(t -> t.v1).orElse(null),
           w.sum(t -> t.v2).orElse(0)))
       .maxBy(t -> t.v3)
);

以上打印:

Optional[(10:18:07.490, 10:33:17.019, 3)]

因此,在 10:18... 和 10:33... 之间,发生了 3 个事件,这是一天中任何时间重叠的事件数量最多。

查找最大并发事件的所有时段:

请注意,样本数据中有几个时期有 3 个并发事件。 maxBy() 只返回第一个这样的时间段。要返回所有此类期间,请改用maxAllBy()(添加到 jOOλ 0.9.11):

   .maxAllBy(t -> t.v3)
   .toList()

然后屈服:

[(10:18:07.490, 10:33:17.019, 3), 
 (10:37:03.100, 11:00:15.123, 3), 
 (11:20:55.037, 11:50:18.251, 3), 
 (12:15       , 14:13:11.456, 3)]

或者,图形表示

3                  /-----\       /-----\       /-----\       /-----\
2           /-----/       \-----/       \-----/       \-----/       \-----\
1     -----/                                                               \-----\
0                                                                                 \--
   08:15  09:37  10:18  10:33  10:37  11:00  11:20  11:50  12:15  14:13  14:37  16:57

说明:

这里是 cmets 的原始解决方案:

// This is your input data    
Seq.of(tuple(LocalTime.parse("10:18:07.490"), LocalTime.parse("11:50:18.251")),
       tuple(LocalTime.parse("09:37:03.100"), LocalTime.parse("16:57:13.938")),
       tuple(LocalTime.parse("08:15:11.201"), LocalTime.parse("10:33:17.019")),
       tuple(LocalTime.parse("10:37:03.100"), LocalTime.parse("11:00:15.123")),
       tuple(LocalTime.parse("11:20:55.037"), LocalTime.parse("14:37:25.188")),
       tuple(LocalTime.parse("12:15:00.000"), LocalTime.parse("14:13:11.456")))

   // Flatten "start" and "end" times into a single sequence, with start times being
   // accompanied by a "+1" event, and end times by a "-1" event, which can then be summed
   .flatMap(t -> Seq.of(tuple(t.v1, 1), tuple(t.v2, -1)))

   // Sort the "start" and "end" times according to the time
   .sorted(Comparator.comparing(t -> t.v1))

   // Create a "window" between the first time and the current time in the sequence
   .window(Long.MIN_VALUE, 0)

   // Map each time value to a tuple containing
   // (1) the time value itself
   // (2) the subsequent time value (lead)
   // (3) the "running total" of the +1 / -1 values
   .map(w -> tuple(
       w.value().v1,
       w.lead().map(t -> t.v1).orElse(null),
       w.sum(t -> t.v2).orElse(0)))

   // Now, find the tuple that has the maximum "running total" value
   .maxBy(t -> t.v3)

我已经写了更多关于window functions and how to implement them in Java in this blog post的文章。

(免责声明:我为 jOOλ 背后的公司工作)

【讨论】:

    【解决方案2】:

    在内存方面可以做得更好(好吧,假设 O(n) 被认为对你有好处,并且你不认为 24​​60*60*1000 是可以容忍的常数):

    • 创建项目列表[time, type](其中时间是时间,类型是 开始或结束)。
    • 按时间对列表进行排序。
    • 迭代列表,当您看到“开始”时,增加一个计数器,当您看到“结束”时,减少它。

    通过存储“迄今为止看到的最大值”,您可以轻松识别其上发生的最大事件数的单点。

    如果你想得到包含这个点的区间,你可以简单地找到“第一个最大值”出现的时间,直到它结束(这是下一个 [time, type] 对,或者如果你允许 start,end 是一起而不计数,从这一点开始线性扫描,直到计数器减少并且时间移动,这只能完成一次,并且不会改变算法的总复杂度)。
    这真的很容易修改这个方法来得到点的间隔

    【讨论】:

    • 正如OP所说,他需要时间范围。引用:"finding a range where maximum events are happening"
    • @vish4071 因此,获取“第一个最大值”发生之间的时间间隔,直到它结束(这是下一个 [time, type] 对,或者如果你允许 start,end 在一起并且不计算,只是从这一点开始线性扫描,直到计数器减少并且时间移动,这只能执行一次,并且不会改变算法的总复杂度)。这真的很容易修改这种方法以获取该点的间隔。
    • 我很想看看在O(N) 中执行的实际算法。看来这个解决方案需要对事件流进行排序才能工作,所以O(N log N) 复杂性似乎是不可避免的。
    • @LukasEder 如果您真的需要 O(N),您可以将元组表示为整数并使用基数排序进行排序步骤。无论如何,这是最好的答案。
    • @LukasEder 不能在代数树模型的“严格”O(n) 中完成,因为它是Element Distincntess Problem 的变体。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-09
    • 2022-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多