Sammle aufeinanderfolgende Paare aus einem Stream


102

Bei einem Stream wie { 0, 1, 2, 3, 4 },

Wie kann ich es am elegantesten in eine gegebene Form bringen:

{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }

(vorausgesetzt natürlich, ich habe das Klassenpaar definiert)?

Bearbeiten: Hier geht es nicht ausschließlich um Ints oder primitive Streams. Die Antwort sollte für einen Stream jeglicher Art allgemein sein.


2
Der Begriff von FP ist "Partition", aber ich finde in Java keine Methode mit der gewünschten Semantik. Es hat eine Partitionierung nach einem Prädikat.
Marko Topolnik

1
In der Regel wird der Spliterator in JDK 8 zum Durchlaufen und Partitionieren verwendet. Ich werde auch versuchen, ein Beispiel zu finden.
Olimpiu POP

list.stream().map(i -> new Pair(i, i+1));
Aepurniet

2
Die entsprechende Frage zu Nicht-Streams finden Sie unter stackoverflow.com/questions/17453022/…
Raedwald,

Übrigens verwenden einige Leute beide Implementierungen Map.Entryals Pair-Klasse. (Zugegeben, einige mögen das für einen Hack halten, aber die Verwendung einer eingebauten Klasse ist praktisch.)
Basil Bourque

Antworten:


33

Meine StreamEx- Bibliothek, die Standard-Streams erweitert, bietet eine pairMapMethode für alle Stream-Typen. Bei primitiven Streams wird der Stream-Typ nicht geändert, es können jedoch einige Berechnungen durchgeführt werden. Am häufigsten werden Unterschiede berechnet:

int[] pairwiseDiffs = IntStreamEx.of(input).pairMap((a, b) -> (b-a)).toArray();

Für den Objektstrom können Sie einen beliebigen anderen Objekttyp erstellen. Meine Bibliothek bietet keine neuen vom Benutzer sichtbaren Datenstrukturen wie Pair(das ist der Teil des Bibliothekskonzepts). Wenn Sie jedoch eine eigene PairKlasse haben und diese verwenden möchten, können Sie Folgendes tun:

Stream<Pair> pairs = IntStreamEx.of(input).boxed().pairMap(Pair::new);

Oder wenn Sie bereits welche haben Stream:

Stream<Pair> pairs = StreamEx.of(stream).pairMap(Pair::new);

Diese Funktionalität wird mithilfe eines benutzerdefinierten Spliterators implementiert . Es hat einen recht geringen Overhead und kann gut parallelisiert werden. Natürlich funktioniert es mit jeder Stream-Quelle, nicht nur mit einer Direktzugriffsliste / einem Array wie vielen anderen Lösungen. In vielen Tests funktioniert es sehr gut. Hier ist ein JMH-Benchmark, bei dem wir alle Eingabewerte vor einem größeren Wert mit unterschiedlichen Ansätzen finden (siehe diese Frage).


Danke dir! Je mehr ich diese Bibliothek studiere, desto mehr liebe ich sie. Ich könnte endlich anfangen, Streams zu verwenden. ( StreamEximplementiert Iterable! Hurra!)
Aleksandr Dubinsky

Können Sie zeigen, wie Sie eine Antwort Streamin eine einwickeln, um Ihre Antwort zu 100% zu vervollständigen StreamEx?
Aleksandr Dubinsky

3
@AleksandrDubinsky: einfach benutzen StreamEx.of(stream). Es gibt andere praktische statische Methoden zum Erstellen des Streams Collection, Arrays Readerusw. Die Antwort wurde bearbeitet.
Tagir Valeev

@TagirValeev wird pairMapin sequentiellen Streams bestellt? Eigentlich hätte ich gerne forPairsOrdered (), aber da es keine solche Methode gibt, kann ich sie irgendwie simulieren? stream.ordered().forPairs()oder stream().pairMap().forEachOrdered()?
Askar Kalykov

1
@AskarKalykov, das pairMapist die Zwischenoperation mit nicht störender zustandsloser Mapper-Funktion, deren Reihenfolge nicht wie bei einfach angegeben wird map. Das forPairsist nach Spezifikation ungeordnet, aber ungeordnete Operationen sind de facto für sequentielle Streams geordnet. Es wäre schön, wenn Sie Ihr ursprüngliches Problem als separate Stackoverflow-Frage formulieren würden, um mehr Kontext bereitzustellen.
Tagir Valeev

