web-dev-qa-db-fra.com

Ordre inverse des flux Java 8

Question générale: Quelle est la bonne façon d’inverser un flux? En supposant que nous ne sachions pas de quel type d’élément se compose ce flux, quel est le moyen générique d’inverser un flux?

Question spécifique: 

IntStream fournit une méthode de plage pour générer des entiers dans une plage spécifique IntStream.range(-range, 0), maintenant que je souhaite l'inverser, le passage d'une plage de 0 à une plage négative ne fonctionne pas. Je ne peux pas non plus utiliser Integer::compare.

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

avec IntStream je vais avoir cette erreur du compilateur

Erreur: (191, 0) ajc: la méthode sorted() dans le type IntStream n'est pas applicable pour les arguments (Integer::compare)

qu'est-ce que j'oublie ici?

109
vach

Pour la question spécifique de générer une variable IntStream inverse, essayez l'une des solutions suivantes:

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

Cela évite la boxe et le tri.

Pour la question générale de savoir comment inverser un flux de n'importe quel type, je ne sais pas s'il existe un moyen "approprié". Il y a plusieurs façons auxquelles je peux penser. Les deux finissent par stocker les éléments de flux. Je ne connais pas de moyen d'inverser un flux sans stocker les éléments.

Cette première méthode stocke les éléments dans un tableau et les lit dans un flux dans l'ordre inverse. Notez que, comme nous ne connaissons pas le type d'exécution des éléments de flux, nous ne pouvons pas taper le tableau correctement, ce qui nécessite une distribution non contrôlée.

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

Une autre technique utilise des collecteurs pour accumuler les éléments dans une liste inversée. Cela fait beaucoup d'insertions à l'avant des objets ArrayList, donc il y a beaucoup de copies en cours.

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

Il est probablement possible d'écrire un collecteur d'inversion beaucoup plus efficace en utilisant une sorte de structure de données personnalisée.

MISE À JOUR 2016-01-29

Étant donné que cette question a récemment retenu l'attention, je pense que je devrais mettre à jour ma réponse pour résoudre le problème de l'insertion au début de ArrayList. Ce sera terriblement inefficace avec un grand nombre d’éléments nécessitant une copie O (N ^ 2).

Il est préférable d'utiliser une variable ArrayDeque, qui prend en charge efficacement l'insertion à l'avant. Une petite ride est que nous ne pouvons pas utiliser la forme à trois arguments de Stream.collect(); il faut que le contenu du deuxième argument soit fusionné dans le premier, et il n'y a pas d'opération en bloc "add-all-at-front" sur Deque. Au lieu de cela, nous utilisons addAll() pour ajouter le contenu du premier argument à la fin du second, puis nous retournons le second. Cela nécessite l'utilisation de la méthode d'usine Collector.of().

Le code complet est le suivant:

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

Le résultat est une Deque au lieu d'une List, mais cela ne devrait pas poser trop de problème, car il peut facilement être itéré ou diffusé dans l'ordre inversé.

55
Stuart Marks

Solution élégante 

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);
30
Kishan B

Beaucoup des solutions ici trient ou inversent la IntStream, mais cela nécessite inutilement un stockage intermédiaire. La solution de Stuart Marks est la voie à suivre:

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

Il gère également correctement les débordements en passant ce test:

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

Question générale:

Stream ne stocke aucun élément.

Ainsi, itérer des éléments dans l'ordre inverse n'est pas possible sans stocker les éléments dans une collection intermédiaire.

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

Mise à jour: LinkedList modifié en ArrayDeque (mieux) voir ici pour plus de détails

Impressions:

3

20

2

1

Par ailleurs, l'utilisation de la méthode sort n'est pas correcte car elle trie, elle n'inverse PAS (les flux peuvent avoir des éléments non ordonnés)

Question spécifique: 

J'ai trouvé cela simple, facile et intuitif (commentaire @Holger copié)

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

sans bibliothèque externe ...

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

}
13
comonad

Si implémenté Comparable<T> (ex. Integer, String, Date), vous pouvez le faire en utilisant Comparator.reverseOrder().

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

Vous pouvez définir votre propre collecteur qui collecte les éléments dans l'ordre inverse:

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

Et utilisez-le comme:

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

J'utilise une ArrayList dans l'ordre avant pour insérer efficacement collecter les éléments (à la fin de la liste) et Guava Lists.reverse pour donner efficacement une vue inversée de la liste sans en faire une autre copie.

Voici quelques cas de test pour le collecteur personnalisé:

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

cyclops-react StreamUtils utilise une méthode de flux inversé ( javadoc ). 

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

