Recoge pares sucesivos de una secuencia

Resuelto Aleksandr Dubinsky asked hace 10 años • 23 respuestas

Dado un objeto o flujo primitivo como { 0, 1, 2, 3, 4 }, ¿cómo puedo transformarlo de manera más elegante en una forma determinada (suponiendo, por supuesto, que haya definido la clase Pair)?

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

Aleksandr Dubinsky avatar Dec 09 '13 18:12 Aleksandr Dubinsky
Aceptado

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

Una forma típica de resolver estos problemas, con algunas limitaciones, por supuesto, es controlar la secuencia por índices y confiar en que los valores se procesen en alguna estructura de datos de acceso aleatorio como una ArrayList desde la cual se pueden recuperar los elementos. Si los valores estuvieran en arrayList, se podrían generar los pares según lo solicitado 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, este canal se puede ejecutar en paralelo.

Stuart Marks avatar Dec 11 '2013 00:12 Stuart Marks

Mi biblioteca StreamExpairMap , que amplía las transmisiones estándar, proporciona un método para todos los tipos de transmisiones. Para 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 ninguna estructura de datos nueva visible para el usuario Pair(esa es parte del concepto de biblioteca). Sin embargo, si tienes tu propia Pairclase y quieres usarla, puedes hacer lo siguiente:

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

O si ya tienes algunos Stream:

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

Esta funcionalidad se implementa mediante spliterator personalizado . Tiene una sobrecarga bastante baja y puede paralelizarse muy bien. Por supuesto, funciona con cualquier fuente de transmisión, no solo con una lista/matriz de acceso aleatorio como muchas otras soluciones. En muchas pruebas funciona muy 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 avatar May 30 '2015 03:05 Tagir Valeev

Puedes 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 avatar Mar 22 '2017 17:03 SamTebbs33

Esto no es elegante, es una solución pirateada, pero funciona para transmisiones infinitas.

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 puedes limitar tu transmisión a la duración que desees

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 avatar Dec 09 '2013 15:12 mishadoff

Implementé un contenedor spliterator que toma todos nlos elementos 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 avatar Dec 09 '2013 18:12 Tomek Rękawek