74

Die Java 8-Streams-Bibliothek ist in erster Linie darauf ausgerichtet, Streams für die parallele Verarbeitung in kleinere Blöcke aufzuteilen. Daher sind Stateful-Pipeline-Phasen sehr begrenzt, und beispielsweise das Abrufen des Index des aktuellen Stream-Elements und der Zugriff auf benachbarte Stream-Elemente werden nicht unterstützt.

Ein typischer Weg, um diese Probleme mit einigen Einschränkungen zu lösen, besteht natürlich darin, den Stream durch Indizes zu steuern und sich darauf zu verlassen, dass die Werte in einer Datenstruktur mit wahlfreiem Zugriff wie einer ArrayList verarbeitet werden, aus der die Elemente abgerufen werden können. Wenn die Werte in arrayListwären, könnte man die Paare wie gewünscht erzeugen, indem man so etwas macht:

    IntStream.range(1, arrayList.size())
             .mapToObj(i -> new Pair(arrayList.get(i-1), arrayList.get(i)))
             .forEach(System.out::println);

Die Einschränkung besteht natürlich darin, dass die Eingabe kein unendlicher Strom sein kann. Diese Pipeline kann jedoch parallel ausgeführt werden.


5
"Die Eingabe kann kein unendlicher Strom sein." Tatsächlich kann die Eingabe überhaupt kein Stream sein. Die Eingabe ( arrayList) ist in der Tat eine Sammlung, weshalb ich sie nicht als Antwort markiert habe. (Aber herzlichen Glückwunsch zu Ihrem Goldabzeichen!)
Aleksandr Dubinsky

16

Dies ist nicht elegant, es ist eine hackige Lösung, funktioniert aber für unendliche Streams

Stream<Pair> pairStream = Stream.iterate(0, (i) -> i + 1).map( // natural numbers
    new Function<Integer, Pair>() {
        Integer previous;

        @Override
        public Pair apply(Integer integer) {
            Pair pair = null;
            if (previous != null) pair = new Pair(previous, integer);
            previous = integer;
            return pair;
        }
    }).skip(1); // drop first null

Jetzt können Sie Ihren Stream auf die gewünschte Länge beschränken

pairStream.limit(1_000_000).forEach(i -> System.out.println(i));

PS Ich hoffe, es gibt eine bessere Lösung, so etwas wie Clojure(partition 2 1 stream)


6
Ein großes Lob für den Hinweis, dass anonyme Klassen eine manchmal nützliche Alternative zu Lambdas sind.
Aleksandr Dubinsky

2
@aepurniet Ich gehe davon aus, dass es nicht richtig funktioniert. Laut dem parallelStreamDokument: "Um korrektes Verhalten zu bewahren, müssen diese Verhaltensparameter nicht störend und in den meisten Fällen zustandslos sein"
Missgeschick

14
Dies widerspricht völlig dem Design des Streams-Frameworks und verstößt direkt gegen den Vertrag der Map-API, da die anonyme Funktion nicht zustandslos ist. Versuchen Sie, dies mit einem parallelen Stream und mehr Daten auszuführen, damit das Stream-Framework mehr funktionierende Threads erstellt, und Sie werden das Ergebnis sehen: Seltene zufällige "Fehler" sind fast unmöglich zu reproduzieren und schwer zu erkennen, bis Sie über genügend Daten verfügen (in der Produktion?). Dies kann katastrophal sein.
Mario Rossi

4
@AleksandrDubinsky Sie sind falsch, wenn Limit / Skip parallelisierbar ist. Die in JDK bereitgestellte Implementierung funktioniert tatsächlich parallel. Da die Operation an die Reihenfolge gebunden ist, bietet die Parallelisierung möglicherweise nicht immer einen Leistungsvorteil, in Situationen mit hohem Q jedoch.
Brian Goetz