Cela fonctionne en collectant dans une ArrayList et en utilisant ensuite la classe ListIterator qui peut itérer dans les deux sens, pour parcourir en arrière la liste. 

Si vous avez déjà une liste, ce sera plus efficace

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

Voici la solution que j'ai trouvée:

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

puis en utilisant ces comparateurs:

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

Je suggérerais d'utiliser jOOλ , c'est une excellente bibliothèque qui ajoute de nombreuses fonctionnalités utiles aux flux et lambdas Java 8.

Vous pouvez alors faire ce qui suit:

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

Aussi simple que cela. C'est une bibliothèque assez légère, qu'il convient d'ajouter à tout projet Java 8.

4
lukens

Manière la plus simple (collecte simple - prend en charge les flux parallèles):

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

Méthode avancée (prend en charge les flux parallèles de manière continue):

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

Notez que vous pouvez rapidement l'étendre à d'autres types de flux (IntStream, ...).

Essais:

// Use parallel if you wish only
revert(Stream.of("One", "Two", "Three", "Four", "Five", "Six").parallel())
    .forEachOrdered(System.out::println);

Résultats:

Six
Five
Four
Three
Two
One

Notes supplémentaires: Le simplest way n'est pas très utile lorsqu'il est utilisé avec d'autres opérations de flux (la jointure de collecte rompt le parallélisme). Le advance way n'a pas ce problème, et il conserve également les caractéristiques initiales du flux, par exemple SORTED. C'est donc le moyen d'utiliser d'autres opérations de flux après l'inverse.

3
Tet

Que diriez-vous de cette méthode d'utilité?

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

Semble travailler avec tous les cas sans duplication.

2
Jerome

On pourrait écrire un collecteur qui collecte les éléments dans l'ordre inverse:

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

Et utilisez-le comme ceci:

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

Réponse originale (contient un bogue - il ne fonctionne pas correctement pour les flux parallèles):

Une méthode inverse de flux à usage général pourrait ressembler à:

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

Répondre à la question spécifique de l'inversion avec IntStream, ci-dessous a fonctionné pour moi:

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

Ce n'est pas purement Java8, mais si vous utilisez la méthode Lists.reverse () de goyave conjointement, vous pouvez facilement y parvenir:

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

la solution la plus simple utilise List::listIterator et 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);
1
Adrian

Pour référence, je cherchais le même problème, je voulais joindre la valeur de chaîne des éléments de flux dans l'ordre inverse.

itemList = {dernier, milieu, premier} => premier, milieu, dernier

J'ai commencé à utiliser une collection intermédiaire avec collectingAndThen de comonad ou le collectionneur ArrayDeque de Stuart Marks , même si je n'étais pas satisfait de la collection intermédiaire et de le diffuser à nouveau.

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

J'ai donc itéré sur la réponse de Stuart Marks qui utilisait la fabrique Collector.of, qui a l'intéressant finisher lambda.

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

Étant donné que dans ce cas, le flux n'est pas parallèle, le combineur n'est pas très pertinent. J'utilise quand même insert pour des raisons de cohérence du code, mais cela n'a pas d'importance, car cela dépend du constructeur de chaîne construit en premier.

J'ai regardé le StringJoiner, cependant il n'a pas de méthode insert.

1
Brice

Chaîne d'inversion ou un tableau

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

split peut être modifié en fonction du délimiteur ou de l'espace

0
sai manoj

C'est comme ça que je le fais.

Je n'aime pas l'idée de créer une nouvelle collection et de l'itérer à l'envers.

L'idée de la carte IntStream # est assez soignée, mais je préfère la méthode IntStream # iterate, car je pense que l'idée d'un compte à rebours à zéro est mieux exprimée avec la méthode iterate et plus facile à comprendre en termes de navigation du tableau de haut en bas.

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

Voici quelques tests pour prouver que cela fonctionne:

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

Dans tout cela, je ne vois pas la réponse à laquelle je voudrais aller en premier.

Ce n'est pas exactement une réponse directe à la question, mais c'est une solution potentielle au problème.

Construisez simplement la liste à l’arrière. Si vous le pouvez, utilisez une liste LinkedList au lieu d'une liste ArrayList. Lorsque vous ajoutez des éléments, utilisez "Push" au lieu de "add". La liste sera construite dans l'ordre inverse et sera ensuite diffusée correctement sans aucune manipulation.

Cela ne convient pas aux cas où vous utilisez des tableaux primitifs ou des listes qui sont déjà utilisés de diverses manières mais qui fonctionnent bien dans un nombre surprenant de cas.

0
Bill K

ArrayDeque sont plus rapides dans la pile que Stack ou LinkedList. "Push ()" insère des éléments à l'avant de la Deque

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