Pergunta

Pergunta geral:Qual é a maneira correta de reverter um fluxo?Supondo que não sabemos em que tipo de elementos consiste esse fluxo, qual é a maneira genérica de reverter qualquer fluxo?

Pergunta específica:

IntStream fornece método de intervalo para gerar inteiros em intervalo específico IntStream.range(-range, 0), agora que quero reverter, mudar o intervalo de 0 para negativo não funcionará, também não posso usar Integer::compare

List<Integer> list = Arrays.asList(1,2,3,4);
list.stream().sorted(Integer::compare).forEach(System.out::println);

com IntStream Vou receber este erro do compilador

Erro:(191, 0) ajc:O método sorted() no tipo IntStream não é aplicável aos argumentos (Integer::compare)

O que estou perdendo aqui?

Foi útil?

Solução

Para a questão específica de gerar um retorno IntStream, tente algo assim:

static IntStream revRange(int from, int to) {
    return IntStream.range(from, to)
                    .map(i -> to - i + from - 1);
}

Isso evita encaixotamento e classificação.

Para a questão geral de como reverter um fluxo de qualquer tipo, não sei se existe uma maneira "correta".Posso pensar em algumas maneiras.Ambos acabam armazenando os elementos do fluxo.Não conheço uma maneira de reverter um fluxo sem armazenar os elementos.

Esta primeira forma armazena os elementos em um array e os lê em um fluxo na ordem inversa.Observe que como não sabemos o tipo de tempo de execução dos elementos do fluxo, não podemos digitar o array corretamente, exigindo uma conversão não verificada.

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

Outra técnica utiliza coletores para acumular os itens em uma lista invertida.Isso faz muitas inserções na frente do ArrayList objetos, então há muitas cópias acontecendo.

Stream<T> input = ... ;
List<T> output =
    input.collect(ArrayList::new,
                  (list, e) -> list.add(0, e),
                  (list1, list2) -> list1.addAll(0, list2));

Provavelmente é possível escrever um coletor reverso muito mais eficiente usando algum tipo de estrutura de dados customizada.

ATUALIZAÇÃO 29/01/2016

Como esta questão recebeu um pouco de atenção recentemente, acho que devo atualizar minha resposta para resolver o problema de inserção na frente de ArrayList.Isso será terrivelmente ineficiente com um grande número de elementos, exigindo cópia O(N^2).

É preferível usar um ArrayDeque em vez disso, o que suporta eficientemente a inserção na frente.Um pequeno problema é que não podemos usar a forma de três argumentos de Stream.collect();requer que o conteúdo do segundo argumento seja mesclado no primeiro argumento e não há operação em massa "adicionar tudo na frente" em Deque.Em vez disso, usamos addAll() para acrescentar o conteúdo do primeiro argumento ao final do segundo e, em seguida, retornamos o segundo.Isto requer o uso do Collector.of() método de fábrica.

O código completo é este:

Deque<String> output =
    input.collect(Collector.of(
        ArrayDeque::new,
        (deq, t) -> deq.addFirst(t),
        (d1, d2) -> { d2.addAll(d1); return d2; }));

O resultado é um Deque em vez de um List, mas isso não deve ser um grande problema, pois pode ser facilmente iterado ou transmitido na ordem invertida.

Outras dicas

Solução 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);

Muitas das soluções aqui classificam ou invertem o IntStream, mas isso requer desnecessariamente armazenamento intermediário. A solução de Stuart Marks é o caminho a seguir:

static IntStream revRange(int from, int to) {
    return IntStream.range(from, to).map(i -> to - i + from - 1);
}

Ele também lida corretamente com o overflow, passando neste teste:

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

Pergunta Geral:

Stream não armazena nenhum elemento.

Portanto, iterar elementos na ordem inversa não é possível sem armazenar os elementos em alguma coleção intermediária.

Stream.of("1", "2", "20", "3")
      .collect(Collectors.toCollection(ArrayDeque::new)) // or LinkedList
      .descendingIterator()
      .forEachRemaining(System.out::println);

Atualizar:LinkedList alterado para ArrayDeque (melhor) veja aqui para detalhes

Impressões:

3

20

2

1

Aliás, usando sort o método não está correto ao classificar, NÃO reverte (assumindo que o fluxo pode ter elementos não ordenados)

