Raccogli le coppie successive da un flusso


102

Dato un flusso come { 0, 1, 2, 3, 4 } ,

come posso trasformarlo nel modo più elegante in una forma data:

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

(supponendo, ovviamente, di aver definito la classe Pair)?

Modifica: non si tratta strettamente di int o flussi primitivi. La risposta dovrebbe essere generale per un flusso di qualsiasi tipo.


2
Il termine da FP è "partizione", ma non trovo un metodo con la semantica desiderata in Java. Ha il partizionamento su un predicato.
Marko Topolnik

1
Tipicamente lo spliterator in JDK 8 è pensato per scopi di attraversamento e partizionamento. Cercherò di fornire anche un esempio.
Olimpiu POP

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

2
Per la domanda equivalente non stream, vedere stackoverflow.com/questions/17453022/…
Raedwald

A proposito, alcune persone usano entrambe le implementazioni Map.Entrycome una classe Pair. (Certo, alcuni potrebbero considerare che un hack, ma usare una classe incorporata è utile.)
Basil Bourque,

Risposte:


33

La mia libreria StreamEx che estende i flussi standard fornisce un pairMapmetodo per tutti i tipi di flusso. Per i flussi primitivi non cambia il tipo di flusso, ma può essere utilizzato per eseguire alcuni calcoli. L'utilizzo più comune è calcolare le differenze:

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

Per il flusso di oggetti è possibile creare qualsiasi altro tipo di oggetto. La mia libreria non fornisce nuove strutture di dati visibili all'utente come Pair(questa è la parte del concetto di libreria). Tuttavia, se hai la tua Pairclasse e desideri utilizzarla, puoi fare quanto segue:

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

O se ne hai già alcuni Stream:

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

Questa funzionalità viene implementata utilizzando uno spliteratore personalizzato . Ha un overhead piuttosto basso e può parallelizzare bene. Ovviamente funziona con qualsiasi sorgente di flusso, non solo con elenchi / array di accesso casuale come molte altre soluzioni. In molti test funziona davvero bene. Ecco un benchmark JMH in cui troviamo tutti i valori di input che precedono un valore più grande utilizzando approcci diversi (vedi questa domanda).


Grazie! Più studio questa libreria, più la amo. Potrei finalmente iniziare a utilizzare i flussi. ( StreamEximplementa Iterable! Evviva!)
Aleksandr Dubinsky

Per rendere la tua risposta completa al 100%, potresti mostrare come racchiudere un Streamin un StreamEx?
Aleksandr Dubinsky

3
@AleksandrDubinsky: basta usare StreamEx.of(stream). Esistono altri metodi statici convenienti per creare il flusso da Collection, array Reader, ecc. Modifica la risposta.
Tagir Valeev

@TagirValeev è pairMapordinato su stream sequenziali? In realtà, mi piacerebbe avere forPairsOrdered (), ma poiché non esiste un tale metodo, posso simularlo in qualche modo? stream.ordered().forPairs()o stream().pairMap().forEachOrdered()?
Askar Kalykov

1
@AskarKalykov, pairMapè l'operazione intermedia con funzione mapper senza stato non interferente, l'ordinamento non è specificato nello stesso modo di simple map. Il forPairsnon è ordinato dalla specifica, ma le operazioni non ordinate sono ordinate de facto per i flussi sequenziali. Sarebbe bello se formulassi il tuo problema originale come domanda di stackoverflow separata per fornire più contesto.
Tagir Valeev

74

La libreria di flussi Java 8 è principalmente orientata alla suddivisione dei flussi in blocchi più piccoli per l'elaborazione parallela, quindi le fasi della pipeline con stato sono piuttosto limitate e non sono supportate operazioni come ottenere l'indice dell'elemento del flusso corrente e accedere agli elementi del flusso adiacenti.

