一般的な質問:ストリームをリバースする適切な方法は何ですか?ストリームがどのタイプの要素から構成されているのかわからないと仮定して、ストリームをリバースするための一般的な方法は何ですか?
具体的な質問:
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
)
私はここで何が足りないのですか?
逆の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
になりますが、逆になった順番で簡単に反復またはストリーミングできるので、それほど問題にはなりません。
エレガントなソリューション
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);
ここでの解決策の多くは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});
}
一般的な質問:
ストリームは要素を格納しません。
したがって、要素を逆の順序で繰り返すことは、要素を何らかの中間コレクションに格納しないと不可能です。
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)
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);
外部ライブラリなしで...
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を使ってリストのコピーを作成せずにリストの逆ビューを効率的に挿入します。
カスタムコレクターのテストケースは次のとおりです。
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);
}
}
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);
これが私が思いついた解決策です:
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...
jOOλ を使用することをお勧めします。これは、Java 8ストリームとラムダに便利な機能を多数追加した優れたライブラリです。
その後、次の操作を実行できます。
List<Integer> list = Arrays.asList(1,2,3,4);
Seq.seq(list).reverse().forEach(System.out::println)
そのような単純な。これはかなり軽量のライブラリです。Java8プロジェクトに追加する価値があります。
最も単純な方法(単純収集 - 並列ストリームをサポート):
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
を保持します、そしてそれ故に、それは逆の後に他のストリーム操作と共に使う方法です。
この実用的な方法はどうですか?
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
- 新しい要素を生成するために前の要素に適用される関数。逆の順序で要素を収集するコレクターを書くことができます:
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();
}
IntStreamを使って逆方向に戻すという具体的な質問に答えると、以下のようになりました。
IntStream.range(0, 10)
.map(x -> x * -1)
.sorted()
.map(Math::abs)
.forEach(System.out::println);
文字列または任意の配列を反転する
(Stream.of("abcdefghijklm 1234567".split("")).collect(Collectors.collectingAndThen(Collectors.toList(),list -> {Collections.reverse(list);return list;}))).stream().forEach(System.out::println);
区切り文字またはスペースに基づいて分割を変更できます
純粋にJava 8ではありませんが、guavaのLists.reverse()メソッドを併用すれば、これを簡単に実現できます。
List<Integer> list = Arrays.asList(1,2,3,4);
Lists.reverse(list).stream().forEach(System.out::println);
参考のために同じ問題を見ていましたが、ストリーム要素の文字列値を逆の順序で結合したいと思いました。
itemList = {最後、中央、最初} =>最初、中央、最後
私はcomonadからのcollectingAndThen
またはStuart Marksの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
ファクトリを使っていました。それは興味深い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
メソッドを持っていません。
これが私のやり方です。
私は新しいコレクションを作成し、それを逆に繰り返すという考えが嫌いです。
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());
}
これらすべてにおいて、私が最初に進むことになる答えは見当たりません。
これは厳密には質問に対する直接的な答えではありませんが、それは問題に対する潜在的な解決策です。
まずリストを逆方向に構築してください。可能であれば、ArrayListの代わりにLinkedListを使用し、アイテムを追加するときはaddの代わりに "Push"を使用してください。リストは逆の順序で作成され、その後何の操作もせずに正しくストリーミングされます。
これは、すでにさまざまな方法で使用されているプリミティブ配列またはリストを扱っているケースには適しませんが、驚くほど多くのケースでうまくいきます。
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();
}
最も簡単な解決策はList::listIterator
とStream::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);