Recoge pares sucesivos de una secuencia
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) }
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.
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 Pair
clase 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).
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;
}
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)
Implementé un contenedor spliterator que toma todos n
los elementos T
del 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);