4
@AleksandrDubinsky Falsch. Es kann ein zufälliges Element überspringen, wenn der Stream ungeordnet ist (hat keine definierte Begegnungsreihenfolge, daher gibt es logischerweise kein "erstes" oder "n-tes" Element, nur Elemente). Unabhängig davon, ob der Stream geordnet oder ungeordnet ist, war das Überspringen immer möglich parallel arbeiten. Es gibt nur weniger Parallelität zu extrahieren, wenn der Stream bestellt wird, aber es ist immer noch parallel.
Brian Goetz

15

Ich habe einen spliterator Wrapper implementiert , die alle nimmt nElemente Taus dem ursprünglichen spliterator und produziert List<T>:

public class ConsecutiveSpliterator<T> implements Spliterator<List<T>> {

    private final Spliterator<T> wrappedSpliterator;

    private final int n;

    private final Deque<T> deque;

    private final Consumer<T> dequeConsumer;

    public ConsecutiveSpliterator(Spliterator<T> wrappedSpliterator, int n) {
        this.wrappedSpliterator = wrappedSpliterator;
        this.n = n;
        this.deque = new ArrayDeque<>();
        this.dequeConsumer = deque::addLast;
    }

    @Override
    public boolean tryAdvance(Consumer<? super List<T>> action) {
        deque.pollFirst();
        fillDeque();
        if (deque.size() == n) {
            List<T> list = new ArrayList<>(deque);
            action.accept(list);
            return true;
        } else {
            return false;
        }
    }

    private void fillDeque() {
        while (deque.size() < n && wrappedSpliterator.tryAdvance(dequeConsumer))
            ;
    }

    @Override
    public Spliterator<List<T>> trySplit() {
        return null;
    }

    @Override
    public long estimateSize() {
        return wrappedSpliterator.estimateSize();
    }

    @Override
    public int characteristics() {
        return wrappedSpliterator.characteristics();
    }
}

Die folgende Methode kann verwendet werden, um einen aufeinanderfolgenden Stream zu erstellen:

public <E> Stream<List<E>> consecutiveStream(Stream<E> stream, int n) {
    Spliterator<E> spliterator = stream.spliterator();
    Spliterator<List<E>> wrapper = new ConsecutiveSpliterator<>(spliterator, n);
    return StreamSupport.stream(wrapper, false);
}

Beispielnutzung:

consecutiveStream(Stream.of(0, 1, 2, 3, 4, 5), 2)
    .map(list -> new Pair(list.get(0), list.get(1)))
    .forEach(System.out::println);

Wiederholt das jedes Element zweimal?
Aleksandr Dubinsky

Nee. Es wird ein neuer Stream mit List<E>Elementen erstellt. Jede Liste enthält naufeinanderfolgende Elemente aus dem ursprünglichen Stream. Überprüfen Sie es selbst;)
Tomek Rękawek

Könnten Sie Ihre Antwort so ändern, dass jedes Element (außer dem ersten und dem letzten) wiederholt wird?
Aleksandr Dubinsky

4
+1 Ich denke, das ist gute Arbeit und sollte zusätzlich zur Partitionsgröße auf jede Schrittgröße verallgemeinert werden. Es besteht ein großer Bedarf an einer (partition size step)Funktion, und dies ist der beste Weg, um sie zu erhalten.
Marko Topolnik

3
Ziehen Sie die Verwendung ArrayDequefür die Leistung vor LinkedList.
Marko Topolnik

14

Sie können dies mit der Stream.reduce () -Methode tun (ich habe keine anderen Antworten mit dieser Technik gesehen).

public static <T> List<Pair<T, T>> consecutive(List<T> list) {
    List<Pair<T, T>> pairs = new LinkedList<>();
    list.stream().reduce((a, b) -> {
        pairs.add(new Pair<>(a, b));
        return b;
    });
    return pairs;
}

1
Es würde (1,2) (2,3) anstelle von (1,2) (3,4) zurückgeben. Ich bin mir auch nicht sicher, ob es in der richtigen Reihenfolge angewendet wird (dafür gibt es sicherlich keine Garantie).
Aleksandr Dubinsky

1
Bitte überprüfen Sie die Frage, das ist das beabsichtigte Verhalten @Aleksandr Dubinsky
SamTebbs33

3
Ahh, ja, sorry. Und zu denken, ich habe es geschrieben.
Aleksandr Dubinsky


6

