Recopilar pares sucesivos de una secuencia

102

Dada una corriente como { 0, 1, 2, 3, 4 },

¿Cómo puedo transformarlo de la manera más elegante en una forma dada?

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

(asumiendo, por supuesto, que he definido la clase Par)?

Editar: No se trata estrictamente de ints o streams primitivos. La respuesta debe ser general para una secuencia de cualquier tipo.

Aleksandr Dubinsky
fuente
2
El término de FP es "partición", pero no encuentro un método con la semántica deseada en Java. Tiene particiones en un predicado.
Marko Topolnik
1
Normalmente, el spliterator en JDK 8 está pensado para propósitos de atravesar y particionar. Intentaré dar un ejemplo también.
Olimpiu POP
list.stream().map(i -> new Pair(i, i+1));
aepurniet
2
Para la pregunta equivalente de no transmisiones, consulte stackoverflow.com/questions/17453022/…
Raedwald
Por cierto, algunas personas usan la implementación de Map.Entryuna clase de par. (De acuerdo, algunos podrían considerar que es un truco, pero usar una clase incorporada es útil).
Basil Bourque

Respuestas:

33

Mi biblioteca StreamEx , que amplía los flujos estándar, proporciona un pairMapmétodo para todos los tipos de flujos. En el caso de los flujos primitivos, no cambia el tipo de flujo, pero se puede utilizar para realizar algunos cálculos. El uso más común es calcular diferencias:

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

Para el flujo de objetos, puede crear cualquier otro tipo de objeto. Mi biblioteca no proporciona nuevas estructuras de datos visibles para el usuario como Pair(esa es la parte del concepto de biblioteca). Sin embargo, si tiene su propia Pairclase y desea usarla, puede hacer lo siguiente:

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

O si ya tienes alguno Stream:

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

Esta funcionalidad se implementa mediante un spliterator personalizado . Tiene una sobrecarga bastante baja y se puede paralelizar muy bien. Por supuesto, funciona con cualquier fuente de flujo, no solo con listas / matrices de acceso aleatorio como muchas otras soluciones. En muchas pruebas funciona realmente bien. Aquí hay un punto de referencia de JMH donde encontramos todos los valores de entrada que preceden a un valor mayor utilizando diferentes enfoques (consulte esta pregunta).

Tagir Valeev
fuente
¡Gracias! Cuanto más estudio esta biblioteca, más la amo. Finalmente podría comenzar a usar streams. ( StreamEximplementos Iterable! ¡Hurra!)
Aleksandr Dubinsky
Para completar su respuesta al 100%, ¿podría mostrar cómo envolver a Streamen a StreamEx?
Aleksandr Dubinsky
3
@AleksandrDubinsky: solo usa StreamEx.of(stream). Hay otros métodos estáticos convenientes para crear el flujo Collection, matriz Reader, etc. Editó la respuesta.
Tagir Valeev
@TagirValeev está pairMapordenado en secuencias secuenciales? En realidad, me gustaría tener forPairsOrdered (), pero como no existe tal método, ¿puedo simularlo de alguna manera? stream.ordered().forPairs()o stream().pairMap().forEachOrdered()?
Askar Kalykov
1
@AskarKalykov, pairMapes la operación intermedia con función de mapeador sin estado no interferente, el orden no se especifica para ella de la misma manera que para simple map. El forPairsno está ordenado por especificación, pero las operaciones no ordenadas están ordenadas de facto para flujos secuenciales. Sería bueno si formulara su problema original como una pregunta separada de stackoverflow para proporcionar más contexto.
Tagir Valeev
74

La biblioteca de flujos de Java 8 está orientada principalmente a dividir flujos en trozos más pequeños para el procesamiento en paralelo, por lo que las etapas de canalización con estado son bastante limitadas y no se admiten acciones como obtener el índice del elemento de flujo actual y acceder a elementos de flujo adyacentes.