Un modo tipico per risolvere questi problemi, con alcune limitazioni, ovviamente, è guidare il flusso per indici e fare affidamento sul fatto che i valori vengano elaborati in una struttura di dati ad accesso casuale come un ArrayList da cui è possibile recuperare gli elementi. Se i valori fossero in arrayList, si potrebbero generare le coppie come richiesto facendo qualcosa del genere:

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

Ovviamente la limitazione è che l'input non può essere un flusso infinito. Tuttavia, questa pipeline può essere eseguita in parallelo.


5
"L'input non può essere un flusso infinito." In realtà, l'input non può essere affatto un flusso. Input ( arrayList) è in effetti una raccolta, motivo per cui non l'ho contrassegnata come risposta. (Ma congratulazioni per il tuo distintivo d'oro!)
Aleksandr Dubinsky

16

Non è elegante, è una soluzione hacker, ma funziona per flussi infiniti

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

Ora puoi limitare il tuo streaming alla lunghezza che desideri

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

PS Spero che ci sia una soluzione migliore, qualcosa come clojure(partition 2 1 stream)


6
Complimenti per aver sottolineato che le classi anonime sono un'alternativa a volte utile ai lambda.
Aleksandr Dubinsky

2
@aepurniet presumo che non funzionerà correttamente. Secondo il parallelStreamdocumento: "Per preservare un comportamento corretto, questi parametri comportamentali devono essere non interferenti e nella maggior parte dei casi devono essere apolidi"
mishadoff

14
Ciò è completamente contrario al design del framework dei flussi e viola direttamente il contratto dell'API della mappa, poiché la funzione anonima non è apolide. Prova a eseguirlo con un flusso parallelo e più dati in modo che il framework del flusso crei più thread funzionanti e vedrai il risultato: "errori" casuali rari quasi impossibili da riprodurre e difficili da rilevare finché non hai dati sufficienti (in produzione?). Questo può essere disastroso.
Mario Rossi

4
@AleksandrDubinsky Non sei corretto riguardo al limite / salto parallelizzabile; l'implementazione fornita in JDK funziona in effetti in parallelo. Poiché l'operazione è vincolata all'ordine di incontro, la parallelizzazione potrebbe non fornire sempre un vantaggio in termini di prestazioni, ma in situazioni ad alto Q può.
Brian Goetz

4
@AleksandrDubinsky Errato. Può saltare un elemento casuale se il flusso non è ordinato ( non ha un ordine di incontro definito, quindi logicamente non esiste un "primo" o un "ennesimo" elemento, solo elementi.) Ma se il flusso è ordinato o non ordinato, skip è sempre stato in grado lavorare in parallelo. C'è solo meno parallelismo da estrarre se il flusso è ordinato, ma è ancora parallelo.
Brian Goetz

15

Ho implementato un wrapper spliterator che prende tutti gli nelementi Tdallo spliterator originale e produce 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();
    }
}

Il seguente metodo può essere utilizzato per creare uno stream consecutivo:

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

Utilizzo del campione:

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

Questo ripete ogni elemento due volte?
Aleksandr Dubinsky

No. Crea un nuovo flusso contenente List<E>elementi. Ciascun elenco contiene nelementi consecutivi dal flusso originale. Controlla tu stesso;)
Tomek Rękawek

