将值与Java8流组合

时间:2015-08-18 09:29:18

标签: java java-8 java-stream rx-java

如果我有一个带整数的列表,有没有办法构建另一个列表,如果新列表的头部差异低于threashold,则整数总和?我想用Java 8流来解决这个问题。它应该与RxJava的Scan operator类似。

Example: 5, 2, 2, 5, 13    
Threashold: 2    
Result: 5, 9, 13

Intermediate results:    
5
5, 2
5, 4 (2 and 2 summed)
5, 9 (4 and 5 summed)
5, 9, 13

4 个答案:

答案 0 :(得分:7)

顺序流解决方案可能如下所示:

List<Integer> result = Stream.of(5, 2, 2, 5, 13).collect(ArrayList::new, (list, n) -> {
    if(!list.isEmpty() && Math.abs(list.get(list.size()-1)-n) < 2)
        list.set(list.size()-1, list.get(list.size()-1)+n);
    else
        list.add(n);
}, (l1, l2) -> {throw new UnsupportedOperationException();});
System.out.println(result);

虽然它看起来不是很好的旧解决方案:

List<Integer> input = Arrays.asList(5, 2, 2, 5, 13);
List<Integer> list = new ArrayList<>();
for(Integer n : input) {
    if(!list.isEmpty() && Math.abs(list.get(list.size()-1)-n) < 2)
        list.set(list.size()-1, list.get(list.size()-1)+n);
    else
        list.add(n);
}
System.out.println(list);

似乎您的问题不是关联的,因此无法轻松并行化。例如,如果将输入拆分为两个组(如(5, 2), (2, 5, 13)),则无法说明是否应合并第二个组的前两个项,直到处理完第一个组。因此,我无法指定正确的组合器功能。

答案 1 :(得分:4)

作为Tagir Valeev observed,(+ 1),组合函数不是关联的,因此reduce()不会起作用,并且无法为{编写组合函数{1}}。相反,这种组合功能需要从左到右应用,前一部分结果被送入下一个操作。这称为 fold-left 操作,遗憾的是Java流没有这样的操作。

(他们应该吗?让我知道。)

在捕获和变异对象以保持部分状态时,可以使用Collector对自己的左侧折叠操作进行排序。首先,让我们将组合函数提取到自己的方法中:

forEachOrdered

然后,创建初始结果列表并从// extracted from Tagir Valeev's answer void combine(List<Integer> list, int n) { if (!list.isEmpty() && Math.abs(list.get(list.size()-1)-n) < 2) list.set(list.size()-1, list.get(list.size()-1)+n); else list.add(n); }

中调用组合函数
forEachOrdered

这给出了期望的结果

List<Integer> result = new ArrayList<>();
IntStream.of(5, 2, 2, 5, 13)
         .forEachOrdered(n -> combine(result, n));

原则上,这可以在并行流上完成,但考虑到[5, 9, 13] 的语义,性能可能会降低到顺序。另请注意,forEachOrdered操作一次只执行一次,因此我们不必担心我们要改变的数据的线程安全性。

答案 2 :(得分:0)

Java 8方式是定义自定义IntSpliterator类:

static class IntThreasholdSpliterator extends Spliterators.AbstractIntSpliterator {
    private PrimitiveIterator.OfInt it;
    private int threashold;
    private int sum;

    public IntThreasholdSpliterator(int threashold, IntStream stream, long est) {
        super(est, ORDERED );
        this.it = stream.iterator();
        this.threashold = threashold;
    }

    @Override
    public boolean tryAdvance(IntConsumer action) {
        if(!it.hasNext()){
            return false;
        }
        int next = it.nextInt();
        if(next<threashold){
            sum += next;
        }else {
            action.accept(next + sum);
            sum = 0;
        }
        return true;
    }

}

public static void main( String[] args )
{
    IntThreasholdSpliterator s = new IntThreasholdSpliterator(3, IntStream.of(5, 2, 2, 5, 13), 5);
    List<Integer> rs= StreamSupport.intStream(s, false).mapToObj(Integer::valueOf).collect(toList());
    System.out.println(rs);
}

你也可以将其破解为

    List<Integer> list = Arrays.asList(5, 2, 2, 5, 13);
    int[] sum = {0};
    list = list.stream().filter(s -> {
        if(s<=2) sum[0]+=s;
        return s>2;
    }).map(s -> {
        int rs = s + sum[0];
        sum[0] = 0;
        return rs;
    }).collect(toList());
    System.out.println(list);

但我不确定这个黑客对于生产代码是个好主意。

答案 3 :(得分:0)

我知道Stream的主人“Tagir Valeev”和“Stuart Marks”已经指出reduce()不会起作用,因为组合功能不是关联的,我冒这里的几个downvotes。无论如何:

如果我们强制流顺序怎么办?我们不能再使用reduce吗?使用并行性时才需要关联属性吗?

    Stream<Integer> s = Stream.of(5, 2, 2, 5, 13);
    LinkedList<Integer> result =  s.sequential().reduce(new LinkedList<Integer>(), 
                 (list, el) -> { 
                     if (list.isEmpty() || Math.abs(list.getLast() - el) >= 2) {
                         list.add(el);
                     } else {
                         list.set(list.size() - 1, list.getLast() + el);
                     }
                     return list; 
                 }, (list1, list2) -> {
                         //don't really needed, as we are sequential
                         list1.addAll(list2); return list1;
                      });