Una forma típica de resolver estos problemas, con algunas limitaciones, por supuesto, es conducir la secuencia por índices y confiar en que los valores se procesen en alguna estructura de datos de acceso aleatorio como una ArrayList de la que se pueden recuperar los elementos. Si los valores estuvieran dentro arrayList, se podrían generar los pares solicitados haciendo algo como esto:

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

Por supuesto, la limitación es que la entrada no puede ser un flujo infinito. Sin embargo, esta canalización se puede ejecutar en paralelo.

Stuart Marks
fuente
5
"La entrada no puede ser un flujo infinito". En realidad, la entrada no puede ser una secuencia en absoluto. La entrada ( arrayList) es de hecho una colección, por lo que no la marqué como respuesta. (¡Pero felicidades por tu insignia de oro!)
Aleksandr Dubinsky
16

Esto no es elegante, es una solución pirata, pero funciona para flujos infinitos

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

Ahora puede limitar su transmisión a la duración que desee

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

PD : espero que haya una mejor solución, algo como clojure(partition 2 1 stream)

mishadoff
fuente
6
Felicitaciones por señalar que las clases anónimas son una alternativa a veces útil a las lambdas.
Aleksandr Dubinsky
2
@aepurniet Supongo que no funcionará correctamente. Según el parallelStreamdocumento: "Para preservar el comportamiento correcto, estos parámetros de comportamiento no deben interferir y, en la mayoría de los casos, deben ser apátridas"
mishadoff
14
Esto es completamente contrario al diseño del marco de transmisiones y viola directamente el contrato de la API de mapas, ya que la función anónima no es apátrida. Intente ejecutar esto con un flujo paralelo y más datos para que el marco de flujo cree más hilos de trabajo y verá el resultado: "errores" aleatorios poco frecuentes, casi imposibles de reproducir y difíciles de detectar hasta que tenga suficientes datos (¿en producción?). Esto puede resultar desastroso.
Mario Rossi
4
@AleksandrDubinsky No es correcto que el límite / salto sea paralelizable; la implementación proporcionada en JDK de hecho funciona en paralelo. Debido a que la operación está ligada al orden de encuentro, es posible que la paralelización no siempre proporcione un beneficio de rendimiento, pero en situaciones de alta Q, puede hacerlo.
Brian Goetz
4
@AleksandrDubinsky Incorrecto. Se puede omitir un elemento de azar si la corriente es desordenada (orden de encuentro no se ha definido, por lo que lógicamente no es ninguna "primera" o "enésimo" elemento, elementos sólo.) Pero si el flujo es ordenada o desordenada, salte siempre ha sido capaz trabajar en paralelo. Hay menos paralelismo que extraer si la secuencia está ordenada, pero sigue siendo paralela.
Brian Goetz
15

Implementé un contenedor de spliterator que toma todos los nelementos Tdel spliterator original y 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();
    }
}

Se puede utilizar el siguiente método para crear una secuencia consecutiva:

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

Uso de muestra:

consecutiveStream(Stream.of(0, 1, 2, 3, 4, 5), 2)
    .map(list -> new Pair(list.get(0), list.get(1)))
    .forEach(System.out::println);
Tomek Rękawek
fuente
¿Eso repite cada elemento dos veces?
Aleksandr Dubinsky
No Crea una nueva secuencia que contiene List<E>elementos. Cada lista contiene nelementos consecutivos del flujo original. Compruébelo usted mismo;)
Tomek Rękawek
¿Podría modificar su respuesta para que todos los elementos (excepto el primero y el último) se repitan?
Aleksandr Dubinsky
4
+1 Creo que este es un buen trabajo y debería generalizarse a cualquier tamaño de paso además del tamaño de la partición. Hay mucha necesidad de una (partition size step)función y esta es la mejor manera de conseguirla.
Marko Topolnik
3
Considere usar ArrayDequepara el rendimiento, en lugar de LinkedList.
Marko Topolnik
14

