在java中累积流

时间:2015-12-13 14:19:04

标签: java lambda java-8 java-stream accumulate

最近我一直试图将我的数据解析器重新实现为java中的流,但我无法弄清楚如何做一件具体的事情:

使用timeStamp考虑对象A. 考虑由各种A对象组成的对象B. 考虑一些指标,告诉我们对象B的时间范围。

我现在拥有的是一个带有状态的方法,它通过列表包含对象A,如果它适合最后一个对象B,它会去那里,否则它会创建新的B实例并开始在那里放置对象A.

我想以溪流的方式做到这一点

获取对象A的完整列表并将其作为流。现在我需要弄清楚将创建“块”的函数并将它们累积到对象B中。我该怎么做? 感谢

编辑:

A和B很复杂,但我会尝试在这里发布一些简化版本。

class A {
    private final long time;
    private A(long time) {
        this.time = time;
    }
    long getTime() {
        return time;
    }
}

class B {
     // not important, build from "full" temporaryB class
     // result of accumulation         
}

class TemporaryB {
    private final long startingTime;
    private int counter;

    public TemporaryB(A a) {
        this.startingTime = a.getTime();
    }

    boolean fits(A a) {
        return a.getTime() - startingTime < THRESHOLD;
    }

    void add(A a) {
        counter++;
    }
}

class Accumulator {
    private List<B> accumulatedB;
    private TemporaryBParameters temporaryBParameters
    public void addA(A a) {
         if(temporaryBParameters.fits(a)) {
             temporaryBParameters.add(a)
         } else {
             accumulateB.add(new B(temporaryBParameters)
             temporaryBParameters = new TemporaryBParameters(a)
         }
    } 
}

好的,所以这是非常简化的方式,我现在该怎么做。我不喜欢它。它很丑。

1 个答案:

答案 0 :(得分:0)

一般来说,这样的问题非常适合Stream API,因为您可能需要非本地知识,这使得并行处理更加困难。想象一下,您有new A(1)new A(2)new A(3)等等,new A(1000)的{​​{1}}设置为Threshold。所以你基本上需要将输入组合成10个元素。这里我们遇到与讨论in this answer相同的问题:当我们将任务拆分为子任务时,后缀部分可能无法准确知道前缀部分中有多少元素,因此它甚至无法开始将数据组合成批处理直到整个前缀已处理。你的问题基本上是连续的。

另一方面,我的headTail库中有新的StreamEx方法提供的解决方案。这种方法很难并行化,但有了它,你几乎可以在几行中定义任何操作。

以下是使用10

解决问题的方法
headTail

我在这里以这种方式修改了您的static StreamEx<TemporaryB> combine(StreamEx<A> input, TemporaryB tb) { return input.headTail((head, tail) -> tb == null ? combine(tail, new TemporaryB(head)) : tb.fits(head) ? combine(tail, tb.add(head)) : combine(tail, new TemporaryB(head)).prepend(tb), () -> StreamEx.ofNullable(tb)); } 方法:

TemporaryB

示例(假设TemporaryB add(A a) { counter++; return this; } = 1000):

Threshold

输出(我写了简单的List<A> input = Arrays.asList(new A(1), new A(10), new A(1000), new A(1001), new A( 1002), new A(1003), new A(2000), new A(2002), new A(2003), new A(2004)); Stream<B> streamOfB = combine(StreamEx.of(input), null).map(B::new); streamOfB.forEach(System.out::println); ):

B.toString()

所以,实际上你实际上有一个B [counter=2, startingTime=1] B [counter=3, startingTime=1001] B [counter=2, startingTime=2002] Stream的懒惰。

<强>解释

B参数是两个lambdas。当输入流非空时,首先调用一次。它接收第一个流元素(head)和包含所有其他元素(tail)的流。当输入流为空并且不接收任何参数时,第二个被调用一次。两者都应该产生一个可以代替使用的输出流。那么我们在这里有什么:

StreamEx.headTail

return input.headTail((head, tail) -> 是起始案例,从tb == null创建新的TemporaryB并使用head调用self:

tail

tb == null ? combine(tail, new TemporaryB(head)) : ?好的,只需将tb.fits(head)添加到现有head中,然后使用tb调用self:

tail

否则再次创建新的 tb.fits(head) ? combine(tail, tb.add(head)) : ,但也会在输出流前加上当前的TemporaryB(head)(实际上将新元素发送到目标流中):

tb

输入流已用尽?好的,如果有的话,返回上次收集的 combine(tail, new TemporaryB(head)).prepend(tb),

tb

请注意 () -> StreamEx.ofNullable(tb)); 实现保证在查看递归时这样的解决方案不会占用堆栈并且堆积超过常量。如果您有疑问,可以在数千个输入元素上查看它:

headTail