web-dev-qa-db-ja.com

Java 8ストリームの逆順

一般的な質問:ストリームをリバースする適切な方法は何ですか?ストリームがどのタイプの要素から構成されているのかわからないと仮定して、ストリームをリバースするための一般的な方法は何ですか?

具体的な質問:

IntStreamは特定の範囲IntStream.range(-range, 0)でIntegerを生成するためのrangeメソッドを提供します。範囲を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

私はここで何が足りないのですか?

122
vach

逆のIntStreamを生成するという具体的な質問については、次のようにしてください。

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

これはボクシングやソートを避けます。

どのような種類のストリームを逆にするかという一般的な質問については、「正しい」方法があることはわかりません。私が考えることができる2つの方法があります。どちらもストリーム要素を格納することになります。要素を格納せずにストリームを反転する方法がわかりません。

この最初の方法では、要素を配列に格納し、それらを逆の順序でストリームに読み出します。ストリーム要素の実行時型がわからないため、配列を正しく入力できず、キャストが必要です。

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

ある種のカスタマイズされたデータ構造を使って、はるかに効率的な逆転コレクターを書くことはおそらく可能です。

PDATE 2016-01-29

この質問は最近少し注目を集めているので、私はArrayListの前に挿入することで問題を解決するために私の答えを更新するべきだと思います。これは、O(N ^ 2)のコピーを必要とする、多数の要素では非常に非効率的です。

代わりに効率的に先頭への挿入をサポートするArrayDequeを使用することをお勧めします。小さなしわがあるのは、3引数の形式のStream.collect()を使用できないことです。 2番目の引数の内容を最初の引数にマージする必要があります。また、Dequeには「前に追加」の一括操作はありません。代わりに、最初の引数の内容を2番目の引数の最後に追加するためにaddAll()を使用してから、2番目の引数を返します。これには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になりますが、逆になった順番で簡単に反復またはストリーミングできるので、それほど問題にはなりません。

60
Stuart Marks

エレガントなソリューション

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);
34
Kishan B

ここでの解決策の多くはIntStreamをソートまたはリバースしますが、それには不必要に中間ストレージが必要です。 Stuart Marksの解決策 は次の方法です。

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});
}
31
Brandon Mintern

一般的な質問:

ストリームは要素を格納しません。

したがって、要素を逆の順序で繰り返すことは、要素を何らかの中間コレクションに格納しないと不可能です。

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メソッドを使用するとソートされるので正しくありません。逆にはなりません(ストリームに順序付けられていない要素があると仮定します)。

具体的な質問

私はこれが簡単で、簡単で直感的であることに気付きました(Copied @Holgerコメント)

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

Comparable<T>IntegerStringDateなど)を実装した場合は、Comparator.reverseOrder()を使用してそれを実行できます。

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

外部ライブラリなしで...

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

}
14
comonad

逆の順序で要素を集めるあなた自身のコレクターを定義することができます:

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を使ってリストのコピーを作成せずにリストの逆ビューを効率的に挿入します。

カスタムコレクターのテストケースは次のとおりです。

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

cyclops-react 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);
9
John McClean

これが私が思いついた解決策です:

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

jOOλ を使用することをお勧めします。これは、Java 8ストリームとラムダに便利な機能を多数追加した優れたライブラリです。

その後、次の操作を実行できます。

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

そのような単純な。これはかなり軽量のライブラリです。Java8プロジェクトに追加する価値があります。

4
lukens

最も単純な方法(単純収集 - 並列ストリームをサポート):

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は、他のストリーム操作と一緒に使用した場合、あまり役に立ちません(collect joinは並列処理を中断します)。 advance wayはその問題を抱えていません、そしてそれはまたストリームの初期の特性、例えばSORTEDを保持します、そしてそれ故に、それは逆の後に他のストリーム操作と共に使う方法です。

3
Tet

この実用的な方法はどうですか?

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

重複せずにすべてのケースで動作するようです。

2
Jerome

逆の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 - 新しい要素を生成するために前の要素に適用される関数。
2
Oleksandr

逆の順序で要素を収集するコレクターを書くことができます:

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

IntStreamを使って逆方向に戻すという具体的な質問に答えると、以下のようになりました。

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

文字列または任意の配列を反転する

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

区切り文字またはスペースに基づいて分割を変更できます

1
sai manoj

純粋にJava 8ではありませんが、guavaのLists.reverse()メソッドを併用すれば、これを簡単に実現できます。

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

参考のために同じ問題を見ていましたが、ストリーム要素の文字列値を逆の順序で結合したいと思いました。

itemList = {最後、中央、最初} =>最初、中央、最後

私はcomonadからのcollectingAndThenまたはStuart MarksArrayDequeコレクターで中間コレクションを使い始めました。

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

それで、私はStuart Marksの回答を繰り返しました。それはCollector.ofファクトリを使っていました。それは興味深いfinisher 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を使用しますが、どちらの文字列ビルダが最初に構築されるかには関係ありません。

私はStringJoinerを見ました、しかしそれはinsertメソッドを持っていません。

1
Brice

これが私のやり方です。

私は新しいコレクションを作成し、それを逆に繰り返すという考えが嫌いです。

IntStream#mapの考え方はかなりきちんとしていますが、IntStream#iterateメソッドをお勧めします。これは、ゼロまでカウントダウンするという考え方がiterateメソッドで表現され、配列を前後に移動するという点で理解しやすいためです。

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

これらすべてにおいて、私が最初に進むことになる答えは見当たりません。

これは厳密には質問に対する直接的な答えではありませんが、それは問題に対する潜在的な解決策です。

まずリストを逆方向に構築してください。可能であれば、ArrayListの代わりにLinkedListを使用し、アイテムを追加するときはaddの代わりに "Push"を使用してください。リストは逆の順序で作成され、その後何の操作もせずに正しくストリーミングされます。

これは、すでにさまざまな方法で使用されているプリミティブ配列またはリストを扱っているケースには適しませんが、驚くほど多くのケースでうまくいきます。

0
Bill K

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

最も簡単な解決策は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);
0
Adrian