Puede hacer esto con el método Stream.reduce () (no he visto ninguna otra respuesta usando esta técnica).

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;
}
SamTebbs33
fuente
1
Devolvería (1,2) (2,3) en lugar de (1,2) (3,4). Además, no estoy seguro de si se aplicaría en orden (ciertamente no hay garantía de eso).
Aleksandr Dubinsky
1
Por favor marque la pregunta, ese es el comportamiento previsto @Aleksandr Dubinsky
SamTebbs33
3
Ahh, sí, lo siento. Y pensar, lo escribí.
Aleksandr Dubinsky
6

Puede hacer esto en cyclops-react (contribuyo a esta biblioteca), usando el operador deslizante.

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

Suponiendo que el constructor de pares puede aceptar una colección con 2 elementos.

Si desea agrupar por 4 e incrementar por 2 eso también es compatible.

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

Los métodos estáticos equivalentes para crear una vista deslizante sobre java.util.stream.Stream también se proporcionan en la clase cyclops-streams StreamUtils .

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

Nota: - para la operación de un solo subproceso, ReactiveSeq sería más apropiado. LazyFutureStream extiende ReactiveSeq pero está principalmente orientado al uso concurrente / paralelo (es un Stream of Futures).

LazyFutureStream extiende ReactiveSeq, que extiende Seq desde el impresionante jOOλ (que extiende java.util.stream.Stream), por lo que las soluciones que presenta Lukas también funcionarían con cualquier tipo de Stream. Para cualquier persona interesada, las principales diferencias entre los operadores de ventana / deslizamiento son el obvio equilibrio relativo de potencia / complejidad y la idoneidad para su uso con flujos infinitos (el deslizamiento no consume el flujo, sino amortiguadores a medida que fluye).

John McClean
fuente
De esta forma se obtiene [(0,1) (2,3) ...] pero la pregunta pregunta [(0,1) (1,2) ...]. Por favor, vea mi respuesta con RxJava ...
frhack
1
Tiene razón, mal, leí mal la pregunta: el operador deslizante es el correcto para usar aquí. Actualizaré mi respuesta, ¡gracias!
John McClean
4

La biblioteca de paquetes de protones proporciona la funcionalidad de ventana. Dada una clase de par y una secuencia, puede hacerlo así:

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

Ahora la pairssecuencia contiene:

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, ...) and so on
Alexis C.
fuente
Sin embargo, parece que necesitas crear stdos veces. ¿Puede esta biblioteca resolver el problema usando una sola secuencia?
Aleksandr Dubinsky
@AleksandrDubinsky No creo que esté disponible con los spliterators actuales. Envié
Alexis C.
@AleksandrDubinsky ¡Se windowedha agregado la funcionalidad! Ver la edición.
Alexis C.
1
¿Por qué no borra su respuesta anterior, para que otros usuarios puedan ver la solución, no el historial?
Aleksandr Dubinsky
4

Encontrar parejas sucesivas

Si está dispuesto a utilizar una biblioteca de terceros y no necesita paralelismo, entonces jOOλ ofrece funciones de ventana de estilo SQL de la siguiente manera

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

Flexible

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

La lead()función accede al siguiente valor en orden transversal desde la ventana.

Encontrar sucesivos triples / cuádruples / n-tuplas

Una pregunta en los comentarios pedía una solución más general, donde no se deberían recopilar pares sino n-tuplas (o posiblemente listas). Por tanto, aquí hay un enfoque 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()
);

Produciendo una lista de listas

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

Sin el filter(w -> w.count() == n), el resultado sería

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

Descargo de responsabilidad: trabajo para la empresa detrás de jOOλ

Lukas Eder
fuente
Interesante. ¿Qué pasa si necesito agrupar 3 o más elementos? Uso w.lead().lead()?
Raul Santelices
1
@RaulSantelices: tuple(w.value(), w.lead(1), w.lead(2))sería una opción. Actualicé mi respuesta con una solución más genérica paralength = n
Lukas Eder
1
Entiendo correctamente que .window()no es una operación perezosa que recopila todo el flujo de entrada en una colección intermedia y luego crea un nuevo flujo a partir de él.
Tagir Valeev
@TagirValeev: Sí, esa es la implementación actual. En el caso anterior (no Comparatorse usa para reordenar las ventanas), entonces sería posible una optimización como esta , y es probable que se implemente en el futuro.
Lukas Eder
2