Pergunta específica:

Achei isso simples, mais fácil e intuitivo (copiado @Holger Comente)

IntStream.iterate(to - 1, i -> i - 1).limit(to - from)

sem biblioteca externa...

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

}

Se implementado Comparable<T> (ex. Integer, String, Date), você pode fazer isso usando Comparator.reverseOrder().

List<Integer> list = Arrays.asList(1, 2, 3, 4);
list.stream()
     .sorted(Comparator.reverseOrder())
     .forEach(System.out::println);

Você poderia definir seu próprio coletor que coleta os elementos na ordem inversa:

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

E use-o como:

stream.collect(inReverse()).forEach(t -> ...)

Eu uso um ArrayList na ordem direta para inserir com eficiência a coleta de itens (no final da lista) e Guava Lists.reverse para fornecer com eficiência uma visão invertida da lista sem fazer outra cópia dela.

Aqui estão alguns casos de teste para o coletor 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);
    }
}

ciclope-reage StreamUtils tem um método Stream reverso (javadoc).

  StreamUtils.reverse(Stream.of("1", "2", "20", "3"))
             .forEach(System.out::println);

Ele funciona coletando em um ArrayList e, em seguida, fazendo uso da classe ListIterator, que pode iterar em qualquer direção, para iterar de trás para frente na lista.

Se você já possui uma Lista, será mais eficiente

  StreamUtils.reversedStream(Arrays.asList("1", "2", "20", "3"))
             .forEach(System.out::println);

Aqui está a solução que encontrei:

private static final Comparator<Integer> BY_ASCENDING_ORDER = Integer::compare;
private static final Comparator<Integer> BY_DESCENDING_ORDER = BY_ASCENDING_ORDER.reversed();

então usando esses comparadores:

IntStream.range(-range, 0).boxed().sorted(BY_DESCENDING_ORDER).forEach(// etc...

Eu sugeriria usar joOλ, é uma ótima biblioteca que adiciona muitas funcionalidades úteis aos streams e lambdas do Java 8.

Você pode então fazer o seguinte:

List<Integer> list = Arrays.asList(1,2,3,4);    
Seq.seq(list).reverse().forEach(System.out::println)

Simples assim.É uma biblioteca bastante leve e vale a pena adicioná-la a qualquer projeto Java 8.

Maneira mais simples (coleta simples - suporta fluxos paralelos):

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

Maneira avançada (suporta fluxos paralelos de forma contínua):

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

Observe que você pode estender rapidamente para outros tipos de fluxos (IntStream, ...).

Teste:

// 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 Adicionais: O simplest way não é tão útil quando usado com outras operações de fluxo (a junção de coleta quebra o paralelismo).O advance way não tem esse problema, e mantém também as características iniciais do stream, por exemplo SORTED, e, portanto, é o caminho a seguir para usar com outras operações de fluxo após o inverso.

Que tal este método utilitário?

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 funcionar com todos os casos sem duplicação.

Poderíamos escrever um coletor que coletasse elementos em ordem inversa:

public static <T> Collector<T, ?, Stream<T>> reversed() {
    return Collectors.collectingAndThen(Collectors.toList(), list -> {
        Collections.reverse(list);
        return list.stream();
    });
}

E use assim:

Stream.of(1, 2, 3, 4, 5).collect(reversed()).forEach(System.out::println);

Resposta original (contém um bug - não funciona corretamente para fluxos paralelos):

Um método reverso de fluxo de uso geral poderia ser semelhante a:

public static <T> Stream<T> reverse(Stream<T> stream) {
    LinkedList<T> stack = new LinkedList<>();
    stream.forEach(stack::push);
    return stack.stream();
}

No que diz respeito à questão específica de gerar uma situação inversa IntStream:

Começando de Java 9 você pode usar a versão de três argumentos do IntStream.iterate(...):

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

onde:

IntStream.iterate​(int seed, IntPredicate hasNext, IntUnaryOperator next);

  • seed - o elemento inicial;
  • hasNext - um predicado para aplicar aos elementos para determinar quando o fluxo deve terminar;
  • next - Uma função a ser aplicada ao elemento anterior para produzir um novo elemento.

Para referência, eu estava analisando o mesmo problema, queria juntar o valor da string dos elementos do fluxo na ordem inversa.

itemList = { último, meio, primeiro } => primeiro, meio, último

Comecei a usar uma coleção intermediária com collectingAndThen de comonada ou o ArrayDeque colecionador de Stuart Marcos, embora eu não tenha ficado satisfeito com a coleta intermediária e com o streaming novamente

itemList.stream()
        .map(TheObject::toString)
        .collect(Collectors.collectingAndThen(Collectors.toList(),
                                              strings -> {
                                                      Collections.reverse(strings);
                                                      return strings;
                                              }))
        .stream()
        .collect(Collector.joining());

Então, repeti a resposta de Stuart Marks que estava usando o Collector.of fábrica, que tem o interessante finalizador lambda.

itemList.stream()
        .collect(Collector.of(StringBuilder::new,
                             (sb, o) -> sb.insert(0, o),
                             (r1, r2) -> { r1.insert(0, r2); return r1; },
                             StringBuilder::toString));

Como neste caso o stream não é paralelo, o combinador não é tão relevante, estou usando insert de qualquer maneira, por uma questão de consistência do código, mas isso não importa, pois dependeria de qual stringbuilder é construído primeiro.

Olhei o StringJoiner, porém ele não possui um insert método.

Respondendo a uma pergunta específica sobre reversão com IntStream, abaixo funcionou para mim:

IntStream.range(0, 10)
  .map(x -> x * -1)
  .sorted()
  .map(Math::abs)
  .forEach(System.out::println);

Não puramente Java8, mas se você usar o método Lists.reverse() do goiaba em conjunto, poderá conseguir isso facilmente:

List<Integer> list = Arrays.asList(1,2,3,4);
Lists.reverse(list).stream().forEach(System.out::println);

ArrayDeque são mais rápidos na pilha do que Stack ou LinkedList."push()" insere elementos na frente do Deque

 protected <T> Stream<T> reverse(Stream<T> stream) {
    ArrayDeque<T> stack = new ArrayDeque<>();
    stream.forEach(stack::push);
    return stack.stream();
}

Invertendo string ou qualquer array

(Stream.of("abcdefghijklm 1234567".split("")).collect(Collectors.collectingAndThen(Collectors.toList(),list -> {Collections.reverse(list);return list;}))).stream().forEach(System.out::println);

split pode ser modificado com base no delimitador ou espaço

a solução mais simples é usar List::listIterator e Stream::generate

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
ListIterator<Integer> listIterator = list.listIterator(list.size());

Stream.generate(listIterator::previous)
      .limit(list.size())
      .forEach(System.out::println);

É assim que eu faço.

Não gosto da ideia de criar uma nova coleção e iterá-la reversamente.

A ideia do IntStream#map é bem legal, mas eu prefiro o método IntStream#iterate, pois acho que a ideia de uma contagem regressiva até Zero é melhor expressa com o método iterate e mais fácil de entender em termos de percorrer o array de trás para frente.

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

Aqui estão alguns testes para provar 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());
}

Em tudo isso não vejo a resposta que eu daria primeiro.

Esta não é exatamente uma resposta direta à pergunta, mas é uma solução potencial para o problema.

Em primeiro lugar, basta construir a lista de trás para frente.Se puder, use um LinkedList em vez de um ArrayList e quando adicionar itens use "Push" em vez de add.A lista será construída na ordem inversa e será transmitida corretamente, sem qualquer manipulação.

Isso não se aplica a casos em que você está lidando com matrizes ou listas primitivas que já são usadas de várias maneiras, mas funciona bem em um número surpreendente de casos.

Lista newStream = list.stream().sorted(Collections.reverseOrder()).collect(Collectors.toList());newStream.forEach(System.out::println);

A maneira mais genérica e fácil de reverter uma lista será:

public static <T> void reverseHelper(List<T> li){

 li.stream()
.sorted((x,y)-> -1)
.collect(Collectors.toList())
.forEach(System.out::println);

    }

Maneira Java 8 de fazer isso:

    List<Integer> list = Arrays.asList(1,2,3,4);
    Comparator<Integer> comparator = Integer::compare;
    list.stream().sorted(comparator.reversed()).forEach(System.out::println);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top