Potresti modificare la tua risposta in modo che ogni elemento (tranne il primo e l'ultimo) venga ripetuto?
Aleksandr Dubinsky

4
+1 Penso che questo sia un buon lavoro e dovrebbe essere generalizzato a qualsiasi dimensione del passo oltre alla dimensione della partizione. C'è molto bisogno di una (partition size step)funzione e questo è il modo migliore per ottenerlo.
Marko Topolnik

3
Considera l'utilizzo ArrayDequeper le prestazioni, invece di LinkedList.
Marko Topolnik

14

Puoi farlo con il metodo Stream.reduce () (non ho visto altre risposte usando questa tecnica).

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
Restituirebbe (1,2) (2,3) invece di (1,2) (3,4). Inoltre non sono sicuro che venga applicato in ordine (di certo non c'è garanzia).
Aleksandr Dubinsky

1
Si prega di controllare la domanda, questo è il comportamento previsto @Aleksandr Dubinsky
SamTebbs33

3
Ahh, sì, scusa. E pensare, l'ho scritto.
Aleksandr Dubinsky


6

Puoi farlo in cyclops-react (contribuisco a questa libreria), usando l'operatore di scorrimento.

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

O

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

Supponendo che il costruttore della coppia possa accettare una raccolta con 2 elementi.

Se si desidera raggruppare per 4 e incrementare di 2, anche questo è supportato.

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

Metodi statici equivalenti per la creazione di una visualizzazione scorrevole su java.util.stream.Stream sono forniti anche nella classe StreamUtils di cyclops-streams .

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

Nota: - per il funzionamento a thread singolo ReactiveSeq sarebbe più appropriato. LazyFutureStream estende ReactiveSeq ma è principalmente orientato all'uso simultaneo / parallelo (è uno Stream of Futures).

LazyFutureStream estende ReactiveSeq che estende Seq dal fantastico jOOλ (che estende java.util.stream.Stream), quindi le soluzioni presentate da Lukas funzionerebbero anche con entrambi i tipi di Stream. Per chiunque sia interessato, le differenze principali tra gli operatori finestra / scorrevole sono l'ovvio rapporto potenza / complessità relativo e l'idoneità per l'uso con flussi infiniti (lo scorrimento non consuma il flusso, ma buffer mentre scorre).


In questo modo si ottiene [(0,1) (2,3) ...] ma la domanda chiede [(0,1) (1,2) ...]. Si prega di vedere la mia risposta con RxJava ...
frhack

1
Hai ragione, colpa mia, ho letto male la domanda: l'operatore scorrevole è quello corretto da usare qui. Aggiornerò la mia risposta - grazie!
John McClean

4

La libreria proton-pack fornisce la funzionalità a finestra. Data una classe Pair e uno Stream, puoi farlo in questo modo:

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

Ora lo pairsstream contiene:

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

Tuttavia, sembra che tu debba creare stdue volte! Questa libreria può risolvere il problema utilizzando un unico flusso?
Aleksandr Dubinsky

@AleksandrDubinsky Non credo sia disponibile con gli attuali spliterator. Ho inviato un problema github.com/poetix/protonpack/issues/9
Alexis C.

@AleksandrDubinsky La windowedfunzionalità è stata aggiunta! Vedi la modifica.
Alexis C.

1
Perché non elimini la tua vecchia risposta, in modo che gli altri utenti possano vedere la soluzione, non la cronologia.
Aleksandr Dubinsky

4

Trovare coppie successive

Se sei disposto a utilizzare una libreria di terze parti e non hai bisogno del parallelismo, jOOλ offre le funzioni della finestra in stile SQL come segue

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

cedimento

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

Il lead() funzione accede al valore successivo in ordine trasversale dalla finestra.

Trovare successive triple / quadruple / n-tuple

Una domanda nei commenti chiedeva una soluzione più generale, in cui non dovrebbero essere raccolte coppie ma n-tuple (o possibilmente elenchi). Ecco quindi un approccio alternativo:

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

Fornire un elenco di elenchi

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

Senza il filter(w -> w.count() == n), il risultato sarebbe

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

Disclaimer: lavoro per l'azienda dietro jOOλ


Interessante. Cosa succede se devo raggruppare 3 o più elementi? Utilizzare w.lead().lead()?
Raul Santelices

1
@RaulSantelices: tuple(w.value(), w.lead(1), w.lead(2))sarebbe un'opzione. Ho aggiornato la mia risposta con una soluzione più generica perlength = n
Lukas Eder

1
Capisco correttamente che .window()non è un'operazione pigra che raccoglie l'intero flusso di input in una raccolta intermedia, quindi crea un nuovo flusso da esso?
Tagir Valeev

@TagirValeev: Sì, questa è l'implementazione corrente. Nel caso precedente (no Comparatorviene utilizzato per riordinare le finestre), un'ottimizzazione come questa sarebbe possibile e probabilmente verrà implementata in futuro.
Lukas Eder


2

Possiamo usare RxJava ( libreria di estensioni reattive molto potente )

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

L' operatore buffer trasforma un Observable che emette elementi in un Observable che emette raccolte bufferizzate di quegli elementi.


1
Ho usato Observable.zip(obs, obs.skip(1), pair->{...})fino ad ora! Non sapevo Observable.bufferavesse una versione con un passaggio (e sono abituato al ziptrucco di Python). +1
Reut Sharabani

1

L'operazione è essenzialmente stateful, quindi non proprio ciò che i flussi dovrebbero risolvere - vedere la sezione "Stateless Behaviors" nel javadoc :

L'approccio migliore è evitare completamente i parametri comportamentali stateful per eseguire lo streaming delle operazioni

Una soluzione qui è introdurre lo stato nel flusso tramite un contatore esterno, sebbene funzionerà solo con un flusso sequenziale.

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 + '}'; }
}