Podemos usar RxJava ( biblioteca de extensión reactiva muy poderosa )

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

El operador de búfer transforma un Observable que emite elementos en un Observable que emite colecciones almacenadas en búfer de esos elementos.

frhack
fuente
1
¡Lo usé Observable.zip(obs, obs.skip(1), pair->{...})hasta ahora! No sabía que Observable.buffertenía una versión con un paso (y estoy acostumbrado al ziptruco de Python). +1
Reut Sharabani
1

La operación es esencialmente con estado, por lo que no es realmente lo que los flujos deben resolver; consulte la sección "Comportamientos sin estado" en el javadoc :

El mejor enfoque es evitar los parámetros de comportamiento con estado para transmitir las operaciones por completo

Una solución aquí es introducir el estado en su flujo a través de un contador externo, aunque solo funcionará con un flujo secuencial.

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 + '}'; }
}
Assylias
fuente
La pregunta pide recopilar elementos sucesivos de un flujo de entrada, no simplemente recopilar números enteros sucesivos. Una aclaración importante de la terminología Stream:! = "Lambdas".
Aleksandr Dubinsky
Podría reemplazar AtomicInteger por AtomicReference. La alternativa es rodar su propio colector o usar bibliotecas externas como en este ejemplo: stackoverflow.com/a/30090528/829571
assylias
Ver mi edición. Además, no estoy seguro de entender su comentario sobre lambda! = Stream. La otra respuesta que usa una clase anónima hace esencialmente lo mismo, excepto que el estado está en manos de la clase anónima en lugar de ser externo ...
assylias
1
Eso funciona. La StreamExbiblioteca también es un buen hallazgo y podría ser una respuesta en sí misma. Mi comentario sobre "streams! = Lambdas" se refiere a que usted dice "La operación es esencialmente con estado, así que no es realmente lo que las lambdas deben resolver". Creo que querías usar la palabra "arroyos".
Aleksandr Dubinsky
Oh, ya veo, lo he aclarado.
assylias
0

En su caso, escribiría mi IntFunction personalizada que realiza un seguimiento del último int pasado y lo usaría para mapear el IntStream original.

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

  }

}
jpvee
fuente
El problema con esto es que arroja la apatridia por la ventana.
Rob
@Rob y ¿cuál es el problema con eso?
Aleksandr Dubinsky
Uno de los puntos principales de lambda es no tener un estado mutable para que los integradores internos puedan paralelismo del trabajo.
Rob
@Rob: Sí, tiene razón, pero la secuencia de ejemplo dada desafía el paralelismo de todos modos, ya que cada elemento (excepto el primero y el último) se usa como primer y segundo elemento de algún par.
jpvee
@jpvee sí, pensé que eso es lo que estabas pensando. Sin embargo, me pregunto si no hay una manera de hacer esto con algún otro mapeador. En esencia, todo lo que necesitaría sería el equivalente a hacer que el incrementador de bucle vaya de dos en dos y luego haga que el functor tome 2 argumentos. Eso debe ser posible.
Rob
0

Para el cálculo de las diferencias sucesivas en el tiempo (x-valores) de una serie de tiempo, utilizo el stream's collect(...)método:

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

Donde el DifferenceCollector es algo como esto:

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

Probablemente podría modificar esto para adaptarlo a sus necesidades.

Rob Philipp
fuente
0

Finalmente descubrí una forma de engañar a Stream.reduce para poder manejar cuidadosamente los pares de valores; Hay una multitud de casos de uso que requieren esta función que no aparece de forma natural en 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;
}

El truco que utilizo es el derecho de devolución; declaración.

Beezer
fuente
1
No creo que reduceofrezca suficientes garantías para que esto funcione.
Aleksandr Dubinsky
Estaría interesado en saber más sobre las garantías suficientes . ¿Podrías dar más detalles? Quizás haya una alternativa en Guayaba ... pero estoy limitado y no puedo usarlo.
Beezer
-1