Sie können dies in Cyclops-React (ich trage zu dieser Bibliothek bei) mit dem Sliding-Operator tun .

  LazyFutureStream.of( 0, 1, 2, 3, 4 )
                  .sliding(2)
                  .map(Pair::new);

Oder

   ReactiveSeq.of( 0, 1, 2, 3, 4 )
                  .sliding(2)
                  .map(Pair::new);

Angenommen, der Pair-Konstruktor kann eine Sammlung mit 2 Elementen akzeptieren.

Wenn Sie um 4 gruppieren und um 2 erhöhen möchten, wird dies ebenfalls unterstützt.

     ReactiveSeq.rangeLong( 0L,Long.MAX_VALUE)
                .sliding(4,2)
                .forEach(System.out::println);

Entsprechende statische Methoden zum Erstellen einer verschiebbaren Ansicht über java.util.stream.Stream werden auch in der StreamUtils- Klasse von cyclops-Streams bereitgestellt .

       StreamUtils.sliding(Stream.of(1,2,3,4),2)
                  .map(Pair::new);

Hinweis: - Für den Single-Thread-Betrieb wäre ReactiveSeq besser geeignet. LazyFutureStream erweitert ReactiveSeq, ist jedoch hauptsächlich auf die gleichzeitige / parallele Verwendung ausgerichtet (es handelt sich um einen Stream of Futures).

LazyFutureStream erweitert ReactiveSeq, wodurch Seq vom fantastischen jOOλ (das java.util.stream.Stream erweitert) erweitert wird, sodass die von Lukas vorgestellten Lösungen auch mit beiden Stream-Typen funktionieren würden. Für alle Interessierten sind die Hauptunterschiede zwischen den Fenster- / Gleitoperatoren der offensichtliche relative Kompromiss zwischen Leistung und Komplexität und die Eignung für die Verwendung mit unendlichen Streams (das Gleiten verbraucht nicht den Stream, sondern puffert, während er fließt).


Auf diese Weise erhalten Sie [(0,1) (2,3) ...], aber die Frage lautet [(0,1) (1,2) ...]. Bitte sehen Sie meine Antwort mit RxJava ...
frhack

1
Sie haben Recht, mein Schlechtes, ich habe die Frage falsch verstanden - der Gleitoperator ist der richtige, der hier verwendet wird. Ich werde meine Antwort aktualisieren - danke!
John McClean

4

Die Protonenpack-Bibliothek bietet die Fensterfunktionalität. Bei einer Pair-Klasse und einem Stream können Sie dies folgendermaßen tun:

Stream<Integer> st = Stream.iterate(0 , x -> x + 1);
Stream<Pair<Integer, Integer>> pairs = StreamUtils.windowed(st, 2, 1)
                                                  .map(l -> new Pair<>(l.get(0), l.get(1)))
                                                  .moreStreamOps(...);

Jetzt pairsenthält der Stream:

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, ...) and so on

Es sieht jedoch so aus, als müssten Sie stzweimal erstellen ! Kann diese Bibliothek das Problem mit einem einzigen Stream lösen?
Aleksandr Dubinsky

@AleksandrDubinsky Ich glaube nicht, dass es mit den aktuellen Spliteratoren verfügbar ist. Ich habe eine Ausgabe eingereicht github.com/poetix/protonpack/issues/9
Alexis C.

@AleksandrDubinsky Die windowedFunktionalität wurde hinzugefügt! Siehe die Bearbeitung.
Alexis C.

1
Warum löschen Sie nicht Ihre alte Antwort, damit andere Benutzer die Lösung und nicht den Verlauf sehen können?
Aleksandr Dubinsky

4

Aufeinanderfolgende Paare finden

Wenn Sie bereit sind, eine Bibliothek eines Drittanbieters zu verwenden und keine Parallelität benötigen, bietet jOOλ folgende Fensterfunktionen im SQL-Stil

System.out.println(
Seq.of(0, 1, 2, 3, 4)
   .window()
   .filter(w -> w.lead().isPresent())
   .map(w -> tuple(w.value(), w.lead().get())) // alternatively, use your new Pair() class
   .toList()
);

Nachgeben

[(0, 1), (1, 2), (2, 3), (3, 4)]

Die lead()Funktion greift über das Fenster auf den nächsten Wert in Durchlaufreihenfolge zu.

