如何将Java 8流从许多条目“缩减”到更少

时间:2019-06-05 08:21:12

标签: java functional-java

如何将包含很多项目的Java 8流“缩小”到包含更少项目的流?

我不是在问映射,每个输入项有1个“输出”项,或者在流减少到单个值的情况下减少,而是将许多项的流缩小到更少的项。 “收缩”是有状态的;发射一个物品是基于1个或多个以前的物品(尽管它只是向前移动,所以状态非常简单)。

我有一堆简单的带有时间戳的事件; START或STOP事件。我需要将这种简单事件流简化为记录,每个记录都包含开始和停止时间。在最简单的情况下,有一个START和STOP对,但是重复进行START而又不介入STOP是完全合法的。尽管变质了,但重复停止也是合法的。

下面是一个演示的(简化)版本。查看inputexpected之间的区别;输入项多于输出项。

关键是,shrinkEvents签名是基于流而不是列表。我想要一个不需要List<String> output中的中间shrinkEvents的版本。

public class ShrinkStream {
    @Test
    public void shrinkStream() {
        Stream<String> input = Stream.of("START@1", "STOP@12", "START@14", "START@24", "STOP@35", "STOP@45");
        List<String> expected = Arrays.asList("1-12", "14-24", "24-35");

        Stream<String> actual = shrinkEvents(input);

        assertEquals(expected, actual.collect(toList()));
    }

    private Stream<String> shrinkEvents(Stream<String> input) {
        List<String> output = new ArrayList<>();

        final StringBuilder startTime = new StringBuilder(); // mutable (effectively final BS)
        input.forEach(s -> {
            String[] tokens = s.split("@");
            String type = tokens[0];
            String time = tokens[1];

            boolean isAlreadyActive = startTime.length() > 0;
            if (isAlreadyActive)
                output.add(startTime + "-" + time);

            startTime.setLength(0); // reset

            if (type.equals("START"))
                startTime.append(time);
        });

        return output.stream();
    }
}

2 个答案:

答案 0 :(得分:1)

请考虑使用flatMap(),它将在该对的开头生成空流,而在该对的结尾生成单项流。

答案 1 :(得分:0)

字符串的目的是独立于其他对象检查Stream内部的元素,而不必担心按顺序处理元素。

在这种情况下,您的要求有些紧张,因为我们需要跟踪上一个“ START”元素。 我看到的更正确的方法是使用自定义收集器。

public class ShrinkStream {
    @Test
    public void shrinkStream() {
        Stream<String> input = Stream.of("START@1", "STOP@12", "START@14", "START@24", "STOP@35", "STOP@45").parallel();
        List<String> expected = Arrays.asList("1-12", "14-24", "24-35");

        MyShrinkCollector myShrinkCollector= new MyShrinkCollector();
        assertEquals(expected, input.collect(myShrinkCollector));
    } 
}
public class MyShrinkCollector implements Collector<String, List<String>, List<String>> {

    private String startNumber = null;

    @Override
    public Supplier<List<String>> supplier() {
        return ArrayList::new;
    }

    @Override
    public BiConsumer<List<String>, String> accumulator() {
        return (list, val) -> {
            String[] s = val.split("@");
            String type = s[0];
            String num = s[1];

            if (startNumber != null) {
                list.add(startNumber + "-" + num);
                startNumber = null;
            }

            if (type.equals("START")) startNumber = num;
        };
    }

    @Override
    public BinaryOperator<List<String>> combiner() {
        return null;
    }

    @Override
    public Function<List<String>, List<String>> finisher() {
        return Function.identity();
    }

    @Override
    public Set<Characteristics> characteristics() {
        return new HashSet<>();
    }
}