titledborder - Orden inversa de la secuencia Java 8
titledborder java (20)
¿Qué tal este método de utilidad?
public static <T> Stream<T> getReverseStream(List<T> list) {
final ListIterator<T> listIt = list.listIterator(list.size());
final Iterator<T> reverseIterator = new Iterator<T>() {
@Override
public boolean hasNext() {
return listIt.hasPrevious();
}
@Override
public T next() {
return listIt.previous();
}
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
reverseIterator,
Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
}
Parece que funciona con todos los casos sin duplicación.
Pregunta general: ¿Cuál es la forma correcta de invertir una secuencia? Suponiendo que no sabemos en qué tipo de elementos consiste la transmisión, ¿cuál es la forma genérica de revertir una transmisión?
Pregunta específica
IntStream
proporciona un método de rango para generar enteros en el rango específico IntStream.range(-range, 0)
, ahora que quiero invertir el rango de conmutación de 0 a negativo no funciona, tampoco puedo usar Integer::compare
List<Integer> list = Arrays.asList(1,2,3,4);
list.stream().sorted(Integer::compare).forEach(System.out::println);
con IntStream
obtendré este error de compilación
Error: (191, 0) ajc: el método
sorted()
en el tipoIntStream
no es aplicable para los argumentos (Integer::compare
)
¿Qué me estoy perdiendo allí?
Así es como lo hago.
No me gusta la idea de crear una nueva colección e invertirla iterando.
La idea del mapa IntStream # es bastante clara, pero prefiero el método IntStream # iterate, porque creo que la idea de una cuenta atrás para Zero se expresa mejor con el método de iteración y es más fácil de entender en términos de recorrer la matriz de atrás hacia adelante.
import static java.lang.Math.max;
private static final double EXACT_MATCH = 0d;
public static IntStream reverseStream(final int[] array) {
return countdownFrom(array.length - 1).map(index -> array[index]);
}
public static DoubleStream reverseStream(final double[] array) {
return countdownFrom(array.length - 1).mapToDouble(index -> array[index]);
}
public static <T> Stream<T> reverseStream(final T[] array) {
return countdownFrom(array.length - 1).mapToObj(index -> array[index]);
}
public static IntStream countdownFrom(final int top) {
return IntStream.iterate(top, t -> t - 1).limit(max(0, (long) top + 1));
}
Aquí hay algunas pruebas para demostrar que funciona:
import static java.lang.Integer.MAX_VALUE;
import static org.junit.Assert.*;
@Test
public void testReverseStream_emptyArrayCreatesEmptyStream() {
Assert.assertEquals(0, reverseStream(new double[0]).count());
}
@Test
public void testReverseStream_singleElementCreatesSingleElementStream() {
Assert.assertEquals(1, reverseStream(new double[1]).count());
final double[] singleElementArray = new double[] { 123.4 };
assertArrayEquals(singleElementArray, reverseStream(singleElementArray).toArray(), EXACT_MATCH);
}
@Test
public void testReverseStream_multipleElementsAreStreamedInReversedOrder() {
final double[] arr = new double[] { 1d, 2d, 3d };
final double[] revArr = new double[] { 3d, 2d, 1d };
Assert.assertEquals(arr.length, reverseStream(arr).count());
Assert.assertArrayEquals(revArr, reverseStream(arr).toArray(), EXACT_MATCH);
}
@Test
public void testCountdownFrom_returnsAllElementsFromTopToZeroInReverseOrder() {
assertArrayEquals(new int[] { 4, 3, 2, 1, 0 }, countdownFrom(4).toArray());
}
@Test
public void testCountdownFrom_countingDownStartingWithZeroOutputsTheNumberZero() {
assertArrayEquals(new int[] { 0 }, countdownFrom(0).toArray());
}
@Test
public void testCountdownFrom_doesNotChokeOnIntegerMaxValue() {
assertEquals(true, countdownFrom(MAX_VALUE).anyMatch(x -> x == MAX_VALUE));
}
@Test
public void testCountdownFrom_givesZeroLengthCountForNegativeValues() {
assertArrayEquals(new int[0], countdownFrom(-1).toArray());
assertArrayEquals(new int[0], countdownFrom(-4).toArray());
}
Como referencia, estaba viendo el mismo problema, quería unir el valor de cadena de los elementos de la secuencia en el orden inverso.
itemList = {last, middle, first} => first, middle, last
Empecé a utilizar una colección intermedia con collectingAndThen
de comonad o el recopilador ArrayDeque
de Stuart Marks , aunque no estaba contento con la colección intermedia y la transmisión de nuevo
itemList.stream()
.map(TheObject::toString)
.collect(Collectors.collectingAndThen(Collectors.toList(),
strings -> {
Collections.reverse(strings);
return strings;
}))
.stream()
.collect(Collector.joining());
Así que repetí sobre la respuesta de Stuart Marks que estaba usando el Collector.of
factory, que tiene el interesante terminador lambda.
itemList.stream()
.collect(Collector.of(StringBuilder::new,
(sb, o) -> sb.insert(0, o),
(r1, r2) -> { r1.insert(0, r2); return r1; },
StringBuilder::toString));
Dado que en este caso la transmisión no es paralela, el combinador no es tan relevante, estoy usando insert
todos modos en aras de la coherencia del código, pero no importa ya que dependería de qué constructor de cadenas se construya primero.
Miré a StringJoiner, sin embargo, no tiene un método de insert
.
Con respecto a la pregunta específica de generar un IntStream
invertido, Java 9 introdujo un IntStream.iterate(...)
alternativo IntStream.iterate(...)
, que podría usarse fácilmente para iterar en orden inverso:
IntStream.iterate(10, x -> x >= 0, x -> x - 1).forEach(System.out::println);
// Out: 10 9 8 7 6 5 4 3 2 1 0
Descripción del método:
IntStream.iterate(int seed, IntPredicate hasNext, IntUnaryOperator next);
-
seed
- el elemento inicial; -
hasNext
- un predicado para aplicar a los elementos para determinar cuándo debe terminar la secuencia; -
next
- una función que se aplicará al elemento anterior para producir un nuevo elemento.
Esta es la solución que se me ocurrió:
private static final Comparator<Integer> BY_ASCENDING_ORDER = Integer::compare;
private static final Comparator<Integer> BY_DESCENDING_ORDER = BY_ASCENDING_ORDER.reversed();
luego usando esos comparadores:
IntStream.range(-range, 0).boxed().sorted(BY_DESCENDING_ORDER).forEach(// etc...
Java 8 forma de hacer esto:
List<Integer> list = Arrays.asList(1,2,3,4);
Comparator<Integer> comparator = Integer::compare;
list.stream().sorted(comparator.reversed()).forEach(System.out::println);
La manera más genérica y más fácil de revertir una lista será:
public static <T> void reverseHelper(List<T> li){
li.stream()
.sorted((x,y)-> -1)
.collect(Collectors.toList())
.forEach(System.out::println);
}
Muchas de las soluciones aquí ordenan o invierten el IntStream
, pero eso requiere innecesariamente un almacenamiento intermedio. La solución de Stuart Marks es el camino a seguir:
static IntStream revRange(int from, int to) {
return IntStream.range(from, to).map(i -> to - i + from - 1);
}
También maneja correctamente el desbordamiento, pasando esta prueba:
@Test
public void testRevRange() {
assertArrayEquals(revRange(0, 5).toArray(), new int[]{4, 3, 2, 1, 0});
assertArrayEquals(revRange(-5, 0).toArray(), new int[]{-1, -2, -3, -4, -5});
assertArrayEquals(revRange(1, 4).toArray(), new int[]{3, 2, 1});
assertArrayEquals(revRange(0, 0).toArray(), new int[0]);
assertArrayEquals(revRange(0, -1).toArray(), new int[0]);
assertArrayEquals(revRange(MIN_VALUE, MIN_VALUE).toArray(), new int[0]);
assertArrayEquals(revRange(MAX_VALUE, MAX_VALUE).toArray(), new int[0]);
assertArrayEquals(revRange(MIN_VALUE, MIN_VALUE + 1).toArray(), new int[]{MIN_VALUE});
assertArrayEquals(revRange(MAX_VALUE - 1, MAX_VALUE).toArray(), new int[]{MAX_VALUE - 1});
}
No es puramente Java8 pero si utilizas el método Lists.reverse () de guayapa en conjunto, puedes lograrlo fácilmente:
List<Integer> list = Arrays.asList(1,2,3,4);
Lists.reverse(list).stream().forEach(System.out::println);
Para la pregunta específica de generar un IntStream
inverso, intente algo como esto:
static IntStream revRange(int from, int to) {
return IntStream.range(from, to)
.map(i -> to - i + from - 1);
}
Esto evita el boxeo y la clasificación.
Para la pregunta general de cómo revertir un flujo de cualquier tipo, no sé si hay una forma "adecuada". Hay un par de formas en que puedo pensar. Ambos terminan almacenando los elementos de la secuencia. No sé de una forma de invertir una secuencia sin almacenar los elementos.
Esta primera forma almacena los elementos en una matriz y los lee en una secuencia en orden inverso. Tenga en cuenta que, dado que no conocemos el tipo de tiempo de ejecución de los elementos de la secuencia, no podemos escribir la matriz correctamente, lo que requiere un modelo sin verificar.
@SuppressWarnings("unchecked")
static <T> Stream<T> reverse(Stream<T> input) {
Object[] temp = input.toArray();
return (Stream<T>) IntStream.range(0, temp.length)
.mapToObj(i -> temp[temp.length - i - 1]);
}
Otra técnica utiliza colectores para acumular los artículos en una lista invertida. Esto hace muchas inserciones en la parte frontal de los objetos ArrayList
, por lo que hay muchas copias en curso.
Stream<T> input = ... ;
List<T> output =
input.collect(ArrayList::new,
(list, e) -> list.add(0, e),
(list1, list2) -> list1.addAll(0, list2));
Probablemente sea posible escribir un colector reversible mucho más eficiente utilizando algún tipo de estructura de datos personalizada.
ACTUALIZACIÓN 2016-01-29
Debido a que esta pregunta ha llamado recientemente la atención, creo que debería actualizar mi respuesta para resolver el problema con la inserción al frente de ArrayList
. Esto será terriblemente ineficiente con una gran cantidad de elementos, que requieren O (N ^ 2) copia.
Es preferible utilizar un ArrayDeque
en ArrayDeque
lugar, que admite de manera eficiente la inserción en la parte delantera. Una pequeña arruga es que no podemos usar la forma de tres Stream.collect()
de Stream.collect()
; requiere que los contenidos del segundo arg se fusionen en el primer arg, y no haya operación masiva "add-all-en-front" en Deque
. En cambio, usamos addAll()
para anexar el contenido del primer arg al final del segundo, y luego devolvemos el segundo. Esto requiere usar el método de fábrica Collector.of()
.
El código completo es este:
Deque<String> output =
input.collect(Collector.of(
ArrayDeque::new,
(deq, t) -> deq.addFirst(t),
(d1, d2) -> { d2.addAll(d1); return d2; }));
El resultado es una Deque
lugar de una List
, pero eso no debería ser un gran problema, ya que puede repetirse o transmitirse fácilmente en el orden ahora invertido.
Pregunta general:
Stream no almacena ningún elemento.
Por lo tanto, iterar elementos en el orden inverso no es posible sin almacenar los elementos en alguna colección intermedia.
Stream.of("1", "2", "20", "3")
.collect(Collectors.toCollection(ArrayDeque::new)) // or LinkedList
.descendingIterator()
.forEachRemaining(System.out::println);
Actualización: se cambió LinkedList a ArrayDeque (mejor) ver aquí para detalles
Huellas dactilares:
3
20
2
1
Por cierto, el método de sort
no es correcto ya que ordena, NO se invierte (suponiendo que el flujo puede tener elementos desordenados)
Pregunta específica:
Encontré esto simple, más fácil e intuitivo (comentario Copiado @Holger)
IntStream.iterate(to - 1, i -> i - 1).limit(to - from)
Puede definir su propio recopilador que recopila los elementos en orden inverso:
public static <T> Collector<T, List<T>, List<T>> inReverse() {
return Collector.of(
ArrayList::new,
(l, t) -> l.add(t),
(l, r) -> {l.addAll(r); return l;},
Lists::<T>reverse);
}
Y úsalo como:
stream.collect(inReverse()).forEach(t -> ...)
Utilizo una ArrayList en orden directo para insertar de manera eficiente los elementos de colección (al final de la lista) y Guava Lists.reverse para ofrecer de manera eficiente una vista invertida de la lista sin hacer otra copia de la misma.
Aquí hay algunos casos de prueba para el recopilador personalizado:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import org.hamcrest.Matchers;
import org.junit.Test;
import com.google.common.collect.Lists;
public class TestReverseCollector {
private final Object t1 = new Object();
private final Object t2 = new Object();
private final Object t3 = new Object();
private final Object t4 = new Object();
private final Collector<Object, List<Object>, List<Object>> inReverse = inReverse();
private final Supplier<List<Object>> supplier = inReverse.supplier();
private final BiConsumer<List<Object>, Object> accumulator = inReverse.accumulator();
private final Function<List<Object>, List<Object>> finisher = inReverse.finisher();
private final BinaryOperator<List<Object>> combiner = inReverse.combiner();
@Test public void associative() {
final List<Object> a1 = supplier.get();
accumulator.accept(a1, t1);
accumulator.accept(a1, t2);
final List<Object> r1 = finisher.apply(a1);
final List<Object> a2 = supplier.get();
accumulator.accept(a2, t1);
final List<Object> a3 = supplier.get();
accumulator.accept(a3, t2);
final List<Object> r2 = finisher.apply(combiner.apply(a2, a3));
assertThat(r1, Matchers.equalTo(r2));
}
@Test public void identity() {
final List<Object> a1 = supplier.get();
accumulator.accept(a1, t1);
accumulator.accept(a1, t2);
final List<Object> r1 = finisher.apply(a1);
final List<Object> a2 = supplier.get();
accumulator.accept(a2, t1);
accumulator.accept(a2, t2);
final List<Object> r2 = finisher.apply(combiner.apply(a2, supplier.get()));
assertThat(r1, equalTo(r2));
}
@Test public void reversing() throws Exception {
final List<Object> a2 = supplier.get();
accumulator.accept(a2, t1);
accumulator.accept(a2, t2);
final List<Object> a3 = supplier.get();
accumulator.accept(a3, t3);
accumulator.accept(a3, t4);
final List<Object> r2 = finisher.apply(combiner.apply(a2, a3));
assertThat(r2, contains(t4, t3, t2, t1));
}
public static <T> Collector<T, List<T>, List<T>> inReverse() {
return Collector.of(
ArrayList::new,
(l, t) -> l.add(t),
(l, r) -> {l.addAll(r); return l;},
Lists::<T>reverse);
}
}
Respondiendo a la pregunta específica de invertir con IntStream, a continuación funcionó para mí:
IntStream.range(0, 10)
.map(x -> x * -1)
.sorted()
.map(Math::abs)
.forEach(System.out::println);
Si se implementa Comparable <T> (por ejemplo, Integer, String, Date), puede hacerlo utilizando Comparator.reverseOrder () .
List<Integer> list = Arrays.asList(1, 2, 3, 4);
list.stream()
.sorted(Comparator.reverseOrder())
.forEach(System.out::println);
Solución elegante
List<Integer> list = Arrays.asList(1,2,3,4);
list.stream()
.boxed() // Converts Intstream to Stream<Integer>
.sorted(Collections.reverseOrder()) // Method on Stream<Integer>
.forEach(System.out::println);
Sugiero usar jOOλ , es una gran biblioteca que agrega mucha funcionalidad útil a Java 8 streams y lambdas.
A continuación, puede hacer lo siguiente:
List<Integer> list = Arrays.asList(1,2,3,4);
Seq.seq(list).reverse().forEach(System.out::println)
Simple como eso. Es una biblioteca bastante liviana, y vale la pena agregarla a cualquier proyecto de Java 8.
Uno podría escribir un coleccionista que colecciona elementos en orden inverso:
public static <T> Collector<T, ?, Stream<T>> reversed() {
return Collectors.collectingAndThen(Collectors.toList(), list -> {
Collections.reverse(list);
return list.stream();
});
}
Y úsalo así:
Stream.of(1, 2, 3, 4, 5).collect(reversed()).forEach(System.out::println);
Respuesta original (contiene un error: no funciona correctamente para transmisiones paralelas):
Un método inverso de secuencia general podría ser similar a:
public static <T> Stream<T> reverse(Stream<T> stream) {
LinkedList<T> stack = new LinkedList<>();
stream.forEach(stack::push);
return stack.stream();
}
sin lib externo ...
import java.util.List;
import java.util.Collections;
import java.util.stream.Collector;
public class MyCollectors {
public static <T> Collector<T, ?, List<T>> toListReversed() {
return Collectors.collectingAndThen(Collectors.toList(), l -> {
Collections.reverse(l);
return l;
});
}
}
cyclops-react StreamUtils tiene un método Stream inverso ( javadoc ).
StreamUtils.reverse(Stream.of("1", "2", "20", "3"))
.forEach(System.out::println);
Funciona al recopilar en una ArrayList y luego hacer uso de la clase ListIterator que puede iterar en cualquier dirección, para iterar hacia atrás sobre la lista.
Si ya tiene una lista, será más eficiente
StreamUtils.reversedStream(Arrays.asList("1", "2", "20", "3"))
.forEach(System.out::println);
Manera más simple (recopilación simple: admite transmisiones paralelas):
public static <T> Stream<T> reverse(Stream<T> stream) {
return stream
.collect(Collector.of(
() -> new ArrayDeque<T>(),
ArrayDeque::addFirst,
(q1, q2) -> { q2.addAll(q1); return q2; })
)
.stream();
}
Forma avanzada (admite transmisiones paralelas de forma continua):
public static <T> Stream<T> reverse(Stream<T> stream) {
Objects.requireNonNull(stream, "stream");
class ReverseSpliterator implements Spliterator<T> {
private Spliterator<T> spliterator;
private final Deque<T> deque = new ArrayDeque<>();
private ReverseSpliterator(Spliterator<T> spliterator) {
this.spliterator = spliterator;
}
@Override
@SuppressWarnings({"StatementWithEmptyBody"})
public boolean tryAdvance(Consumer<? super T> action) {
while(spliterator.tryAdvance(deque::addFirst));
if(!deque.isEmpty()) {
action.accept(deque.remove());
return true;
}
return false;
}
@Override
public Spliterator<T> trySplit() {
// After traveling started the spliterator don''t contain elements!
Spliterator<T> prev = spliterator.trySplit();
if(prev == null) {
return null;
}
Spliterator<T> me = spliterator;
spliterator = prev;
return new ReverseSpliterator(me);
}
@Override
public long estimateSize() {
return spliterator.estimateSize();
}
@Override
public int characteristics() {
return spliterator.characteristics();
}
@Override
public Comparator<? super T> getComparator() {
Comparator<? super T> comparator = spliterator.getComparator();
return (comparator != null) ? comparator.reversed() : null;
}
@Override
public void forEachRemaining(Consumer<? super T> action) {
// Ensure that tryAdvance is called at least once
if(!deque.isEmpty() || tryAdvance(action)) {
deque.forEach(action);
}
}
}
return StreamSupport.stream(new ReverseSpliterator(stream.spliterator()), stream.isParallel());
}
Tenga en cuenta que puede extenderse rápidamente a otro tipo de flujos (IntStream, ...).
Pruebas:
// Use parallel if you wish only
revert(Stream.of("One", "Two", "Three", "Four", "Five", "Six").parallel())
.forEachOrdered(System.out::println);
Resultados:
Six
Five
Four
Three
Two
One
Notas adicionales: La simplest way
no es tan útil cuando se usa con otras operaciones de flujo (la combinación recolectada rompe el paralelismo). La advance way
no tiene ese problema, y también mantiene las características iniciales de la secuencia, por ejemplo SORTED
, y por lo tanto, es la forma de usar otras operaciones de la secuencia después de la inversa.