Aufeinanderfolgende Tripel / Vierfache / n-Tupel finden

Eine Frage in den Kommentaren war die Frage nach einer allgemeineren Lösung, bei der nicht Paare, sondern n-Tupel (oder möglicherweise Listen) gesammelt werden sollten. Hier ist also ein alternativer Ansatz:

int n = 3;

System.out.println(
Seq.of(0, 1, 2, 3, 4)
   .window(0, n - 1)
   .filter(w -> w.count() == n)
   .map(w -> w.window().toList())
   .toList()
);

Eine Liste von Listen erstellen

[[0, 1, 2], [1, 2, 3], [2, 3, 4]]

Ohne das filter(w -> w.count() == n)wäre das Ergebnis

[[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4], [4]]

Haftungsausschluss: Ich arbeite für die Firma hinter jOOλ


Interessant. Was ist, wenn ich 3 oder mehr Elemente gruppieren muss? Verwenden w.lead().lead()?
Raul Santelices

1
@ RaulSantelices: tuple(w.value(), w.lead(1), w.lead(2))wäre eine Option. Ich habe meine Antwort mit einer allgemeineren Lösung fürlength = n
Lukas Eder

1
Ich verstehe richtig, dass dies .window()keine verzögerte Operation ist, bei der der gesamte Eingabestream in einer Zwischensammlung gesammelt und dann ein neuer Stream daraus erstellt wird.
Tagir Valeev

@TagirValeev: Ja, das ist die aktuelle Implementierung. Im obigen Fall (kein Comparatorwird Neuordnungs Fenster verwendet wird ), dann eine Optimierung wie dies möglich wäre, und wird wahrscheinlich in Zukunft umgesetzt werden.
Lukas Eder


2

Wir können RxJava (sehr leistungsfähige reaktive Erweiterungsbibliothek ) verwenden.

IntStream intStream  = IntStream.iterate(1, n -> n + 1);

Observable<List<Integer>> pairObservable = Observable.from(intStream::iterator).buffer(2,1);

pairObservable.take(10).forEach(b -> {
            b.forEach(n -> System.out.println(n));
            System.out.println();
        });

Der Puffer Operator wandelt ein beobachtbares dass aussendet Artikel in einer beobachtbaren dass aussendet Sammlungen dieser Elemente gepuffert ..


1
Ich habe Observable.zip(obs, obs.skip(1), pair->{...})bis jetzt verwendet! Ich wusste nicht, dass Observable.bufferes eine Version mit einem Schritt gibt (und ich bin an den zipTrick von Python gewöhnt). +1
Reut Sharabani

1

Die Operation ist im Wesentlichen zustandsbehaftet, daher nicht wirklich, welche Streams gelöst werden sollen - siehe Abschnitt "Statusloses Verhalten" im Javadoc :

Der beste Ansatz besteht darin, statusbehaftete Verhaltensparameter zu vermeiden, um Operationen vollständig zu streamen

Eine Lösung besteht darin, den Status in Ihren Stream über einen externen Zähler einzuführen, obwohl dies nur mit einem sequentiellen Stream funktioniert.

public static void main(String[] args) {
    Stream<String> strings = Stream.of("a", "b", "c", "c");
    AtomicReference<String> previous = new AtomicReference<>();
    List<Pair> collect = strings.map(n -> {
                            String p = previous.getAndSet(n);
                            return p == null ? null : new Pair(p, n);
                        })
                        .filter(p -> p != null)
                        .collect(toList());
    System.out.println(collect);
}


static class Pair<T> {
    private T left, right;
    Pair(T left, T right) { this.left = left; this.right = right; }
    @Override public String toString() { return "{" + left + "," + right + '}'; }
}

Die Frage lautet, aufeinanderfolgende Elemente eines Eingabestreams zu sammeln, nicht nur aufeinanderfolgende ganze Zahlen. Eine wichtige Klarstellung der Terminologie : Stream! = "Lambdas".
Aleksandr Dubinsky

Sie können AtomicInteger durch eine AtomicReference ersetzen. Die Alternative besteht darin, Ihren eigenen Kollektor zu rollen oder externe Bibliotheken wie in diesem Beispiel zu verwenden: stackoverflow.com/a/30090528/829571
assylias

