Java 8流反向顺序

一般问题:什么是反转流的正确方法?假设我们不知道流由什么类型的元素组成,反转任何流的通用方法是什么?

具体问题:

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,我将得到这个编译器错误

错误:(191,0)ajc: IntStream类型中的方法sorted()不适用于实参(Integer::compare)

我错过了什么?

287501 次浏览

以下是我想出的解决方案:

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...

对于生成反向IntStream的特定问题,尝试这样做:

static IntStream revRange(int from, int to) {
return IntStream.range(from, to)
.map(i -> to - i + from - 1);
}

这避免了装箱和排序。

对于如何反转任何类型的流的一般问题,我不知道有一个“合适的”方法。我可以想到几种方法。两者最终都存储了流元素。我不知道如何在不存储元素的情况下反转流。

第一种方法将元素存储到一个数组中,并以相反的顺序将它们读入一个流。请注意,由于我们不知道流元素的运行时类型,因此不能正确地键入数组,需要进行未检查的强制转换。

@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对象的前面做了很多插入,所以有很多复制在进行。

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上没有“add-all-at-front”批量操作。相反,我们使用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.of("1", "2", "20", "3")
.collect(Collectors.toCollection(ArrayDeque::new)) // or LinkedList
.descendingIterator()
.forEachRemaining(System.out::println);

更新:改变LinkedList为ArrayDeque(更好)详情请看这里

打印:

3


20


2


1

顺便说一下,使用sort方法是不正确的,因为它排序,而不是反转(假设流可能有无序元素)

具体问题:

我发现这很简单,更容易和直观(复制@Holger 评论)

IntStream.iterate(to - 1, i -> i - 1).limit(to - from)

你可以定义自己的收集器,按相反的顺序收集元素:

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在前向顺序有效地插入收集项(在列表的末尾),和番石榴列表。Reverse用于有效地提供列表的反向视图,而无需复制另一个列表。

下面是自定义收集器的一些测试用例:

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);
}
}

没有外部库…

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;
});
}


}

cyclops-react StreamUtils有一个反向流方法(javadoc)。

  StreamUtils.reverse(Stream.of("1", "2", "20", "3"))
.forEach(System.out::println);

它的工作原理是收集到一个数组列表,然后使用可以向任意方向迭代的ListIterator类,在列表上向后迭代。

如果你已经有了一个List,它会更有效率

  StreamUtils.reversedStream(Arrays.asList("1", "2", "20", "3"))
.forEach(System.out::println);

我们可以编写一个收集器,以相反的顺序收集元素:

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);

原来的答案(包含一个错误-它不能正确工作的并行流):

一个通用的流反向方法可以是这样的:

public static <T> Stream<T> reverse(Stream<T> stream) {
LinkedList<T> stack = new LinkedList<>();
stream.forEach(stack::push);
return stack.stream();
}

最通用和最简单的反转列表的方法是:

public static <T> void reverseHelper(List<T> li){


li.stream()
.sorted((x,y)-> -1)
.collect(Collectors.toList())
.forEach(System.out::println);


}

这里的许多解决方案对IntStream进行排序或反转,但这不必要地需要中间存储。斯图尔特·马克斯的解决方案是要走的路:

static IntStream revRange(int from, int to) {
return IntStream.range(from, to).map(i -> to - i + from - 1);
}

它正确地处理溢出以及,通过这个测试:

@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});
}

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);

优雅的解决方案

List<Integer> list = Arrays.asList(1,2,3,4);
list.stream()
.sorted(Collections.reverseOrder()) // Method on Stream<Integer>
.forEach(System.out::println);

我建议使用jOOλ,它是一个很棒的库,为Java 8流和lambdas添加了许多有用的功能。

然后您可以执行以下操作:

List<Integer> list = Arrays.asList(1,2,3,4);
Seq.seq(list).reverse().forEach(System.out::println)

就这么简单。它是一个非常轻量级的库,非常值得添加到任何Java 8项目中。

如果实现了Comparable<T>(例如IntegerStringDate),则可以使用Comparator.reverseOrder()来实现。

List<Integer> list = Arrays.asList(1, 2, 3, 4);
list.stream()
.sorted(Comparator.reverseOrder())
.forEach(System.out::println);

作为参考,我正在考虑同样的问题,我想以相反的顺序连接流元素的字符串值。

itemList = {last, middle, first} =>第一个,中间,最后一个

我开始使用来自comonadcollectingAndThen的中间收集或斯图尔特标志ArrayDeque收集器,尽管我对中间收集不满意,并再次流化

itemList.stream()
.map(TheObject::toString)
.collect(Collectors.collectingAndThen(Collectors.toList(),
strings -> {
Collections.reverse(strings);
return strings;
}))
.stream()
.collect(Collector.joining());

所以我迭代了Stuart Marks的答案,它使用了Collector.of工厂,它有有趣的修整器 lambda。

