Java 8流逆序

vac*_*ach 135 java java-8 java-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);
Run Code Online (Sandbox Code Playgroud)

IntStream我会得到这个编译器错误

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

我在这里想念的是什么?

Stu*_*rks 67

对于生成反向的具体问题IntStream,请尝试以下方法:

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

这避免了装箱和分拣.

对于如何反转任何类型的流的一般问题,我不知道有一种"正确"的方式.我有几种方法可以想到.两者最终都存储了流元素.我不知道如何在不存储元素的情况下反转流.

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

@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]);
}
Run Code Online (Sandbox Code Playgroud)

另一种技术使用收集器将项目累积到反向列表中.这会在ArrayList对象的前面进行大量插入,因此会进行大量复制.

Stream<T> input = ... ;
List<T> output =
    input.collect(ArrayList::new,
                  (list, e) -> list.add(0, e),
                  (list1, list2) -> list1.addAll(0, list2));
Run Code Online (Sandbox Code Playgroud)

使用某种自定义数据结构编写更高效的可逆回收器可能是可能的.

更新2016-01-29

由于这个问题最近引起了一些关注,我想我应该更新我的答案来解决问题,插入前面ArrayList.对于大量元素,这将是非常低效的,需要O(N ^ 2)复制.

最好使用ArrayDeque替代,它有效地支持前面的插入.一个小皱纹是我们不能使用三个arg形式Stream.collect(); 它要求将第二个arg的内容合并到第一个arg中,并且没有"add-all-at-front"批量操作Deque.相反,我们使用addAll()将第一个arg的内容追加到第二个arg的末尾,然后我们返回第二个arg.这需要使用Collector.of()工厂方法.

完整的代码是这样的:

Deque<String> output =
    input.collect(Collector.of(
        ArrayDeque::new,
        (deq, t) -> deq.addFirst(t),
        (d1, d2) -> { d2.addAll(d1); return d2; }));
Run Code Online (Sandbox Code Playgroud)

结果是a Deque而不是a List,但这不应该是一个很大的问题,因为它可以很容易地以现在颠倒的顺序进行迭代或流式传输.

  • 或者:`IntStream.iterate(to-1,i-> i-1).limit(to-from)` (12认同)
  • @Holger不幸的是,该解决方案无法正确处理溢出. (2认同)
  • @Brandon Mintern:确实,您必须使用`.limit(endExcl-(long)startIncl)`来代替,但是对于如此大的流,无论如何它都是不鼓励的,因为它的效率要比基于range的解决方案低得多。在我写评论时,我还不了解效率差异。 (2认同)

Kis*_*n B 35

优雅的解决方

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);
Run Code Online (Sandbox Code Playgroud)

  • 这假设我们希望元素以相反的顺序排序*.问题是关于颠倒流的顺序. (12认同)
  • 它优雅但不完全正常工作,因为它似乎要求列表的元素是"可比较的"...... (6认同)

Rea*_*ted 33

这里的许多解决方案排序或反转IntStream,但这不必要地需要中间存储.Stuart Marks的解决方案是要走的路:

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

它正确处理溢出,通过此测试:

@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});
}
Run Code Online (Sandbox Code Playgroud)

  • 好的......"真棒而且简单"......这个解决方案有没有处理过,[Stuart Marks'更简单的解决方案](http://stackoverflow.com/a/24011264/2711488)还没有处理过多个问题半年前? (3认同)

Ven*_*aju 30

一般问题:

Stream不存储任何元素.

因此,如果不将元素存储在某个中间集合中,则无法以相反的顺序迭代元素.

Stream.of("1", "2", "20", "3")
      .collect(Collectors.toCollection(ArrayDeque::new)) // or LinkedList
      .descendingIterator()
      .forEachRemaining(System.out::println);
Run Code Online (Sandbox Code Playgroud)

更新:将LinkedList更改为ArrayDeque(更好)请参阅此处了解详细信息

打印:

3

20

2

1
Run Code Online (Sandbox Code Playgroud)

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

具体问题:

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

IntStream.iterate(to - 1, i -> i - 1).limit(to - from)
Run Code Online (Sandbox Code Playgroud)

  • 某些流操作,例如`sorted`和`distinct`实际上存储了一个中间结果.有关此内容的一些信息,请参阅[package API docs](http://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#StreamOps). (3认同)

com*_*nad 15

没有外部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;
        });
    }

}
Run Code Online (Sandbox Code Playgroud)