Siehe meine Bearbeitung. Ich bin mir auch nicht sicher, ob ich deinen Kommentar zu Lambda! = Stream verstehe. Die andere Antwort, die eine anonyme Klasse verwendet, macht im Wesentlichen dasselbe, außer dass der Status von der anonymen Klasse gehalten wird, anstatt extern zu sein ...
Assylias

1
Das funktioniert. Die StreamExBibliothek ist auch ein guter Fund und könnte eine Antwort für sich sein. Mein Kommentar zu "Streams! = Lambdas" bezieht sich auf Sie mit der Aussage "Die Operation ist im Wesentlichen zustandsbehaftet, also nicht wirklich, was Lambdas lösen sollen." Ich denke, Sie wollten das Wort "Streams" verwenden.
Aleksandr Dubinsky

Oh, ich verstehe - das habe ich klargestellt.
Assylias

0

In Ihrem Fall würde ich meine benutzerdefinierte IntFunction schreiben, die den zuletzt übergebenen int verfolgt, und diesen verwenden, um den ursprünglichen IntStream zuzuordnen.

import java.util.function.IntFunction;
import java.util.stream.IntStream;

public class PairFunction implements IntFunction<PairFunction.Pair> {

  public static class Pair {

    private final int first;
    private final int second;

    public Pair(int first, int second) {
      this.first = first;
      this.second = second;
    }

    @Override
    public String toString() {
      return "[" + first + "|" + second + "]";
    }
  }

  private int last;
  private boolean first = true;

  @Override
  public Pair apply(int value) {
    Pair pair = !first ? new Pair(last, value) : null;
    last = value;
    first = false;
    return pair;
  }

  public static void main(String[] args) {

    IntStream intStream = IntStream.of(0, 1, 2, 3, 4);
    final PairFunction pairFunction = new PairFunction();
    intStream.mapToObj(pairFunction)
        .filter(p -> p != null) // filter out the null
        .forEach(System.out::println); // display each Pair

  }

}

Das Problem dabei ist, dass es Staatenlosigkeit aus dem Fenster wirft.
Rob

@Rob und was ist das Problem damit?
Aleksandr Dubinsky

Einer der Hauptpunkte von Lambda ist, keinen veränderlichen Zustand zu haben, damit die internen Integratoren die Arbeit parallelisieren können.
Rob

@Rob: Ja, Sie haben Recht, aber der angegebene Beispiel-Stream trotzt trotzdem der Parallelität, da jedes Element (außer dem ersten und dem letzten) als erstes und zweites Element eines Paares verwendet wird.
Jpvee

@jpvee Ja, ich dachte, das hast du dir gedacht. Ich frage mich jedoch, ob es keine Möglichkeit gibt, dies mit einem anderen Mapper zu tun. Im Wesentlichen wäre alles, was Sie brauchen würden, das Äquivalent dazu, den Schleifeninkrementierer zu zweit laufen zu lassen, und dann den Funktor zwei Argumente nehmen zu lassen. Das muss möglich sein.
Rob

0

Für aufeinanderfolgende Unterschiede in der Zeit (x-Werte) ein zeitseriellen Berechnung verwende ich die stream‚s - collect(...)Methode:

final List< Long > intervals = timeSeries.data().stream()
                    .map( TimeSeries.Datum::x )
                    .collect( DifferenceCollector::new, DifferenceCollector::accept, DifferenceCollector::combine )
                    .intervals();

Wo der DifferenceCollector so etwas ist:

public class DifferenceCollector implements LongConsumer
{
    private final List< Long > intervals = new ArrayList<>();
    private Long lastTime;

    @Override
    public void accept( final long time )
    {
        if( Objects.isNull( lastTime ) )
        {
            lastTime = time;
        }
        else
        {
            intervals.add( time - lastTime );
            lastTime = time;
        }
    }

    public void combine( final DifferenceCollector other )
    {
        intervals.addAll( other.intervals );
        lastTime = other.lastTime;
    }

    public List< Long > intervals()
    {
        return intervals;
    }
}

Sie könnten dies wahrscheinlich an Ihre Bedürfnisse anpassen.


0