itemList.stream()
.collect(Collector.of(StringBuilder::new,
(sb, o) -> sb.insert(0, o),
(r1, r2) -> { r1.insert(0, r2); return r1; },
StringBuilder::toString));

由于在这种情况下,流不是并行的,组合器不是那么相关,为了代码的一致性,我无论如何都使用insert,但这并不重要,因为它取决于哪个stringbuilder是第一个构建的。

我查看了StringJoiner,但是它没有insert方法。

回答与IntStream反转的具体问题,以下工作对我来说:

IntStream.range(0, 10)
.map(x -> x * -1)
.sorted()
.map(Math::abs)
.forEach(System.out::println);

简单的方法(简单收集-支持并行流):

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();
}

先进的方法(以持续的方式支持并行流):

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());
}

注意,您可以快速扩展到其他类型的流(IntStream,…)。

测试:

// 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在与其他流操作一起使用时不太有用(收集连接破坏了并行性)。advance way就没有这个问题,而且它还保留了流的初始特征,例如SORTED,因此,它是在反向之后与其他流操作一起使用的方式。

我就是这么做的。

我不喜欢创建一个新集合并反向迭代它的想法。

IntStream#映射的想法是非常整洁的,但我更喜欢IntStream#迭代方法,因为我认为倒计时到零的想法更好地表达了迭代方法,更容易理解从后面到前面的数组行走。

import static java.lang.Math.max;


private static final double EXACT_MATCH = 0d;


public static IntStream reverseStream(final int[] array) {
return countdownFrom(array.length - 1).map(index -> array[index]);
}


public static DoubleStream reverseStream(final double[] array) {
return countdownFrom(array.length - 1).mapToDouble(index -> array[index]);
}


public static <T> Stream<T> reverseStream(final T[] array) {
return countdownFrom(array.length - 1).mapToObj(index -> array[index]);
}


public static IntStream countdownFrom(final int top) {
return IntStream.iterate(top, t -> t - 1).limit(max(0, (long) top + 1));
}

下面是一些测试来证明它是有效的:

import static java.lang.Integer.MAX_VALUE;
import static org.junit.Assert.*;


@Test
public void testReverseStream_emptyArrayCreatesEmptyStream() {
Assert.assertEquals(0, reverseStream(new double[0]).count());
}


@Test
public void testReverseStream_singleElementCreatesSingleElementStream() {
Assert.assertEquals(1, reverseStream(new double[1]).count());
final double[] singleElementArray = new double[] { 123.4 };
assertArrayEquals(singleElementArray, reverseStream(singleElementArray).toArray(), EXACT_MATCH);
}


@Test
public void testReverseStream_multipleElementsAreStreamedInReversedOrder() {
final double[] arr = new double[] { 1d, 2d, 3d };
final double[] revArr = new double[] { 3d, 2d, 1d };
Assert.assertEquals(arr.length, reverseStream(arr).count());
Assert.assertArrayEquals(revArr, reverseStream(arr).toArray(), EXACT_MATCH);
}


@Test
public void testCountdownFrom_returnsAllElementsFromTopToZeroInReverseOrder() {
assertArrayEquals(new int[] { 4, 3, 2, 1, 0 }, countdownFrom(4).toArray());
}


@Test
public void testCountdownFrom_countingDownStartingWithZeroOutputsTheNumberZero() {
assertArrayEquals(new int[] { 0 }, countdownFrom(0).toArray());
}


@Test
public void testCountdownFrom_doesNotChokeOnIntegerMaxValue() {
assertEquals(true, countdownFrom(MAX_VALUE).anyMatch(x -> x == MAX_VALUE));
}


@Test
public void testCountdownFrom_givesZeroLengthCountForNegativeValues() {
assertArrayEquals(new int[0], countdownFrom(-1).toArray());
assertArrayEquals(new int[0], countdownFrom(-4).toArray());
}

这个实用方法怎么样?