Una solución elegante sería utilizar zip . Algo como:

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

Esto es bastante conciso y elegante, sin embargo, utiliza una lista como entrada. Una fuente de flujo infinito no se puede procesar de esta manera.

Otro problema (mucho más problemático) es que el zip junto con toda la clase Streams se ha eliminado recientemente de la API. El código anterior solo funciona con versiones b95 o anteriores. Entonces, con el último JDK, diría que no hay una solución elegante de estilo FP y ahora mismo podemos esperar que de alguna manera se reintroduzca zip en la API.

artilugio
fuente
De hecho, zipfue eliminado. No recuerdo todo lo que había en la Streamsclase, pero algunas cosas se han migrado para ser métodos estáticos en la Streaminterfaz, y también hay clases StreamSupporty Stream.Builder.
Stuart Marks
Así es. Algunos otros métodos como concat o iterate se han movido y se han convertido en métodos predeterminados en Stream. Lamentablemente, zip se acaba de eliminar de la API. Entiendo las razones detrás de esta elección (por ejemplo, la falta de tuplas) pero aún así fue una buena característica.
gadget
2
@gadget ¿Qué tienen que ver las tuplas zip? Cualquiera sea la razón pedante que se pueda inventar, no justifica matar zip.
Aleksandr Dubinsky
@AleksandrDubinsky En la mayoría de los casos, zip se usa para producir una colección de pares / tuplas como salida. Ellos argumentaron que si se mantienen zip gente se pregunte por tuplas como parte del JDK también. Sin embargo, nunca habría eliminado una función existente.
gadget
-1

Este es un problema interesante. ¿Es bueno mi intento de híbrido a continuación?

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

Creo que no se presta al procesamiento en paralelo y, por lo tanto, puede ser descalificado.

Kedar Mhaswade
fuente
La pregunta no solicitó procesamiento paralelo, pero asumió que solo tenemos un Stream, no un List. Por supuesto, también podemos sacar un iterador de una secuencia, por lo que esta podría ser una solución válida. Sin embargo, es un enfoque original.
Aleksandr Dubinsky
-1

Como han observado otros, debido a la naturaleza del problema, se requiere cierta condición de estado.

Me enfrenté a un problema similar, en el que quería lo que era esencialmente la función LEAD de Oracle SQL. Mi intento de implementar eso está a continuación.

/**
 * 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();
}
Hoons
fuente
-1

Puede lograrlo utilizando una cola limitada para almacenar elementos que fluyen a través de la secuencia (que se basa en la idea que describí en detalle aquí: ¿Es posible obtener el siguiente elemento en la secuencia? )

El siguiente ejemplo define primero la instancia de la clase BoundedQueue que almacenará los elementos que pasan por la secuencia (si no le gusta la idea de extender la LinkedList, consulte el enlace mencionado anteriormente para obtener un enfoque alternativo y más genérico). Más tarde, simplemente combine dos elementos posteriores en una instancia de 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);
  }
}
walkeros
fuente
-3

Estoy de acuerdo con @aepurniet pero en su lugar map tienes que usar mapToObj

range(0, 100).mapToObj((i) -> new Pair(i, i+1)).forEach(System.out::println);
user190364
fuente
1
Correcto. Pero esto simplemente recopila pares de números enteros, no pares de elementos de una secuencia (de cualquier tipo).
Aleksandr Dubinsky
-5

Ejecute un forbucle que va desde 0 hasta length-1de su flujo

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
}
Savv
fuente
3
y ¿dónde está la parte lambda de la solución?
Olimpiu POP
No es el caso cuando la transmisión es infinita
mishadoff
@Olimpiu - ¿De dónde sacaste una lambda? ¿Era un requisito? Leí la pregunta dos veces para asegurarme de que no me la estaba perdiendo. También verifiqué el historial de ediciones. Y la pregunta no está etiquetada con eso.
jww