Ich habe endlich einen Weg gefunden, den Stream.reduce auszutricksen, um ordentlich mit Wertepaaren umgehen zu können. Es gibt eine Vielzahl von Anwendungsfällen, für die diese Funktion erforderlich ist, die in JDK 8 nicht selbstverständlich vorkommen :

public static int ArithGeo(int[] arr) {
    //Geometric
    List<Integer> diffList = new ArrayList<>();
    List<Integer> divList = new ArrayList<>();
    Arrays.stream(arr).reduce((left, right) -> {
        diffList.add(right-left);
        divList.add(right/left);
        return right;
    });
    //Arithmetic
    if(diffList.stream().distinct().count() == 1) {
        return 1;
    }
    //Geometric
    if(divList.stream().distinct().count() == 1) {
        return 2;
    }
    return -1;
}

Der Trick, den ich benutze, ist das Rückgaberecht; Aussage.


1
Ich denke, es reducegibt keine ausreichenden Garantien dafür, dass dies funktioniert.
Aleksandr Dubinsky

Würde mich interessieren, mehr über die ausreichenden Garantien zu erfahren . Können Sie bitte näher darauf eingehen? Vielleicht gibt es in Guave eine Alternative ... aber ich bin eingeschränkt und kann sie nicht verwenden.
Beezer

-1

Eine elegante Lösung wäre die Verwendung eines Reißverschlusses . Etwas wie:

List<Integer> input = Arrays.asList(0, 1, 2, 3, 4);
Stream<Pair> pairStream = Streams.zip(input.stream(),
                                      input.stream().substream(1),
                                      (a, b) -> new Pair(a, b)
);

Dies ist ziemlich prägnant und elegant, verwendet jedoch eine Liste als Eingabe. Eine unendliche Stream-Quelle kann auf diese Weise nicht verarbeitet werden.

Ein weiteres (viel problematischeres) Problem ist, dass zip zusammen mit der gesamten Streams-Klasse kürzlich aus der API entfernt wurde. Der obige Code funktioniert nur mit b95 oder älteren Versionen. Mit dem neuesten JDK würde ich also sagen, dass es keine elegante Lösung im FP-Stil gibt, und im Moment können wir nur hoffen, dass zip auf irgendeine Weise wieder in die API eingeführt wird.


In der Tat zipwurde entfernt. Ich erinnere mich nicht an alles, was in der StreamsKlasse war, aber einige Dinge wurden zu statischen Methoden auf der StreamSchnittstelle migriert , und es gibt auch StreamSupportund Stream.BuilderKlassen.
Stuart Marks

Das stimmt. Einige andere Methoden wie concat oder iterate wurden verschoben und wurden zu Standardmethoden in Stream. Leider wurde zip gerade aus der API entfernt. Ich verstehe die Gründe für diese Wahl (z. B. das Fehlen von Tupeln), aber es war trotzdem eine nette Funktion.
Gadget

2
@gadget Was haben Tupel damit zu tun zip? Welcher pedantische Grund auch immer erfunden werden mag, rechtfertigt nicht das Töten zip.
Aleksandr Dubinsky

@AleksandrDubinsky In den meisten Fällen wird zip verwendet, um eine Sammlung von Paaren / Tupeln als Ausgabe zu erstellen. Sie argumentierten, dass die Leute, wenn sie Reißverschlüsse behalten würden, auch im Rahmen des JDK nach Tupeln fragen würden. Ich hätte jedoch niemals eine vorhandene Funktion entfernt.
Gadget

-1

Dies ist ein interessantes Problem. Ist mein Hybridversuch unter irgendetwas gut?

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3);
    Iterator<Integer> first = list.iterator();
    first.next();
    if (first.hasNext())
        list.stream()
        .skip(1)
        .map(v -> new Pair(first.next(), v))
        .forEach(System.out::println);
}

Ich glaube, es eignet sich nicht für die parallele Verarbeitung und kann daher disqualifiziert werden.


Die Frage verlangte keine parallele Verarbeitung, aber es wurde angenommen, dass wir nur eine haben Stream, keine List. Natürlich können wir auch einen Iterator aus einem Stream herausholen, daher ist dies möglicherweise eine gültige Lösung. Trotzdem ist es ein origineller Ansatz.
Aleksandr Dubinsky

-1