La domanda chiede di raccogliere elementi successivi di un flusso di input, non semplicemente di raccogliere interi successivi. Un importante chiarimento della terminologia Stream:! = "Lambdas".
Aleksandr Dubinsky

È possibile sostituire AtomicInteger con AtomicReference. L'alternativa è quella di rotolare il proprio collettore o utilizzare librerie esterne, come in questo esempio: stackoverflow.com/a/30090528/829571
assylias

Vedi la mia modifica. Inoltre non sono sicuro di aver capito il tuo commento su lambda! = Stream. L'altra risposta che utilizza una classe anonima fa essenzialmente la stessa cosa tranne che lo stato è detenuto dalla classe anonima invece di essere esterno ...
assylias

1
Che funzioni. La StreamExbiblioteca è anche una buona scoperta e potrebbe essere una risposta in sé. Il mio commento su "flussi! = Lambda" si riferisce a te che affermi "L'operazione è essenzialmente con stato, quindi non proprio ciò che i lambda dovrebbero risolvere". Penso che volessi usare la parola "flussi".
Aleksandr Dubinsky

Oh capisco - l'ho chiarito.
assylias

0

Nel tuo caso, scriverei la mia IntFunction personalizzata che tiene traccia dell'ultimo int passato e lo userei per mappare l'IntStream originale.

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

  }

}

Il problema è che getta l'apolidia fuori dalla finestra.
Rob

@ Rob e qual è il problema con quello?
Aleksandr Dubinsky

Uno dei punti principali di lambda è di non avere uno stato mutabile in modo che gli integratori interni possano parallelizzare il lavoro.
Rob il

