Java流减少

我有以下示例数据集,我想根据方向的值使用Java流api进行转换/缩减

Direction int[] IN 1, 2 OUT 3, 4 OUT 5, 6, 7 IN 8 IN 9 IN 10, 11 OUT 12, 13 IN 14 

 Direction int[] IN 1, 2, OUT 3, 4, 5, 6, 7 IN 8, 9, 10, 11 OUT 12, 13 IN 14 

到目前为止我写的代码

 enum Direction { IN, OUT } class Tuple { Direction direction; int[] data; public Tuple merge(Tuple t) { return new Tuple(direction, concat(getData(), t.getData())); } } private static int[] concat(int[] first, int[] second) { int[] result = Arrays.copyOf(first, first.length + second.length); System.arraycopy(second, 0, result, first.length, second.length); return result; } List reduce = tupleStream.reduce(new ArrayList(), WDParser::add, WDParser::combine); private static List combine(List list1, List list2) { System.out.println("combine"); list1.addAll(list2); return list1; } private static List add(List list, Tuple t) { System.out.println("add"); if (list.size() == 0) { list.add(t); } else if (list.size() > 0) { int lastIndex = list.size() - 1; Tuple last = list.get(lastIndex); if (last.getDirection() == t.getDirection()) list.set(lastIndex, last.merge(t)); else list.add(t); } return list; } 

我相信有一个更好,更简单的替代方案来实现同样的目标。

我发现的Java流api reduce / combine的在线示例和博客仅使用Integer :: sum函数。 希望为更复杂的案例场景构建它。

我认为你的解决方案已经非常好了,特别是与收集到共享的外部容器相比,使用简化可以轻松实现并行性。 但Holger指出,使用collect而不是reduce更容易。 此外,累加器中的条件可以简化一点,您忘记合并组合器中的最后一个元素和第一个元素:

 List reduce = tupleStream.collect(ArrayList::new, WDParser::add, WDParser::combine); private static List combine(List list1, List list2) { if (!list2.isEmpty()) { add(list1, list2.remove(0)); // merge lists in the middle if necessary list1.addAll(list2); // add all the rest } return list1; } private static List add(List list, Tuple t) { int lastIndex = list.size() - 1; if (list.isEmpty() || list.get(lastIndex).getDirection() != t.getDirection()) { list.add(t); } else { list.set(lastIndex, list.get(lastIndex).merge(t)); } return list; } 

您可以使用LinkedList和方法add/removeFirst/Last()而不是使用索引来访问第一个/最后一个元素。

这个怎么样。 首先定义一个小帮助方法:

 private static Tuple mergeTwo(Tuple left, Tuple right) { int[] leftArray = left.getData(); int[] rightArray = right.getData(); int[] result = new int[leftArray.length + rightArray.length]; System.arraycopy(leftArray, 0, result, 0, leftArray.length); System.arraycopy(rightArray, 0, result, leftArray.length, rightArray.length); return new Tuple(left.getDirection(), result); } 

这是接近你的concat/merge我想,但只有一个。 基本上是将两个Tuple组合并在一起的方法。

并且有一个生成所需Collector的辅助方法,您可以将其放入实用程序中,以便可以重复使用它:

 private static Collector> mergedTuplesCollector() { class Acc { ArrayDeque deque = new ArrayDeque<>(); void add(Tuple elem) { Tuple head = deque.peek(); if (head == null || head.getDirection() != elem.getDirection()) { deque.offerFirst(elem); } else { deque.offerFirst(mergeTwo(deque.poll(), elem)); } } Acc merge(Acc right) { Tuple lastLeft = deque.peekLast(); Tuple firstRight = right.deque.peekFirst(); if (lastLeft.getDirection() == firstRight.getDirection()) { deque.offerLast(mergeTwo(deque.pollLast(), right.deque.pollFirst())); } else { deque.addAll(right.deque); } return this; } public List finisher() { return new ArrayList<>(deque); } } return Collector.of(Acc::new, Acc::add, Acc::merge, Acc::finisher); } 

例如,使用方法是:

 List merged = tuples.stream() .parallel() .collect(mergedTuplesCollector()); 

这是一种使用略有不同的数据结构的替代方法。

如果这是一个选项,则从int[]更改为List可以提供更大的灵活性(更不用说避免多次创建/复制数组):

 class Tuple { Direction direction; List data; } 

以下函数对Deque集合进行合并:

 private static List next(Deque t, Direction d) { if (!t.isEmpty() && t.peekLast().getDirection() == d) { return t.peekLast().getData(); } else { Tuple next = new Tuple(); next.direction = d; next.data = new ArrayList<>(); t.addLast(next); return next.data; } } 

有了它,流可以看起来像这样简单:

 Deque deq = new LinkedList<>(); //the final collection of tuples tuples.stream() .flatMap(tp -> tp.getData().stream() .map(d -> Pair.of(tp.getDirection(), Integer.valueOf(d)))) .forEach(el -> next(deq, el.getLeft()).add(el.getRight())); 

我对这个话题有两个想法。 第一个是在这个答案中得到索引并相应地对其进行分组。

第二个想法 – 如果你已经有一个Stream应该使用一个自定义Collector (类似于其他解决方案,但使用Deque ):

 private Collector> squashTuples() { return new Collector, List>() { @Override public Supplier> supplier() { return ArrayDeque::new; } @Override public BiConsumer, Tuple> accumulator() { return (acc, e) -> { Objects.requireNonNull(e); if (!acc.isEmpty() && acc.peekLast().getDirection() == e.getDirection()) { acc.offerLast(acc.pollLast().merge(e)); } else { acc.offerLast(e); } }; } @Override public BinaryOperator> combiner() { return (left, right) -> { if (!left.isEmpty() && !right.isEmpty() && left.peekLast().getDirection() == right.peekFirst().getDirection()) { left.offerLast(left.pollLast().merge(right.pollFirst())); } left.addAll(right); return left; }; } @Override public Function, List> finisher() { return ArrayList::new; } @Override public Set characteristics() { return EnumSet.noneOf(Characteristics.class); } }; }