Yuj*_*are 15

如果实施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);
Run Code Online (Sandbox Code Playgroud)

  • 这不会反转流。它以相反的顺序对流进行排序。因此,如果您有“Stream.of(1,3,2)”,结果将是“Stream.of(3,2,1)”而不是“Stream.of(2,3,1)” (22认同)

luk*_*uke 11

如何不这样做:

  • 不要使用.sorted(Comparator.reverseOrder())or .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);
Run Code Online (Sandbox Code Playgroud)

输出:
[3, 5, 2, 4, 1]

相同的String

List<String> stringList = Arrays.asList("A", "D", "B", "E", "C");
Collections.reverse(stringList);
System.out.println(stringList);
Run Code Online (Sandbox Code Playgroud)

输出:
[C, E, B, D, A]

不要用.sorted((a, b) -> -1)
它违反了比较器合同,可能仅适用于某些情况,即。仅在单线程上,而不是在并行上。
洋基解释:

(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);
Run Code Online (Sandbox Code Playgroud)

正例输出:
[3, 5, 2, 4, 1]

并行流或其他 JVM 实现中的可能输出:
[4, 1, 2, 3, 5]

相同的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);
Run Code Online (Sandbox Code Playgroud)

正例输出:
[C, E, B, D, A]

并行流或其他 JVM 实现中的可能输出:
[A, E, B, D, C]

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

lex*_*ope 10

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

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);
}
Run Code Online (Sandbox Code Playgroud)

并使用它像:

stream.collect(inReverse()).forEach(t -> ...)
Run Code Online (Sandbox Code Playgroud)

我以正向顺序使用ArrayList来有效地插入收集项目(在列表的末尾),并使用Guava Lists.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);
    }
}
Run Code Online (Sandbox Code Playgroud)


Joh*_*ean 9

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

  StreamUtils.reverse(Stream.of("1", "2", "20", "3"))
             .forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)

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

如果您已经有一个List,它将更有效率

  StreamUtils.reversedStream(Arrays.asList("1", "2", "20", "3"))
             .forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)


vac*_*ach 6

这是我提出的解决方案:

private static final Comparator<Integer> BY_ASCENDING_ORDER = Integer::compare;
private static final Comparator<Integer> BY_DESCENDING_ORDER = BY_ASCENDING_ORDER.reversed();
Run Code Online (Sandbox Code Playgroud)

然后使用那些比较器:

IntStream.range(-range, 0).boxed().sorted(BY_DESCENDING_ORDER).forEach(// etc...
Run Code Online (Sandbox Code Playgroud)

  • [`Collections.reverseOrder()`](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#reverseOrder--)从Java 1.2开始就存在并与`Integer一起使用' ... (8认同)
  • 这只是您"特定问题"的答案,而不是您的"一般性问题". (2认同)

luk*_*ens 5

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

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

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

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


Jer*_*ome 5

这个实用方法怎么样?

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);
}
Run Code Online (Sandbox Code Playgroud)

似乎适用于所有情况而不会重复。


Ole*_*hov 5

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

\n\n

Java 9开始,您可以使用三参数版本IntStream.iterate(...)

\n\n
IntStream.iterate(10, x -> x >= 0, x -> x - 1).forEach(System.out::println);\n\n// Out: 10 9 8 7 6 5 4 3 2 1 0\n
Run Code Online (Sandbox Code Playgroud)\n\n

在哪里:

\n\n

IntStream.iterate\xe2\x80\x8b(int seed, IntPredicate hasNext, IntUnaryOperator next);

\n\n
    \n
  • seed- 初始元素;
  • \n
  • hasNext- 应用于元素的谓词以确定\n流必须终止的时间;
  • \n
  • next- 应用于前一个元素以生成\n新元素的函数。
  • \n
\n