如果我有一个带整数的列表,有没有办法构建另一个列表,如果新列表的头部差异低于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
答案 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;
});