Wie andere beobachtet haben, ist aufgrund der Art des Problems eine gewisse Staatlichkeit erforderlich.

Ich war mit einem ähnlichen Problem konfrontiert, bei dem ich im Wesentlichen die Oracle SQL-Funktion LEAD haben wollte. Mein Versuch, das umzusetzen, ist unten.

/**
 * Stream that pairs each element in the stream with the next subsequent element.
 * The final pair will have only the first item, the second will be null.
 */
<T> Spliterator<Pair<T>> lead(final Stream<T> stream)
{
    final Iterator<T> input = stream.sequential().iterator();

    final Iterable<Pair<T>> iterable = () ->
    {
        return new Iterator<Pair<T>>()
        {
            Optional<T> current = getOptionalNext(input);

            @Override
            public boolean hasNext()
            {
                return current.isPresent();
            }

            @Override
            public Pair<T> next()
            {
                Optional<T> next = getOptionalNext(input);
                final Pair<T> pair = next.isPresent()
                    ? new Pair(current.get(), next.get())
                    : new Pair(current.get(), null);
                current = next;

                return pair;
            }
        };
    };

    return iterable.spliterator();
}

private <T> Optional<T> getOptionalNext(final Iterator<T> iterator)
{
    return iterator.hasNext()
        ? Optional.of(iterator.next())
        : Optional.empty();
}

-1

Sie können dies erreichen, indem Sie eine begrenzte Warteschlange verwenden, um Elemente zu speichern, die durch den Stream fließen (was auf der Idee basiert, die ich hier ausführlich beschrieben habe: Ist es möglich, das nächste Element im Stream abzurufen? )

Das folgende Beispiel definiert zuerst die Instanz der BoundedQueue-Klasse, in der Elemente gespeichert werden, die durch den Stream gehen (wenn Sie die Idee zur Erweiterung der LinkedList nicht mögen, lesen Sie den oben genannten Link für einen alternativen und allgemeineren Ansatz). Später kombinieren Sie einfach zwei aufeinanderfolgende Elemente zu einer Instanz von Pair:

public class TwoSubsequentElems {
  public static void main(String[] args) {
    List<Integer> input = new ArrayList<Integer>(asList(0, 1, 2, 3, 4));

    class BoundedQueue<T> extends LinkedList<T> {
      public BoundedQueue<T> save(T curElem) {
        if (size() == 2) { // we need to know only two subsequent elements
          pollLast(); // remove last to keep only requested number of elements
        }

        offerFirst(curElem);

        return this;
      }

      public T getPrevious() {
        return (size() < 2) ? null : getLast();
      }

      public T getCurrent() {
        return (size() == 0) ? null : getFirst();
      }
    }

    BoundedQueue<Integer> streamHistory = new BoundedQueue<Integer>();

    final List<Pair<Integer>> answer = input.stream()
      .map(i -> streamHistory.save(i))
      .filter(e -> e.getPrevious() != null)
      .map(e -> new Pair<Integer>(e.getPrevious(), e.getCurrent()))
      .collect(Collectors.toList());

    answer.forEach(System.out::println);
  }
}

-3

Ich stimme @aepurniet zu, aber stattdessen müssen Sie mapToObj verwenden

range(0, 100).mapToObj((i) -> new Pair(i, i+1)).forEach(System.out::println);

1
Richtig. Dabei werden jedoch lediglich Paare von Ganzzahlen erfasst, nicht Paare von Elementen eines Streams (eines beliebigen Typs).
Aleksandr Dubinsky

-5

Führen Sie eine forSchleife aus, die von 0 bis length-1zu Ihrem Stream läuft

for(int i = 0 ; i < stream.length-1 ; i++)
{
    Pair pair = new Pair(stream[i], stream[i+1]);
    // then add your pair to an array
}

3
und wo ist das Lambda Teil der Lösung?
Olimpiu POP

Es ist nicht der Fall, wenn der Strom unendlich ist
Missgeschick

@Olimpiu - Woher hast du ein Lambda war eine Voraussetzung? Ich habe die Frage zweimal gelesen, um sicherzugehen, dass ich sie nicht verpasst habe. Ich habe auch den Verlauf der Änderungen überprüft. Und die Frage ist nicht damit markiert.
JWW
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.