@ Rob: Sì, hai ragione, ma il flusso di esempio fornito sfida il parallelismo comunque poiché ogni elemento (eccetto il primo e l'ultimo) viene utilizzato come primo e secondo elemento di una coppia.
jpvee

@ jpvee yeah ho pensato che fosse quello che stavi pensando. Mi chiedo però se non ci sia un modo per farlo con qualche altro mappatore. In sostanza, tutto ciò di cui hai bisogno sarebbe l'equivalente di fare in modo che l'incrementatore di ciclo vada a due a due e che il funtore prenda 2 argomenti. Deve essere possibile.
Rob il

0

Per calcolare le differenze successive nel tempo (valori x) di una serie temporale, utilizzo il metodo streams collect(...):

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

Dove DifferenceCollector è qualcosa del genere:

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

Probabilmente potresti modificarlo in base alle tue esigenze.


0

Alla fine ho trovato un modo per ingannare Stream.reduce per essere in grado di gestire ordinatamente coppie di valori; ci sono una moltitudine di casi d'uso che richiedono questa funzionalità che non appare naturalmente in JDK 8:

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

Il trucco che uso è il diritto di ritorno; dichiarazione.


1
Non credo reduceche offra garanzie sufficienti perché funzioni.
Aleksandr Dubinsky

Sarei interessato a saperne di più sulle garanzie sufficienti . Puoi approfondire per favore? Forse c'è un'alternativa in Guava ... ma sono vincolato e non posso usarlo.
Beezer

-1

Una soluzione elegante sarebbe usare zip . Qualcosa di simile a:

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

Questo è abbastanza conciso ed elegante, tuttavia utilizza un elenco come input. Una sorgente di flusso infinita non può essere elaborata in questo modo.

Un altro problema (molto più problematico) è che lo zip insieme all'intera classe Streams è stato recentemente rimosso dall'API. Il codice sopra funziona solo con b95 o versioni precedenti. Quindi con l'ultimo JDK direi che non esiste un'elegante soluzione in stile FP e in questo momento possiamo solo sperare che in qualche modo zip venga reintrodotto nell'API.


Anzi, è zipstato rimosso. Non ricordo tutto quello che c'era sulla Streamsclasse, ma alcune cose sono migrate per diventare metodi statici Streamsull'interfaccia e ci sono anche StreamSupporte Stream.Builderclassi.
Stuart Marks

Giusto. Alcuni altri metodi come concat o iterate sono stati spostati e sono diventati metodi predefiniti in Stream. Purtroppo zip è stato appena rimosso dall'API. Capisco le ragioni alla base di questa scelta (ad es. Mancanza di tuple) ma comunque era una bella caratteristica.
gadget

2
@gadget Con cosa hanno a che fare le tuple zip? Qualunque sia la ragione pedante potrebbe essere inventata non giustifica l'uccisione zip.
Aleksandr Dubinsky

@AleksandrDubinsky Nella maggior parte dei casi zip viene utilizzato per produrre una raccolta di coppie / tuple come output. Hanno sostenuto che se avessero mantenuto zip le persone avrebbero chiesto anche Tuple come parte del JDK. Tuttavia, non avrei mai rimosso una funzionalità esistente.
gadget

-1

Questo è un problema interessante. Il mio tentativo ibrido è buono?

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

Credo che non si presti all'elaborazione parallela e quindi possa essere squalificato.


La domanda non richiedeva l'elaborazione parallela, ma presupponeva che avessimo solo Streamun file List. Naturalmente, possiamo anche estrarre un iteratore da uno stream, quindi questa potrebbe essere una soluzione valida. Tuttavia, è un approccio originale.
Aleksandr Dubinsky

-1

Come altri hanno osservato, a causa della natura del problema è richiesta una certa staticità.

Mi sono trovato di fronte a un problema simile, in cui volevo quella che era essenzialmente la funzione Oracle SQL LEAD. Il mio tentativo di implementarlo è di seguito.

/**
 * 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

È possibile ottenere ciò utilizzando una coda delimitata per memorizzare gli elementi che fluiscono attraverso il flusso (che si basa sull'idea che ho descritto in dettaglio qui: è possibile ottenere l'elemento successivo nello stream? )

L'esempio seguente definisce prima l'istanza della classe BoundedQueue che memorizzerà gli elementi che passano attraverso il flusso (se non ti piace l'idea di estendere la LinkedList, fai riferimento al collegamento menzionato sopra per un approccio alternativo e più generico). Successivamente combini solo due elementi successivi nell'istanza di 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

Sono d'accordo con @aepurniet ma invece map devi usare mapToObj

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

1
Destra. Ma questo raccoglie semplicemente coppie di numeri interi, non coppie di elementi di un flusso (di qualsiasi tipo).
Aleksandr Dubinsky

-5

Esegui un forciclo che va da 0 a length-1del tuo flusso

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
e dov'è la parte lambda della soluzione?
Olimpiu POP

Non è il caso quando lo streaming è infinito
mishadoff

@Olimpiu - Dove hai preso un lambda era un requisito? Ho letto la domanda due volte per assicurarmi che non mi mancasse. Ho anche controllato la cronologia delle modifiche. E la domanda non è etichettata con esso.
jww
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.