2

我正在发现 Java 8,尤其是Stream的使用,它看起来非常强大。不过,我在表达查询时遇到了问题。

我有一个要分析的事件对象列表。我想在这个列表中找出一些不好的模式(事件序列),应该删除。

基本上,一个事件对象有 3 个字段:

  • Element eventSource(Element 超类的对象,例如 VirtualMachine),
  • String eventName(事件类的类型,例如“VMHighCpu”或“VMLowCpu”),
  • 字符串 eventMetric(涉及的指标,例如“cpu”)。

如果我有 2 个与相同源和相同指标相关的事件,但它们是相反的(例如,一个是“VMHighCpu”类型,另一个是“VMLowCpu”类型),我想从我的列表中删除这两个事件。

我尝试了几件事都不成功...

    // Simple query
    Map<Element, List<EventToAnalyze>> bySource = (Map) eventsToPurge.stream().collect(Collectors.groupingBy(EventToAnalyze::getSource));

    // Another attempt
    Map<Element, List<EventToAnalyze>> bySourceWithFilter = (Map) eventsToPurge.stream().filter(e -> e.getEventName().contains("Low")).collect(Collectors.groupingBy(EventToAnalyze::getSource));

    // Last attempt
    Map<Element, List<EventToAnalyze>> bySourceByMetric = (Map) eventsToPurge.stream().collect(Collectors.groupingBy(
                                    EventToAnalyze::getSource, Collectors.groupingBy(
                                                    EventToAnalyze::getMetricName, Collectors.groupingBy(
                                                                    EventToAnalyze::getEventName))));

希望我的解释清楚。

4

1 回答 1

0

您想要做的事情在使用 Streams 时有些困难,因为大多数 Streams 操作都发生在流中的每个单独的值上,独立于其他值。distinct有像and这样的有状态操作,sort但这些操作有些不寻常,无法自定义。

可以编写自己的有状态操作(类似于我的另一个答案中的有状态过滤器)。在这种情况下,它将是一个有状态的平面映射器,但对我来说,如何让它工作并不明显。

这是一种基于数组的替代方法。它假设您可以随机访问这些事件。你说你有一个事件列表,所以我希望是这样。为了简单起见,让我们这样设置:

enum Event {
    HIGH, NORMAL, LOW
}

我们需要一个函数来接受两个事件并确定它们是否与要删除的模式匹配:

boolean match(Event e1, Event e2) {
    return e1 == Event.HIGH && e2 == Event.LOW
        || e1 == Event.LOW && e2 == Event.HIGH;
}

请注意,这与BiPredicate<Event>功能接口匹配。

作为最后的设置,让我们引入一个助手,它在给定子范围内的数组的每个索引上调用一个 lambda 函数。这就像Arrays.setAll只是它需要一个子范围,并且它对布尔值[]进行操作。

void ArraySetRange(boolean[] array, int start, int end, IntPredicate op) {
    IntStream.range(start, end).forEach(i -> array[i] = op.test(i));
}

现在设置完成。主要任务是什么?给定一个事件列表,我们想要删除匹配某个模式的事件序列,并返回一个事件列表:

List<Event> remove(List<Event> input, BiPredicate<Event,Event> matcher) {
    ...

我们要做的第一件事是遍历数组并找到所有匹配条件的事件对:

    int n = input.size();
    boolean[] flags = new boolean[n];
    ArraySetRange(flags, 1, n, i -> matcher.test(input.get(i-1), input.get(i)));

这会将布尔true值放置在数组中此事件及其左侧的事件与模式匹配的每个位置。请注意,模式可以重叠。我们跳过数组的第一个元素,因为它的左边没有任何内容。

但是我们想删除整个模式。对于每一个事件,如果它右边的元素是模式的右端,我们也想删除这个元素。这是另一个数组操作,但这次是对除最后一个之外的所有数组索引:

    ArraySetRange(flags, 0, n-1, i -> flags[i] || flags[i+1]);

(请注意,这会根据输入数组中的值修改输入数组。如果处理是从左到右的顺序处理,则此方法有效,但如果我们想并行执行此操作,最好将结果存储到单独的数组中.)

现在我们有一个数组 flags ,其中true指示我们要删除的模式中的存在。我们可以使用简单的过滤操作来做到这一点:

    return IntStream.range(0, n)
        .filter(i -> ! flags[i])
        .mapToObj(input::get)
        .collect(toList());

完整的例子在这里:

List<Event> remove(List<Event> input, BiPredicate<Event,Event> matcher) {
    int n = input.size();
    boolean[] flags = new boolean[n];
    ArraySetRange(flags, 1, n, i -> matcher.test(input.get(i-1), input.get(i)));
    ArraySetRange(flags, 0, n-1, i -> flags[i] || flags[i+1]);
    return IntStream.range(0, n)
        .filter(i -> ! flags[i])
        .mapToObj(input::get)
        .collect(toList());
}

你可以这样称呼它:

    List<Event> purgedEvents = remove(eventsToPurge, this::match);

我有一个偷偷摸摸的怀疑,这不是你想要的。这将很好地删除孤立的对:

正常、高、低、正常 → 正常、正常

但如果三个“相反”事件连续发生,它们将全部被删除:

正常、高、低、高、正常 → 正常、正常

如果存在一系列并非完全相反的事件,则某些事件将被删除,但相反的事件可能会保留在流中:

正常、高、高、低、低、正常 → 正常、高、低、正常

它取决于您要删除的模式的确切规范,但我相信您可以通过调整布尔数组的处理来完成大多数您想做的事情。

于 2015-01-21T06:25:42.693 回答