public static <T> Stream<T> getReverseStream(List<T> list) {
final ListIterator<T> listIt = list.listIterator(list.size());
final Iterator<T> reverseIterator = new Iterator<T>() {
@Override
public boolean hasNext() {
return listIt.hasPrevious();
}


@Override
public T next() {
return listIt.previous();
}
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
reverseIterator,
Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
}

似乎对所有案件都有效,没有重复。

关于生成反向IntStream的具体问题:

Java 9开始,你可以使用IntStream.iterate(...)的三个参数版本:

IntStream.iterate(10, x -> x >= 0, x -> x - 1).forEach(System.out::println);


// Out: 10 9 8 7 6 5 4 3 2 1 0

地点:

IntStream.iterate​(int seed, IntPredicate hasNext, IntUnaryOperator next);

  • seed -初始元素;
  • hasNext -应用于元素的谓词,以确定何时 流必须终止;
  • next -应用于前一个元素以生成一个 李的新元素。< / >

不仅仅是Java8,如果你结合使用guava的Lists.reverse()方法,你可以很容易地实现:

List<Integer> list = Arrays.asList(1,2,3,4);
Lists.reverse(list).stream().forEach(System.out::println);

在所有这些问题中,我没有看到我首先要回答的答案。

这并不是对这个问题的直接回答,但却是一个潜在的解决方案。

只要一开始就逆向构建列表。如果可以的话,使用LinkedList而不是ArrayList,当你添加项目时使用“Push”而不是add。列表将以相反的顺序构建,然后将正确地流,而不需要任何操作。

这并不适用于您正在处理原始数组或列表的情况,这些原始数组或列表已经以各种方式使用,但在令人惊讶的大量情况下工作得很好。

ArrayDeque在堆栈中比stack或LinkedList更快。"push()"将元素插入到Deque的前面

 protected <T> Stream<T> reverse(Stream<T> stream) {
ArrayDeque<T> stack = new ArrayDeque<>();
stream.forEach(stack::push);
return stack.stream();
}

反转字符串或任何数组

(Stream.of("abcdefghijklm 1234567".split("")).collect(Collectors.collectingAndThen(Collectors.toList(),list -> {Collections.reverse(list);return list;}))).stream().forEach(System.out::println);

Split可以根据分隔符或空格进行修改

最简单的解决方案是使用List::listIteratorStream::generate

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
ListIterator<Integer> listIterator = list.listIterator(list.size());


Stream.generate(listIterator::previous)
.limit(list.size())
.forEach(System.out::println);
List newStream = list.stream().sorted(Collections.reverseOrder()).collect(Collectors.toList());
newStream.forEach(System.out::println);

此方法适用于任何流,并且兼容Java 8:

Stream<Integer> myStream = Stream.of(1, 2, 3, 4, 5);
myStream.reduce(Stream.empty(),
(Stream<Integer> a, Integer b) -> Stream.concat(Stream.of(b), a),
(a, b) -> Stream.concat(b, a))
.forEach(System.out::println);

如何避免这样做:

  • 不要使用.sorted(Comparator.reverseOrder()).sorted(Collections.reverseOrder()),因为它只会按降序排序元素。
    使用它为给定的整数输入:
    [1, 4, 2, 5, 3]
    输出如下:
    [5, 4, 3, 2, 1]
    对于字符串输入:
    ["A", "D", "B", "E", "C"]
    输出如下:
    [E, D, C, B, A] < / >
  • 不要使用.sorted((a, b) -> -1)(解释在最后)

最简单的正确方法是:

List<Integer> list = Arrays.asList(1, 4, 2, 5, 3);
Collections.reverse(list);
System.out.println(list);
< p >输出:
[3, 5, 2, 4, 1] < / p >

String也是一样:

List<String> stringList = Arrays.asList("A", "D", "B", "E", "C");
Collections.reverse(stringList);
System.out.println(stringList);
< p >输出:
[C, E, B, D, A] < / p > < p > 不要使用.sorted((a, b) -> -1)!
它打破了比较国契约,可能只适用于某些情况。只在单线程上,而不是并行。
洋基解释:< / p >

(a, b) -> -1打破了Comparator的契约。这是否有效取决于排序算法的实现。JVM的下一个版本可能会打破这一点。实际上,我已经可以在我的机器上使用IntStream.range(0, 10000).parallel().boxed().sorted((a, b) -> -1).forEachOrdered(System.out::println);重复地打破这个

//Don't use this!!!
List<Integer> list = Arrays.asList(1, 4, 2, 5, 3);
List<Integer> reversedList = list.stream()
.sorted((a, b) -> -1)
.collect(Collectors.toList());
System.out.println(reversedList);

正面输出:
[3, 5, 2, 4, 1] < / p >

可能的并行流输出或与其他JVM实现:
[4, 1, 2, 3, 5] < / p >

String也是一样:

//Don't use this!!!
List<String> stringList = Arrays.asList("A", "D", "B", "E", "C");
List<String> reversedStringList = stringList.stream()
.sorted((a, b) -> -1)
.collect(Collectors.toList());
System.out.println(reversedStringList);

正面输出:
[C, E, B, D, A] < / p >

可能的并行流输出或与其他JVM实现:
[A, E, B, D, C] < / p >

基于@stuart-marks的回答,但没有强制转换,函数返回从end开始的列表元素流:

public static <T> Stream<T> reversedStream(List<T> tList) {
final int size = tList.size();
return IntStream.range(0, size)
.mapToObj(i -> tList.get(size - 1 - i));
}


// usage
reversedStream(list).forEach(System.out::println);
How about reversing the Collection backing the stream prior?


import java.util.Collections;
import java.util.List;


public void reverseTest(List<Integer> sampleCollection) {
Collections.reverse(sampleCollection); // remember this reverses the elements in the list, so if you want the original input collection to remain untouched clone it first.


sampleCollection.stream().forEach(item -> {
// you op here
});
}

What's the proper generic way to reverse a stream?

如果流没有指定< < em >遇到顺序/ em >,不要。 (!s.spliterator().hasCharacteristics(java.util.Spliterator.ORDERED)) < / p >