Java 8stream逆序
一般问题:什么是扭转stream的正确方法? 假设我们不知道stream包含的元素是什么types,那么什么是反转任何stream的通用方法?
具体问题:
IntStream
提供范围方法来生成特定范围内的整数IntStream.range(-range, 0)
,现在我想反转它切换范围从0到负将不会工作,我也不能使用Integer::compare
List<Integer> list = Arrays.asList(1,2,3,4); list.stream().sorted(Integer::compare).forEach(System.out::println);
与IntStream
我会得到这个编译器错误
错误:(
IntStream
)ajc:typesIntStream
中的sorted()
方法不适用于参数(Integer::compare
)
我在那里错过了什么?
这里的许多解决scheme对IntStream
sorting或反转,但是这不必要地需要中间存储。 斯图尔特·马克斯的解决scheme是要走的路:
static IntStream revRange(int from, int to) { return IntStream.range(from, to).map(i -> to - i + from - 1); }
它也正确处理溢出,通过这个testing:
@Test public void testRevRange() { assertArrayEquals(revRange(0, 5).toArray(), new int[]{4, 3, 2, 1, 0}); assertArrayEquals(revRange(-5, 0).toArray(), new int[]{-1, -2, -3, -4, -5}); assertArrayEquals(revRange(1, 4).toArray(), new int[]{3, 2, 1}); assertArrayEquals(revRange(0, 0).toArray(), new int[0]); assertArrayEquals(revRange(0, -1).toArray(), new int[0]); assertArrayEquals(revRange(MIN_VALUE, MIN_VALUE).toArray(), new int[0]); assertArrayEquals(revRange(MAX_VALUE, MAX_VALUE).toArray(), new int[0]); assertArrayEquals(revRange(MIN_VALUE, MIN_VALUE + 1).toArray(), new int[]{MIN_VALUE}); assertArrayEquals(revRange(MAX_VALUE - 1, MAX_VALUE).toArray(), new int[]{MAX_VALUE - 1}); }
对于生成反向IntStream
的具体问题,请尝试如下所示:
static IntStream revRange(int from, int to) { return IntStream.range(from, to) .map(i -> to - i + from - 1); }
这避免了拳击和sorting。
对于如何扭转任何types的stream的一般问题,我不知道有一个“适当”的方式。 有一些我能想到的方法。 两者都最终存储stream元素。 我不知道如何在不存储元素的情况下翻转stream。
这第一种方法将元素存储到数组中,并以相反的顺序将它们读出到stream中。 请注意,由于我们不知道stream元素的运行时types,所以我们无法正确键入数组,因此需要进行未经检查的转换。
@SuppressWarnings("unchecked") static <T> Stream<T> reverse(Stream<T> input) { Object[] temp = input.toArray(); return (Stream<T>) IntStream.range(0, temp.length) .mapToObj(i -> temp[temp.length - i - 1]); }
另一种技术使用收集器来将这些项目累积成反向列表。 这在ArrayList
对象的前面ArrayList
的插入操作,所以有很多拷贝正在进行。
Stream<T> input = ... ; List<T> output = input.collect(ArrayList::new, (list, e) -> list.add(0, e), (list1, list2) -> list1.addAll(0, list2));
使用某种定制的数据结构来编写一个更高效的反转收集器是可能的。
更新2016-01-29
由于这个问题最近得到了一些关注,我想我应该更新我的答案来解决插入在ArrayList
前面的问题。 这将是非常低效率的大量元素,需要O(N ^ 2)复制。
相反,最好使用ArrayDeque
,它有效地支持前面的插入。 一个小小的错误就是我们不能使用Stream.collect()
的三个参数。 它要求将第二个参数的内容合并到第一个参数中,并且在Deque
上没有“加载全部在前”的批量操作。 相反,我们使用addAll()
将第一个参数的内容追加到第二个参数的末尾,然后返回第二个。 这需要使用Collector.of()
工厂方法。
完整的代码是这样的:
Deque<String> output = input.collect(Collector.of( ArrayDeque::new, (deq, t) -> deq.addFirst(t), (d1, d2) -> { d2.addAll(d1); return d2; }));
结果是一个Deque
而不是一个List
,但这不应该成为一个问题,因为它可以很容易地以现在相反的顺序迭代或stream式传输。
一般问题:
stream不存储任何元素。
因此,如果不将元素存储在某个中间集合中,则以相反顺序迭代元素是不可能的。
Stream.of("1", "2", "20", "3") .collect(Collectors.toCollection(ArrayDeque::new)) // or LinkedList .descendingIterator() .forEachRemaining(System.out::println);
更新:更改LinkedList ArrayDeque(更好) 看到这里的细节
打印:
3 20 2 1
顺便说一下,使用sort
方法是不正确的,因为它sorting,不反转(假设stream可能有无序的元素)
具体问题:
我发现这个简单,容易和直观(Copied @Holger评论)
IntStream.iterate(to - 1, i -> i - 1).limit(to - from)
优雅的解决
List<Integer> list = Arrays.asList(1,2,3,4); list.stream() .boxed() // Converts Intstream to Stream<Integer> .sorted(Collections.reverseOrder()) // Method on Stream<Integer> .forEach(System.out::println);
没有外部的lib …
import java.util.List; import java.util.Collections; import java.util.stream.Collector; public class MyCollectors { public static <T> Collector<T, ?, List<T>> toListReversed() { return Collectors.collectingAndThen(Collectors.toList(), l -> { Collections.reverse(l); return l; }); } }
你可以定义你自己的收集器,以相反的顺序收集元素:
public static <T> Collector<T, List<T>, List<T>> inReverse() { return Collector.of( ArrayList::new, (l, t) -> l.add(t), (l, r) -> {l.addAll(r); return l;}, Lists::<T>reverse); }
并使用它:
stream.collect(inReverse()).forEach(t -> ...)
我按照顺序使用ArrayList来高效地插入收集项目(在列表的末尾)和Guava Lists.reverse以有效地给出列表的反转视图,而不用制作另一个副本。
以下是自定义收集器的一些testing用例:
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; import org.hamcrest.Matchers; import org.junit.Test; import com.google.common.collect.Lists; public class TestReverseCollector { private final Object t1 = new Object(); private final Object t2 = new Object(); private final Object t3 = new Object(); private final Object t4 = new Object(); private final Collector<Object, List<Object>, List<Object>> inReverse = inReverse(); private final Supplier<List<Object>> supplier = inReverse.supplier(); private final BiConsumer<List<Object>, Object> accumulator = inReverse.accumulator(); private final Function<List<Object>, List<Object>> finisher = inReverse.finisher(); private final BinaryOperator<List<Object>> combiner = inReverse.combiner(); @Test public void associative() { final List<Object> a1 = supplier.get(); accumulator.accept(a1, t1); accumulator.accept(a1, t2); final List<Object> r1 = finisher.apply(a1); final List<Object> a2 = supplier.get(); accumulator.accept(a2, t1); final List<Object> a3 = supplier.get(); accumulator.accept(a3, t2); final List<Object> r2 = finisher.apply(combiner.apply(a2, a3)); assertThat(r1, Matchers.equalTo(r2)); } @Test public void identity() { final List<Object> a1 = supplier.get(); accumulator.accept(a1, t1); accumulator.accept(a1, t2); final List<Object> r1 = finisher.apply(a1); final List<Object> a2 = supplier.get(); accumulator.accept(a2, t1); accumulator.accept(a2, t2); final List<Object> r2 = finisher.apply(combiner.apply(a2, supplier.get())); assertThat(r1, equalTo(r2)); } @Test public void reversing() throws Exception { final List<Object> a2 = supplier.get(); accumulator.accept(a2, t1); accumulator.accept(a2, t2); final List<Object> a3 = supplier.get(); accumulator.accept(a3, t3); accumulator.accept(a3, t4); final List<Object> r2 = finisher.apply(combiner.apply(a2, a3)); assertThat(r2, contains(t4, t3, t2, t1)); } public static <T> Collector<T, List<T>, List<T>> inReverse() { return Collector.of( ArrayList::new, (l, t) -> l.add(t), (l, r) -> {l.addAll(r); return l;}, Lists::<T>reverse); } }
独眼巨人反应 StreamUtils有一个逆stream方法( javadoc )。
StreamUtils.reverse(Stream.of("1", "2", "20", "3")) .forEach(System.out::println);
它的工作原理是收集一个ArrayList,然后利用可以在任一方向迭代的ListIterator类,在列表上向后迭代。
如果你已经有一个列表,它会更有效率
StreamUtils.reversedStream(Arrays.asList("1", "2", "20", "3")) .forEach(System.out::println);
如果实现了Comparable <T>(例如Integer,String,Date),可以使用Comparator.reverseOrder()来完成 。
List<Integer> list = Arrays.asList(1, 2, 3, 4); list.stream() .sorted(Comparator.reverseOrder()) .forEach(System.out::println);
以下是我提出的解决scheme:
private static final Comparator<Integer> BY_ASCENDING_ORDER = Integer::compare; private static final Comparator<Integer> BY_DESCENDING_ORDER = BY_ASCENDING_ORDER.reversed();
然后使用这些比较器:
IntStream.range(-range, 0).boxed().sorted(BY_DESCENDING_ORDER).forEach(// etc...
我build议使用jOOλ ,这是一个很棒的库,它为Java 8stream和lambdas添加了很多有用的function。
然后您可以执行以下操作:
List<Integer> list = Arrays.asList(1,2,3,4); Seq.seq(list).reverse().forEach(System.out::println)
就那么简单。 这是一个非常轻量级的库,值得添加到任何Java 8项目。
人们可以写一个收集器,以相反的顺序收集元素:
public static <T> Collector<T, ?, Stream<T>> reversed() { return Collectors.collectingAndThen(Collectors.toList(), list -> { Collections.reverse(list); return list.stream(); }); }
像这样使用它:
Stream.of(1, 2, 3, 4, 5).collect(reversed()).forEach(System.out::println);
原始答案 (包含一个错误 – 并行stream不能正常工作):
通用stream反向方法可能如下所示:
public static <T> Stream<T> reverse(Stream<T> stream) { LinkedList<T> stack = new LinkedList<>(); stream.forEach(stack::push); return stack.stream(); }
Java 8的方式来做到这一点:
List<Integer> list = Arrays.asList(1,2,3,4); Comparator<Integer> comparator = Integer::compare; list.stream().sorted(comparator.reversed()).forEach(System.out::println);
作为参考,我正在看同样的问题,我想以相反的顺序joinstream元素的string值。
itemList = {last,middle,first} => first,middle,last
我开始使用收集中间集合,然后从comonad或ArrayDeque
collections家,虽然我不满意中间收集,并再次stream
itemList.stream() .map(TheObject::toString) .collect(Collectors.collectingAndThen(Collectors.toList(), strings -> { Collections.reverse(strings); return strings; })) .stream() .collect(Collector.joining());
所以我重复了斯图尔特·马克斯(Stuart Marks)的回答,那就是使用Collector.of
工厂( Collector.of
factory),它有一个有趣的修整器 lambda。
itemList.stream() .collect(Collector.of(StringBuilder::new, (sb, o) -> sb.insert(0, o), (r1, r2) -> { r1.insert(0, r2); return r1; }, StringBuilder::toString));
因为在这种情况下stream不是并行的,组合器不是那么重要,为了代码的一致性,我使用insert
,但是没关系,因为它取决于哪个stringbuilder是先构build的。
我看了一下StringJoiner,但没有insert
方法。
用IntStream回答具体的反转问题,下面为我工作:
IntStream.range(0, 10) .map(x -> x * -1) .sorted() .map(Math::abs) .forEach(System.out::println);
最简单的方法 (简单收集 – 支持并行stream):
public static <T> Stream<T> reverse(Stream<T> stream) { return stream .collect(Collector.of( () -> new ArrayDeque<T>(), ArrayDeque::addFirst, (q1, q2) -> { q2.addAll(q1); return q2; }) ) .stream(); }
高级方式 (持续支持并行stream):
public static <T> Stream<T> reverse(Stream<T> stream) { Objects.requireNonNull(stream, "stream"); class ReverseSpliterator implements Spliterator<T> { private Spliterator<T> spliterator; private final Deque<T> deque = new ArrayDeque<>(); private ReverseSpliterator(Spliterator<T> spliterator) { this.spliterator = spliterator; } @Override @SuppressWarnings({"StatementWithEmptyBody"}) public boolean tryAdvance(Consumer<? super T> action) { while(spliterator.tryAdvance(deque::addFirst)); if(!deque.isEmpty()) { action.accept(deque.remove()); return true; } return false; } @Override public Spliterator<T> trySplit() { // After traveling started the spliterator don't contain elements! Spliterator<T> prev = spliterator.trySplit(); if(prev == null) { return null; } Spliterator<T> me = spliterator; spliterator = prev; return new ReverseSpliterator(me); } @Override public long estimateSize() { return spliterator.estimateSize(); } @Override public int characteristics() { return spliterator.characteristics(); } @Override public Comparator<? super T> getComparator() { Comparator<? super T> comparator = spliterator.getComparator(); return (comparator != null) ? comparator.reversed() : null; } @Override public void forEachRemaining(Consumer<? super T> action) { // Ensure that tryAdvance is called at least once if(!deque.isEmpty() || tryAdvance(action)) { deque.forEach(action); } } } return StreamSupport.stream(new ReverseSpliterator(stream.spliterator()), stream.isParallel()); }
请注意,您可以快速扩展到其他types的stream(IntStream,…)。
testing:
// Use parallel if you wish only revert(Stream.of("One", "Two", "Three", "Four", "Five", "Six").parallel()) .forEachOrdered(System.out::println);
结果:
Six Five Four Three Two One
其他注意事项: simplest way
是与其他stream操作一起使用时不那么有用(collect连接打破了并行性)。 advance way
没有这个问题,它也保持了stream的初始特性,例如SORTED
,所以,这是反向后与其他stream操作一起使用的方式。
反转列表的最通用和最简单的方法是:
public static <T> void reverseHelper(List<T> li){ li.stream() .sorted((x,y)-> -1) .collect(Collectors.toList()) .forEach(System.out::println); }