wake-up-neo.net

Java 8-Stream in umgekehrter Reihenfolge

Allgemeine Frage: Wie kann ich einen Stream richtig umkehren? Angenommen, wir wissen nicht, aus welcher Art von Elementen der Stream besteht, wie lautet der generelle Weg, einen Stream umzukehren?

Spezifische Frage: 

IntStream bietet Range-Methode zum Generieren von Ganzzahlen in einem bestimmten Bereich IntStream.range(-range, 0), jetzt, wo ich es umkehren möchte, funktioniert der Umstellungsbereich von 0 auf negativ nicht. Ich kann auch nicht Integer::compare

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

mit IntStream bekomme ich diesen Compiler-Fehler

Fehler: (191, 0) ajc: Die Methode sorted() im Typ IntStream ist für die Argumente nicht anwendbar (Integer::compare)

was fehlt mir hier?

109
vach

Versuchen Sie Folgendes, um eine umgekehrte IntStream zu erzeugen:

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

Dadurch wird das Boxen und Sortieren vermieden.

Für die allgemeine Frage, wie man einen Stream eines beliebigen Typs umkehren kann, weiß ich nicht, dass es einen "richtigen" Weg gibt. Es gibt einige Möglichkeiten, an die ich denken kann. Beide speichern die Stream-Elemente. Ich kenne keine Möglichkeit, einen Stream umzukehren, ohne die Elemente zu speichern.

Dieser erste Weg speichert die Elemente in einem Array und liest sie in umgekehrter Reihenfolge in einen Stream. Da wir den Laufzeittyp der Stream-Elemente nicht kennen, können Sie das Array nicht richtig eingeben, sodass eine ungeprüfte Besetzung erforderlich ist.

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

Eine andere Technik verwendet Sammler, um die Elemente in einer umgekehrten Liste zu sammeln. Dies führt zu vielen Einfügungen an der Vorderseite von ArrayList-Objekten. Es wird also viel kopiert.

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

Es ist wahrscheinlich möglich, mithilfe einer angepassten Datenstruktur einen wesentlich effizienteren Umkehrkollektor zu schreiben.

UPDATE 2016-01-29

Da diese Frage in letzter Zeit etwas Beachtung gefunden hat, sollte ich meine Antwort aktualisieren, um das Problem mit dem Einfügen an der Vorderseite von ArrayList zu lösen. Dies wird bei einer großen Anzahl von Elementen furchtbar ineffizient sein, was das Kopieren von O (N ^ 2) erfordert.

Es ist vorzuziehen, stattdessen eine ArrayDeque zu verwenden, die das Einfügen an der Vorderseite effizient unterstützt. Eine kleine Falte ist, dass wir die Drei-Arg-Form von Stream.collect() nicht verwenden können. Der Inhalt des zweiten Arg muss mit dem ersten Arg zusammengeführt werden. Für Deque gibt es keine Bulk-Operation "Add-All-at-Front". Stattdessen verwenden wir addAll(), um den Inhalt des ersten Argons an das Ende des zweiten Arguments anzuhängen, und geben dann den zweiten zurück. Dies erfordert die Verwendung der Factory-Methode Collector.of().

Der vollständige Code lautet:

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

Das Ergebnis ist eine Deque anstelle einer List, aber das sollte kein großes Problem sein, da es leicht iteriert oder in der nun umgekehrten Reihenfolge gestreamt werden kann.

55
Stuart Marks

Elegante Lösung 

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

Viele der Lösungen hier sortieren oder kehren die IntStream um, dies erfordert jedoch unnötigerweise eine Zwischenspeicherung. Stuart Marks Lösung ist der Weg zu gehen:

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

Es behandelt auch den Überlauf korrekt und besteht diesen Test:

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

Allgemeine Frage:

Stream speichert keine Elemente.

Es ist also nicht möglich, Elemente in umgekehrter Reihenfolge zu iterieren, ohne die Elemente in einer Zwischensammlung zu speichern.

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

Update: LinkedList in ArrayDeque geändert (besser) siehe hier für Details

Drucke:

3

20

2

1

Übrigens ist die Verwendung der sort-Methode beim Sortieren nicht korrekt, NOT wird jedoch umgekehrt (vorausgesetzt, der Stream kann ungeordnete Elemente enthalten).

Spezifische Frage: 

Ich fand das einfach, einfacher und intuitiver (Copied @Holger comment)

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

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

}
13
comonad

Wenn Comparable<T> (z. B. Integer, String, Date) implementiert ist, können Sie dies mit Comparator.reverseOrder() tun.

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

Sie können Ihren eigenen Collector definieren, der die Elemente in umgekehrter Reihenfolge sammelt:

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

Und benutze es wie:

stream.collect(inReverse()).forEach(t -> ...)

Ich verwende eine ArrayList in Vorwärtsreihenfolge, um die Elemente (am Ende der Liste) effizient einzufügen, und Guava Lists.

Hier sind einige Testfälle für den benutzerdefinierten Collector:

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 -rea StreamUtils hat eine umgekehrte Stream-Methode ( javadoc ). 

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

Es sammelt eine ArrayList und verwendet dann die ListIterator-Klasse, die in beide Richtungen iterieren kann, um die Liste nach hinten zu iterieren. 

Wenn Sie bereits eine Liste haben, ist diese effizienter

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

Hier ist die Lösung, die ich mir ausgedacht habe:

private static final Comparator<Integer> BY_ASCENDING_ORDER = Integer::compare;
private static final Comparator<Integer> BY_DESCENDING_ORDER = BY_ASCENDING_ORDER.reversed();

dann diese Komparatoren verwenden:

IntStream.range(-range, 0).boxed().sorted(BY_DESCENDING_ORDER).forEach(// etc...
6
vach

Ich würde vorschlagen, jOOλ zu verwenden, es ist eine großartige Bibliothek, die Java 8-Streams und Lambdas um nützliche Funktionen erweitert.

Sie können dann Folgendes tun:

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

So einfach ist das. Es ist eine recht leichte Bibliothek, die es wert ist, in jedes Java 8-Projekt aufgenommen zu werden.

4
lukens

Einfachste Möglichkeit (Simple Collect - unterstützt parallele Streams):

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

Fortgeschrittener Weg (unterstützt parallel laufende Streams):

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

Beachten Sie, dass Sie sich schnell auf andere Streamtypen (IntStream, ...) erstrecken können.

Testen:

// Use parallel if you wish only
revert(Stream.of("One", "Two", "Three", "Four", "Five", "Six").parallel())
    .forEachOrdered(System.out::println);

Ergebnisse:

Six
Five
Four
Three
Two
One

Zusätzliche Hinweise: Der simplest way ist nicht so nützlich, wenn er mit anderen Stream-Operationen verwendet wird (der Collect-Join unterbricht die Parallelität). Der advance way hat dieses Problem nicht, und er behält auch die anfänglichen Eigenschaften des Streams bei, beispielsweise SORTED. Dies ist der Weg, um mit anderen Streamoperationen nach der Umkehrung zu arbeiten.

3
Tet

Wie wäre es mit dieser Gebrauchsmethode?

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

Scheint mit allen Fällen ohne Doppelarbeit zu funktionieren.

2
Jerome

In Bezug auf die spezifische Frage der Erzeugung eines umgekehrten IntStream:

ausgehend von Java 9 können Sie die aus drei Argumenten bestehende Version von IntStream.iterate(...) verwenden:

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

wo:

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

  • seed - das ursprüngliche Element;
  • hasNext - Ein Prädikat, das auf Elemente angewendet werden soll, um zu bestimmen, wann der Stream beendet werden muss.
  • next - Eine Funktion, die auf das vorherige Element angewendet wird, um ein neues Element zu erstellen.
2

Man könnte einen Collector schreiben, der Elemente in umgekehrter Reihenfolge sammelt:

public static <T> Collector<T, ?, Stream<T>> reversed() {
    return Collectors.collectingAndThen(Collectors.toList(), list -> {
        Collections.reverse(list);
        return list.stream();
    });
}

Und benutze es so:

Stream.of(1, 2, 3, 4, 5).collect(reversed()).forEach(System.out::println);

Ursprüngliche Antwort (enthält einen Fehler - für parallele Streams funktioniert es nicht richtig):

Eine Umkehrmethode für allgemeine Zwecke könnte folgendermaßen aussehen:

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

Die Beantwortung einer bestimmten Rückfragefrage mit IntStream funktionierte für mich:

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

Nicht nur Java8, aber wenn Sie die Lists.reverse () - Methode von guava zusammen verwenden, können Sie dies leicht erreichen:

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

die einfachste Lösung ist die Verwendung von List::listIterator und 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);
1
Adrian

Als Referenz habe ich das gleiche Problem betrachtet, ich wollte den String-Wert von Stream-Elementen in umgekehrter Reihenfolge zusammenfügen.

itemList = {last, middle, first} => first, middle, last

Ich begann eine Zwischensammlung mit collectingAndThen aus comonad oder dem ArrayDeque-Sammler von Stuart Marks zu verwenden, obwohl ich mit der Zwischensammlung und dem Streaming nicht zufrieden war

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

Also habe ich über Stuart Marks Antwort iteriert, dass die Collector.of Fabrik verwendet wurde, die den interessanten Finisher Lambda hat.

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

Da in diesem Fall der Stream nicht parallel ist, ist der Combiner nicht so relevant, ich verwende insert ohnehin aus Gründen der Codekonsistenz, aber es spielt keine Rolle, da er davon abhängt, welcher Stringbuilder zuerst erstellt wird.

Ich habe mir den StringJoiner angeschaut, aber er hat keine insert-Methode.

1
Brice

Umkehrung der Zeichenfolge oder eines Arrays

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

split kann basierend auf dem Begrenzer oder dem Leerzeichen geändert werden

0
sai manoj

So mache ich es.

Ich mag die Idee nicht, eine neue Kollektion zu erstellen, und es zu wiederholen.

Die IntStream-Map-Idee ist ziemlich ordentlich, aber ich bevorzuge die IntStream-Iterate-Methode, denn ich denke, die Idee eines Countdowns zu Zero wird mit der Iterat-Methode besser ausgedrückt und ist einfacher zu verstehen, wenn man das Array von hinten nach vorne durchläuft.

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

Hier sind einige Tests, um zu beweisen, dass es funktioniert:

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

In all dem sehe ich nicht die Antwort, zu der ich zuerst gehen würde.

Dies ist nicht direkt eine direkte Antwort auf die Frage, aber es ist eine mögliche Lösung für das Problem.

Bauen Sie die Liste einfach rückwärts auf. Wenn möglich, verwenden Sie eine LinkedList anstelle einer ArrayList. Wenn Sie Elemente hinzufügen, verwenden Sie "Push" anstelle von "Hinzufügen". Die Liste wird in umgekehrter Reihenfolge erstellt und dann ohne Manipulation korrekt gestreamt.

Dies passt nicht zu Fällen, in denen es sich um primitive Arrays oder Listen handelt, die bereits auf verschiedene Weise verwendet werden, aber in überraschend vielen Fällen gut funktionieren.

0
Bill K

ArrayDeque sind schneller im Stack als ein Stack oder eine LinkedList. "Push ()" fügt Elemente an der Vorderseite des Deque ein

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