Comment utiliser Java 8 streams pour trouver toutes les valeurs précédant une valeur plus grande?

Cas D'Utilisation

grâce à quelques codes Kata affichés au travail, je suis tombé sur ce problème que je ne suis pas sûr de la façon de résoudre.

utilisant Java 8 Streams, à partir d'une liste d'entiers positifs, produire une liste des entiers où l'entier précède une plus grande valeur.

[10, 1, 15, 30, 2, 6]

l'input ci-dessus donnerait:

[1, 15, 2]

puisque 1 précède 15, 15 précède 30, et 2 précède 6.

Non-Stream Solution

public List<Integer> findSmallPrecedingValues(final List<Integer> values) {

    List<Integer> result = new ArrayList<Integer>();
    for (int i = 0; i < values.size(); i++) {
        Integer next = (i + 1 < values.size() ? values.get(i + 1) : -1);
        Integer current = values.get(i);
        if (current < next) {
            result.push(current);
        }
    }
    return result;
}

ce que j'ai essayé

le problème que j'ai, c'est que je ne sais pas comment accéder à la lambda.

return values.stream().filter(v -> v < next).collect(Collectors.toList());

Question

  • est-il possible de récupérer la valeur suivante dans un flux?
  • est-ce que je devrais utiliser map et mapping vers un Pair pour accéder à la page suivante?
28
demandé sur Tagir Valeev 2015-05-07 03:08:14

6 réponses

utilisant IntStream.range :

static List<Integer> findSmallPrecedingValues(List<Integer> values) {
    return IntStream.range(0, values.size() - 1)
        .filter(i -> values.get(i) < values.get(i + 1))
        .mapToObj(values::get)
        .collect(Collectors.toList());
}

il est certainement plus agréable qu'une solution impérative avec une grande boucle, mais encore un peu meh quant à l'objectif de" l'utilisation d'un ruisseau " d'une manière idiomatique.

est-il possible de récupérer la valeur suivante dans un flux?

non, pas vraiment. La meilleure citation que je connaisse pour qui est dans le java.util.stream paquet description :

les éléments d'un ruisseau ne sont visités qu'une seule fois pendant la vie d'un ruisseau. Comme un Iterator , un nouveau flux doit être généré pour revisiter les mêmes éléments de la source.

(récupérer des éléments en plus de l'élément courant sur lequel ils fonctionnent impliquerait qu'ils pourraient être visités plus d'une fois.)

Nous pourrions aussi techniquement le faire dans un couple d'autres façons:

  • Statlightly (very meh).
  • en utilisant le iterator d'un flux est techniquement toujours en utilisant le flux.
21
répondu Radiodef 2015-05-07 03:12:28

ce N'est pas un pur Java8, mais récemment j'ai publié une petite bibliothèque appelée StreamEx qui a une méthode exactement pour cette tâche:

// Find all numbers where the integer preceded a larger value.
Collection<Integer> numbers = Arrays.asList(10, 1, 15, 30, 2, 6);
List<Integer> res = StreamEx.of(numbers).pairMap((a, b) -> a < b ? a : null)
    .nonNull().toList();
assertEquals(Arrays.asList(1, 15, 2), res);

Le pairMap opération interne mis en œuvre à l'aide personnalisée spliterator . En conséquence, vous avez le code tout à fait propre qui ne dépend pas de savoir si la source est List ou autre chose. Bien sûr, il fonctionne bien avec le flux parallèle que bien.

a commis un testcase pour cette tâche.

12
répondu Tagir Valeev 2015-05-07 01:50:22

ce n'est pas une doublure (c'est une doublure), mais ça marche:

List<Integer> result = new ArrayList<>();
values.stream().reduce((a,b) -> {if (a < b) result.add(a); return b;});

Plutôt que de le résoudre par "la recherche à l'élément suivant", ce qui résout en "regardant précédent élément reduce() donnez-vous gratuitement. J'ai plié son usage prévu en injectant un fragment de code qui popule la liste basée sur la comparaison des éléments précédents et actuels, puis renvoie le courant de sorte que la prochaine itération le verra comme son de l'élément précédent.


Certains test de code:

List<Integer> result = new ArrayList<>();
IntStream.of(10, 1, 15, 30, 2, 6).reduce((a,b) -> {if (a < b) result.add(a); return b;});
System.out.println(result);

sortie:

[1, 15, 2]
7
répondu Bohemian 2015-05-07 06:17:32

la réponse acceptée fonctionne très bien si le flux est séquentiel ou parallèle mais peut souffrir si le List sous-jacent n'est pas un accès aléatoire, en raison d'appels multiples à get .

si votre flux est séquentiel, vous pouvez lancer ce collecteur:

public static Collector<Integer, ?, List<Integer>> collectPrecedingValues() {
    int[] holder = {Integer.MAX_VALUE};
    return Collector.of(ArrayList::new,
            (l, elem) -> {
                if (holder[0] < elem) l.add(holder[0]);
                holder[0] = elem;
            },
            (l1, l2) -> {
                throw new UnsupportedOperationException("Don't run in parallel");
            });
}

et un usage:

List<Integer> precedingValues = list.stream().collect(collectPrecedingValues());

Néanmoins, vous pouvez également mettre en œuvre un collecteur afin que cela fonctionne pour les flux séquentiels et parallèles. Le la seule chose est que vous devez appliquer une transformation finale, mais ici vous avez le contrôle sur l'implémentation List pour ne pas souffrir de la performance get .

l'idée est de générer d'abord une liste de paires (représentée par un tableau int[] de taille 2) qui contient les valeurs dans le flux tranché par une fenêtre de taille 2 avec un intervalle d'un. Quand nous avons besoin de fusionner deux listes, nous vérifions le vide et nous fusionnons le vide du dernier élément du première liste avec le premier élément de la deuxième liste. Ensuite, nous appliquons une transformation finale pour filtrer seulement les valeurs désirées et les mapper pour avoir la sortie désirée.

ce n'est peut-être pas aussi simple que la réponse acceptée, mais cela peut être une solution alternative.

public static Collector<Integer, ?, List<Integer>> collectPrecedingValues() {
    return Collectors.collectingAndThen(
            Collector.of(() -> new ArrayList<int[]>(),
                    (l, elem) -> {
                        if (l.isEmpty()) l.add(new int[]{Integer.MAX_VALUE, elem});
                        else l.add(new int[]{l.get(l.size() - 1)[1], elem});
                    },
                    (l1, l2) -> {
                        if (l1.isEmpty()) return l2;
                        if (l2.isEmpty()) return l1;
                        l2.get(0)[0] = l1.get(l1.size() - 1)[1];
                        l1.addAll(l2);
                        return l1;
                    }), l -> l.stream().filter(arr -> arr[0] < arr[1]).map(arr -> arr[0]).collect(Collectors.toList()));
}

vous pouvez ensuite envelopper ces deux collecteurs dans une méthode de collecteur utilitaire, vérifier si le flux est parallèle avec isParallel puis décider quel collecteur à retourner.

4
répondu Alexis C. 2015-05-08 09:54:37

si vous êtes prêt à utiliser une bibliothèque tierce et n'avez pas besoin de parallélisme, alors jOOλ offre des fonctions de fenêtre de style SQL comme suit

System.out.println(
Seq.of(10, 1, 15, 30, 2, 6)
   .window()
   .filter(w -> w.lead().isPresent() && w.value() < w.lead().get())
   .map(w -> w.value())
   .toList()
);

Rendement

[1, 15, 2]

la fonction lead() accède à la valeur suivante dans l'ordre transversal de la fenêtre.

avertissement: je travaille pour la société derrière jOOλ

1
répondu Lukas Eder 2016-01-07 00:17:32

vous pouvez y parvenir en utilisant une file d'attente délimitée pour stocker les éléments qui coulent à travers le cours d'eau (ce qui est basé sur l'idée que j'ai décrit en détail ici: est-il possible d'obtenir l'élément suivant dans le cours d'eau?

below example first définit instance of BoundedQueue class qui stockera les éléments passant par le flux (si vous n'aimez pas l'idée d'étendre la liste de liens, reportez-vous au lien mentionné ci-dessus pour alternative et plus approche générique). Plus tard, il suffit d'examiner les deux éléments suivants-grâce à la classe helper:

public class Kata {
  public static void main(String[] args) {
    List<Integer> input = new ArrayList<Integer>(asList(10, 1, 15, 30, 2, 6));

    class BoundedQueue<T> extends LinkedList<T> {
      public BoundedQueue<T> save(T curElem) {
        if (size() == 2) { // we need to know only two subsequent elements
          pollLast(); // remove last to keep only requested number of elements
        }

        offerFirst(curElem);
        return this;
      }

      public T getPrevious() {
        return (size() < 2) ? null : getLast();
      }

      public T getCurrent() {
        return (size() == 0) ? null : getFirst();
      }
    }

    BoundedQueue<Integer> streamHistory = new BoundedQueue<Integer>();

    final List<Integer> answer = input.stream()
      .map(i -> streamHistory.save(i))
      .filter(e -> e.getPrevious() != null)
      .filter(e -> e.getCurrent() > e.getPrevious())
      .map(e -> e.getPrevious())
      .collect(Collectors.toList());

    answer.forEach(System.out::println);
  }
}
1
répondu walkeros 2017-05-23 12:25:34