Collectez des paires successives à partir d'un flux

102

Étant donné un flux tel que { 0, 1, 2, 3, 4 },

comment puis-je le transformer le plus élégamment en une forme donnée:

{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }

(en supposant, bien sûr, que j'ai défini la classe Pair)?

Edit: Il ne s'agit pas strictement d'ints ou de flux primitifs. La réponse doit être générale pour un flux de tout type.

Aleksandr Dubinsky
la source
2
Le terme de FP est «partition», mais je ne trouve pas de méthode avec la sémantique souhaitée en Java. Il a un partitionnement sur un prédicat.
Marko Topolnik
1
En règle générale, le séparateur dans JDK 8 est pensé à des fins de traversée et de partitionnement. Je vais essayer de trouver un exemple également.
Olimpiu POP
list.stream().map(i -> new Pair(i, i+1));
aepurniet
2
Pour la question équivalente non streams, voir stackoverflow.com/questions/17453022/…
Raedwald
À propos, certaines personnes utilisent l'une ou l'autre implémentation de Map.Entrycomme classe Pair. (Certes, certains pourraient considérer que c'est un hack, mais utiliser une classe intégrée est pratique.)
Basil Bourque

Réponses:

33

Ma bibliothèque StreamEx qui étend les flux standard fournit une pairMapméthode pour tous les types de flux. Pour les flux primitifs, cela ne change pas le type de flux, mais peut être utilisé pour effectuer certains calculs. L'utilisation la plus courante consiste à calculer les différences:

int[] pairwiseDiffs = IntStreamEx.of(input).pairMap((a, b) -> (b-a)).toArray();

Pour le flux d'objets, vous pouvez créer tout autre type d'objet. Ma bibliothèque ne fournit aucune nouvelle structure de données visible par l'utilisateur comme Pair(c'est la partie du concept de bibliothèque). Cependant, si vous avez votre propre Pairclasse et que vous souhaitez l'utiliser, vous pouvez effectuer les opérations suivantes:

Stream<Pair> pairs = IntStreamEx.of(input).boxed().pairMap(Pair::new);

Ou si vous en avez déjà Stream:

Stream<Pair> pairs = StreamEx.of(stream).pairMap(Pair::new);

Cette fonctionnalité est implémentée à l'aide d' un séparateur personnalisé . Il a une surcharge assez faible et peut bien se paralléliser. Bien sûr, cela fonctionne avec n'importe quelle source de flux, pas seulement une liste / un tableau d'accès aléatoire comme beaucoup d'autres solutions. Dans de nombreux tests, il fonctionne très bien. Voici un benchmark JMH où nous trouvons toutes les valeurs d'entrée précédant une valeur plus grande en utilisant différentes approches (voir cette question).

Tagir Valeev
la source
Je vous remercie! Plus j'étudie cette bibliothèque, plus je l'aime. Je pourrais enfin commencer à utiliser des flux. ( StreamEximplements Iterable! Hourra!)
Aleksandr Dubinsky
Pour que votre réponse soit complète à 100%, pourriez-vous montrer comment envelopper un Streamdans un StreamEx?
Aleksandr Dubinsky
3
@AleksandrDubinsky: il suffit d'utiliser StreamEx.of(stream). Il existe d'autres méthodes statiques pratiques pour créer le flux à partir de Collection, tableau Reader, etc. Modification de la réponse.
Tagir Valeev
@TagirValeev est pairMapcommandé sur des flux séquentiels? En fait, j'aimerais avoir forPairsOrdered (), mais comme il n'y a pas de telle méthode, puis-je la simuler d'une manière ou d'une autre? stream.ordered().forPairs()ou stream().pairMap().forEachOrdered()?
Askar Kalykov
1
@AskarKalykov, pairMapest l'opération intermédiaire avec la fonction de mappeur sans état non interférente, l'ordre n'est pas spécifié pour elle de la même manière que pour simple map. Le forPairsn'est pas ordonné par spécification, mais les opérations non ordonnées sont de facto ordonnées pour les flux séquentiels. Ce serait bien si vous formuliez votre problème d'origine en tant que question de stackoverflow distincte pour fournir plus de contexte.
Tagir Valeev
74

La bibliothèque de flux Java 8 est principalement destinée à diviser les flux en plus petits morceaux pour un traitement parallèle, de sorte que les étapes de pipeline avec état sont assez limitées, et les actions telles que l'obtention de l'index de l'élément de flux actuel et l'accès aux éléments de flux adjacents ne sont pas prises en charge.

Un moyen typique de résoudre ces problèmes, avec certaines limitations, bien sûr, consiste à piloter le flux par des index et à compter sur le traitement des valeurs dans une structure de données à accès aléatoire comme une ArrayList à partir de laquelle les éléments peuvent être récupérés. Si les valeurs étaient dans arrayList, on pourrait générer les paires comme demandé en faisant quelque chose comme ceci:

    IntStream.range(1, arrayList.size())
             .mapToObj(i -> new Pair(arrayList.get(i-1), arrayList.get(i)))
             .forEach(System.out::println);

Bien sûr, la limitation est que l'entrée ne peut pas être un flux infini. Ce pipeline peut cependant être exécuté en parallèle.

Marques Stuart
la source
5
"L'entrée ne peut pas être un flux infini." En fait, l'entrée ne peut pas du tout être un flux. L'entrée ( arrayList) est en fait une collection, c'est pourquoi je ne l'ai pas marquée comme réponse. (Mais félicitations pour votre badge d'or!)
Aleksandr Dubinsky
16

Ce n'est pas élégant, c'est une solution hackish, mais fonctionne pour des flux infinis

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

Vous pouvez désormais limiter votre diffusion à la longueur souhaitée

pairStream.limit(1_000_000).forEach(i -> System.out.println(i));

PS j'espère qu'il y a une meilleure solution, quelque chose comme clojure(partition 2 1 stream)

mishadoff
la source
6
Félicitations pour avoir souligné que les classes anonymes sont une alternative parfois utile aux lambdas.
Aleksandr Dubinsky
2
@aepurniet Je suppose que cela ne fonctionnera pas correctement. Selon le parallelStreamdoc: "Pour préserver un comportement correct, ces paramètres comportementaux doivent être non interférents, et dans la plupart des cas doivent être apatrides"
mishadoff
14
Ceci est complètement contraire à la conception du framework de flux et viole directement le contrat de l'API de carte, car la fonction anonyme n'est pas sans état. Essayez de l'exécuter avec un flux parallèle et plus de données pour que le framework de flux crée plus de threads de travail, et vous verrez le résultat: des "erreurs" aléatoires peu fréquentes presque impossibles à reproduire et difficiles à détecter tant que vous n'avez pas suffisamment de données (en production?). Cela peut être désastreux.
Mario Rossi
4
@AleksandrDubinsky Vous vous trompez sur le fait que les limites / sauts sont parallélisables; l'implémentation fournie dans JDK fonctionne en fait en parallèle. Étant donné que l'opération est liée à l'ordre de rencontre, la parallélisation peut ne pas toujours offrir un avantage en termes de performances, mais dans les situations à Q élevé, elle le peut.
Brian Goetz
4
@AleksandrDubinsky Incorrect. Il peut sauter un élément aléatoire si le flux n'est pas ordonné (n'a pas d'ordre de rencontre défini, donc logiquement il n'y a pas de "premier" ou "nième" élément, juste des éléments.) Mais que le flux soit ordonné ou non, skip a toujours été en mesure travailler en parallèle. Il y a juste moins de parallélisme à extraire si le flux est ordonné, mais il est toujours parallèle.
Brian Goetz
15

J'ai implémenté un wrapper de séparateur qui prend tous les néléments Tdu séparateur d'origine et produit 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();
    }
}

La méthode suivante peut être utilisée pour créer un flux consécutif:

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

Exemple d'utilisation:

consecutiveStream(Stream.of(0, 1, 2, 3, 4, 5), 2)
    .map(list -> new Pair(list.get(0), list.get(1)))
    .forEach(System.out::println);
Tomek Rękawek
la source
Est-ce que cela répète chaque élément deux fois?
Aleksandr Dubinsky
Nan. Il crée un nouveau flux contenant des List<E>éléments. Chaque liste contient ndes éléments consécutifs du flux d'origine. Vérifiez-le vous-même;)
Tomek Rękawek
Pourriez-vous modifier votre réponse pour que chaque élément (sauf le premier et le dernier) soit répété?
Aleksandr Dubinsky
4
+1 Je pense que c'est un bon travail et devrait être généralisé à n'importe quelle taille de pas en plus de la taille de la partition. Il y a beaucoup de besoin pour une (partition size step)fonction et c'est la meilleure façon de l'obtenir.
Marko Topolnik
3
Pensez à utiliser ArrayDequepour les performances, de préférence à LinkedList.
Marko Topolnik
14

Vous pouvez le faire avec la méthode Stream.reduce () (je n'ai vu aucune autre réponse utilisant cette technique).

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;
}
SamTebbs33
la source
1
Il renverrait (1,2) (2,3) au lieu de (1,2) (3,4). De plus, je ne sais pas si cela serait appliqué dans l'ordre (il n'y a certainement aucune garantie de cela).
Aleksandr Dubinsky
1
Veuillez vérifier la question, c'est le comportement prévu @Aleksandr Dubinsky
SamTebbs33
4
Ahh, oui, désolé. Et pour réfléchir, je l'ai écrit.
Aleksandr Dubinsky
6

Vous pouvez le faire dans cyclops-react (je contribue à cette bibliothèque), en utilisant l'opérateur glissant.

  LazyFutureStream.of( 0, 1, 2, 3, 4 )
                  .sliding(2)
                  .map(Pair::new);

Ou

   ReactiveSeq.of( 0, 1, 2, 3, 4 )
                  .sliding(2)
                  .map(Pair::new);

En supposant que le constructeur Pair peut accepter une Collection avec 2 éléments.

Si vous souhaitez grouper par 4 et incrémenter de 2, cela est également pris en charge.

     ReactiveSeq.rangeLong( 0L,Long.MAX_VALUE)
                .sliding(4,2)
                .forEach(System.out::println);

Des méthodes statiques équivalentes pour créer une vue glissante sur java.util.stream.Stream sont également fournies dans la classe Cyclops -streams StreamUtils .

       StreamUtils.sliding(Stream.of(1,2,3,4),2)
                  .map(Pair::new);

Remarque: - pour une opération monothread, ReactiveSeq serait plus approprié. LazyFutureStream étend ReactiveSeq mais est principalement conçu pour une utilisation simultanée / parallèle (c'est un Stream of Futures).

LazyFutureStream étend ReactiveSeq qui étend Seq du génial jOOλ (qui étend java.util.stream.Stream), de sorte que les solutions présentées par Lukas fonctionneraient également avec l'un ou l'autre type de Stream. Pour toute personne intéressée, les principales différences entre les opérateurs fenêtre / glissement sont le compromis évident puissance / complexité relative et l'aptitude à être utilisé avec des flux infinis (le glissement ne consomme pas le flux, mais des tampons lorsqu'il s'écoule).

John McClean
la source
De cette façon, vous obtenez [(0,1) (2,3) ...] mais la question demande [(0,1) (1,2) ...]. S'il vous plaît voir ma réponse avec RxJava ...
frhack
1
Vous avez raison, mon mauvais, j'ai mal lu la question - l'opérateur de glissement est le bon à utiliser ici. Je mettrai à jour ma réponse - merci!
John McClean
4

La bibliothèque proton-pack fournit la fonctionnalité fenêtrée. Étant donné une classe Pair et un Stream, vous pouvez le faire comme ceci:

Stream<Integer> st = Stream.iterate(0 , x -> x + 1);
Stream<Pair<Integer, Integer>> pairs = StreamUtils.windowed(st, 2, 1)
                                                  .map(l -> new Pair<>(l.get(0), l.get(1)))
                                                  .moreStreamOps(...);

Maintenant, le pairsflux contient:

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, ...) and so on
Alexis C.
la source
Cependant, il semble que vous deviez créer stdeux fois! Cette bibliothèque peut-elle résoudre le problème en utilisant un seul flux?
Aleksandr Dubinsky
@AleksandrDubinsky Je ne pense pas qu'il soit disponible avec les spliterators actuels. J'ai soumis un problème github.com/poetix/protonpack/issues/9
Alexis C.
@AleksandrDubinsky La windowedfonctionnalité a été ajoutée! Voir la modification.
Alexis C.
1
Pourquoi ne supprimez-vous pas votre ancienne réponse, afin que les autres utilisateurs puissent voir la solution, pas l'historique.
Aleksandr Dubinsky
4

Trouver des paires successives

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

System.out.println(
Seq.of(0, 1, 2, 3, 4)
   .window()
   .filter(w -> w.lead().isPresent())
   .map(w -> tuple(w.value(), w.lead().get())) // alternatively, use your new Pair() class
   .toList()
);

Céder

[(0, 1), (1, 2), (2, 3), (3, 4)]

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

Recherche de triples / quadruples / n-tuples successifs

Une question dans les commentaires demandait une solution plus générale, où non pas des paires mais des n-uplets (ou éventuellement des listes) devraient être collectés. Voici donc une approche alternative:

int n = 3;

System.out.println(
Seq.of(0, 1, 2, 3, 4)
   .window(0, n - 1)
   .filter(w -> w.count() == n)
   .map(w -> w.window().toList())
   .toList()
);

Produire une liste de listes

[[0, 1, 2], [1, 2, 3], [2, 3, 4]]

Sans le filter(w -> w.count() == n), le résultat serait

[[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4], [4]]

Clause de non-responsabilité: je travaille pour l'entreprise derrière jOOλ

Lukas Eder
la source
Intéressant. Et si j'ai besoin de regrouper 3 éléments ou plus? Utiliser w.lead().lead()?
Raul Santelices
1
@RaulSantelices: tuple(w.value(), w.lead(1), w.lead(2))serait une option. J'ai mis à jour ma réponse avec une solution plus générique pourlength = n
Lukas Eder
1
Je comprends bien que ce .window()n'est pas une opération paresseuse qui collecte tout le flux d'entrée dans une collection intermédiaire, puis crée un nouveau flux à partir de celui-ci?
Tagir Valeev
@TagirValeev: Oui, c'est l'implémentation actuelle. Dans le cas ci - dessus (pas Comparatorutilisé pour les fenêtres Réorganiser), puis une optimisation comme cela serait possible, et est susceptible d'être mis en œuvre à l'avenir.
Lukas Eder
2

Nous pouvons utiliser RxJava ( bibliothèque d' extension réactive très puissante )

IntStream intStream  = IntStream.iterate(1, n -> n + 1);

Observable<List<Integer>> pairObservable = Observable.from(intStream::iterator).buffer(2,1);

pairObservable.take(10).forEach(b -> {
            b.forEach(n -> System.out.println(n));
            System.out.println();
        });

L' opérateur de tampon transforme un Observable qui émet des éléments en un Observable qui émet des collections tamponnées de ces éléments.

fracasser
la source
1
J'ai utilisé Observable.zip(obs, obs.skip(1), pair->{...})jusqu'à maintenant! Je ne savais pas qu'il y Observable.bufferavait une version avec une étape (et je suis habitué à l' zipastuce de python). +1
Reut Sharabani
1

L'opération est essentiellement avec état donc pas vraiment ce que les flux sont censés résoudre - voir la section "Comportements sans état" dans le javadoc :

La meilleure approche consiste à éviter les paramètres comportementaux avec état pour diffuser entièrement les opérations

Une solution ici est d'introduire un état dans votre flux via un compteur externe, bien que cela ne fonctionnera qu'avec un flux séquentiel.

public static void main(String[] args) {
    Stream<String> strings = Stream.of("a", "b", "c", "c");
    AtomicReference<String> previous = new AtomicReference<>();
    List<Pair> collect = strings.map(n -> {
                            String p = previous.getAndSet(n);
                            return p == null ? null : new Pair(p, n);
                        })
                        .filter(p -> p != null)
                        .collect(toList());
    System.out.println(collect);
}


static class Pair<T> {
    private T left, right;
    Pair(T left, T right) { this.left = left; this.right = right; }
    @Override public String toString() { return "{" + left + "," + right + '}'; }
}
assylies
la source
La question demande de collecter des éléments successifs d'un flux d'entrée, pas simplement de collecter des entiers successifs. Une clarification importante de la terminologie Stream:! = "Lambdas".
Aleksandr Dubinsky
Vous pouvez remplacer AtomicInteger par un AtomicReference. L'alternative est de rouler votre propre collecteur ou d'utiliser des bibliothèques externes comme dans cet exemple: stackoverflow.com/a/30090528/829571
assylias
Voir ma modification. De plus, je ne suis pas sûr de comprendre votre commentaire sur lambda! = Stream. L'autre réponse qui utilise une classe anonyme fait essentiellement la même chose sauf que l'état est détenu par la classe anonyme au lieu d'être externe ...
assylias
1
Ça marche. La StreamExbibliothèque est également une bonne trouvaille et pourrait être une réponse en soi. Mon commentaire sur "streams! = Lambdas" fait référence à vous en déclarant "L'opération est essentiellement avec état donc pas vraiment ce que les lambdas sont censés résoudre." Je pense que vous vouliez utiliser le mot «flux».
Aleksandr Dubinsky
Oh je vois - j'ai clarifié cela.
assylias
0

Dans votre cas, j'écrirais mon IntFunction personnalisé qui garde la trace du dernier int passé et l'utiliser pour mapper l'IntStream d'origine.

import java.util.function.IntFunction;
import java.util.stream.IntStream;

public class PairFunction implements IntFunction<PairFunction.Pair> {

  public static class Pair {

    private final int first;
    private final int second;

    public Pair(int first, int second) {
      this.first = first;
      this.second = second;
    }

    @Override
    public String toString() {
      return "[" + first + "|" + second + "]";
    }
  }

  private int last;
  private boolean first = true;

  @Override
  public Pair apply(int value) {
    Pair pair = !first ? new Pair(last, value) : null;
    last = value;
    first = false;
    return pair;
  }

  public static void main(String[] args) {

    IntStream intStream = IntStream.of(0, 1, 2, 3, 4);
    final PairFunction pairFunction = new PairFunction();
    intStream.mapToObj(pairFunction)
        .filter(p -> p != null) // filter out the null
        .forEach(System.out::println); // display each Pair

  }

}
jpvee
la source
Le problème, c'est que cela jette l'apatridie par la fenêtre.
Rob
@Rob et quel est le problème avec ça?
Aleksandr Dubinsky
L'un des principaux points de lambda est de ne pas avoir d'état mutable afin que les intégrateurs internes puissent paralléliser le travail.
Rob
@Rob: Oui, vous avez raison, mais le flux d'exemple donné défie le parallélisme de toute façon car chaque élément (sauf le premier et le dernier) est utilisé comme premier et deuxième élément d'une paire.
jpvee
@jpvee ouais je pensais que c'était ce à quoi tu pensais. Je me demande cependant s'il n'y a pas un moyen de faire cela avec un autre mappeur. En substance, tout ce dont vous auriez besoin serait l'équivalent de faire passer l'incrémenteur de boucle par deux puis de faire prendre au foncteur 2 arguments. Cela doit être possible.
Rob
0

Pour le calcul de différences successives dans le temps (valeurs x) d'une série chronologique, j'utilise la streams » collect(...)procédé:

final List< Long > intervals = timeSeries.data().stream()
                    .map( TimeSeries.Datum::x )
                    .collect( DifferenceCollector::new, DifferenceCollector::accept, DifferenceCollector::combine )
                    .intervals();

Où le DifferenceCollector est quelque chose comme ceci:

public class DifferenceCollector implements LongConsumer
{
    private final List< Long > intervals = new ArrayList<>();
    private Long lastTime;

    @Override
    public void accept( final long time )
    {
        if( Objects.isNull( lastTime ) )
        {
            lastTime = time;
        }
        else
        {
            intervals.add( time - lastTime );
            lastTime = time;
        }
    }

    public void combine( final DifferenceCollector other )
    {
        intervals.addAll( other.intervals );
        lastTime = other.lastTime;
    }

    public List< Long > intervals()
    {
        return intervals;
    }
}

Vous pouvez probablement le modifier en fonction de vos besoins.

Rob Philipp
la source
0

J'ai finalement trouvé un moyen de tromper le Stream.reduce pour pouvoir gérer proprement des paires de valeurs; il existe une multitude de cas d'utilisation qui nécessitent cette fonctionnalité qui n'apparaît pas naturellement dans JDK 8:

public static int ArithGeo(int[] arr) {
    //Geometric
    List<Integer> diffList = new ArrayList<>();
    List<Integer> divList = new ArrayList<>();
    Arrays.stream(arr).reduce((left, right) -> {
        diffList.add(right-left);
        divList.add(right/left);
        return right;
    });
    //Arithmetic
    if(diffList.stream().distinct().count() == 1) {
        return 1;
    }
    //Geometric
    if(divList.stream().distinct().count() == 1) {
        return 2;
    }
    return -1;
}

L'astuce que j'utilise est le bon retour; déclaration.

Beezer
la source
1
Je ne pense pas que cela reducedonne des garanties suffisantes pour que cela fonctionne.
Aleksandr Dubinsky
Serait intéressé d'en savoir plus sur les garanties suffisantes . Pouvez-vous s'il vous plaît élaborer? Il y a peut-être une alternative à Guava ... mais je suis contraint et je ne peux pas l'utiliser.
Beezer
-1

Une solution élégante serait d'utiliser zip . Quelque chose comme:

List<Integer> input = Arrays.asList(0, 1, 2, 3, 4);
Stream<Pair> pairStream = Streams.zip(input.stream(),
                                      input.stream().substream(1),
                                      (a, b) -> new Pair(a, b)
);

C'est assez concis et élégant, mais il utilise une liste comme entrée. Une source de flux infinie ne peut pas être traitée de cette manière.

Un autre problème (beaucoup plus gênant) est que le zip avec toute la classe Streams a été récemment supprimé de l'API. Le code ci-dessus ne fonctionne qu'avec b95 ou versions antérieures. Donc, avec le dernier JDK, je dirais qu'il n'y a pas de solution élégante de style FP et pour le moment, nous pouvons juste espérer que d'une manière ou d'une autre, zip sera réintroduit dans l'API.

gadget
la source
En effet, a zipété supprimé. Je ne me rappelle pas tout ce qui était sur la Streamsclasse, mais certaines choses ont migré à être des méthodes statiques sur l' Streaminterface, et il y a aussi StreamSupportet Stream.Builderclasses.
Stuart marque
C'est vrai. Certaines autres méthodes comme concat ou iterate ont été déplacées et sont devenues des méthodes par défaut dans Stream. Malheureusement, zip vient d'être supprimé de l'API. Je comprends les raisons de ce choix (par exemple le manque de tuples) mais c'était quand même une fonctionnalité intéressante.
gadget du
2
@gadget Qu'est-ce que les tuples ont à voir avec zip? La raison pédante qui pourrait être inventée ne justifie pas le meurtre zip.
Aleksandr Dubinsky
@AleksandrDubinsky Dans la plupart des cas, zip est utilisé pour produire une collection de paires / tuples en sortie. Ils ont fait valoir que s'ils gardaient le zip, les gens demanderaient également des tuples dans le cadre du JDK. Cependant, je n'aurais jamais supprimé une fonctionnalité existante.
gadget du
-1

C'est un problème intéressant. Ma tentative hybride ci-dessous est-elle bonne?

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3);
    Iterator<Integer> first = list.iterator();
    first.next();
    if (first.hasNext())
        list.stream()
        .skip(1)
        .map(v -> new Pair(first.next(), v))
        .forEach(System.out::println);
}

Je pense qu'il ne se prête pas à un traitement parallèle et peut donc être disqualifié.

Kedar Mhaswade
la source
La question ne demandait pas de traitement parallèle, mais elle supposait que nous n'avions qu'un Stream, pas un List. Bien sûr, nous pouvons également extraire un itérateur d'un Stream, donc cela pourrait être une solution valide. Néanmoins, c'est une approche originale.
Aleksandr Dubinsky
-1

Comme d'autres l'ont observé, il y a, en raison de la nature du problème, un certain état requis.

J'ai été confronté à un problème similaire, dans lequel je voulais ce qui était essentiellement la fonction Oracle SQL LEAD. Ma tentative de mise en œuvre est ci-dessous.

/**
 * Stream that pairs each element in the stream with the next subsequent element.
 * The final pair will have only the first item, the second will be null.
 */
<T> Spliterator<Pair<T>> lead(final Stream<T> stream)
{
    final Iterator<T> input = stream.sequential().iterator();

    final Iterable<Pair<T>> iterable = () ->
    {
        return new Iterator<Pair<T>>()
        {
            Optional<T> current = getOptionalNext(input);

            @Override
            public boolean hasNext()
            {
                return current.isPresent();
            }

            @Override
            public Pair<T> next()
            {
                Optional<T> next = getOptionalNext(input);
                final Pair<T> pair = next.isPresent()
                    ? new Pair(current.get(), next.get())
                    : new Pair(current.get(), null);
                current = next;

                return pair;
            }
        };
    };

    return iterable.spliterator();
}

private <T> Optional<T> getOptionalNext(final Iterator<T> iterator)
{
    return iterator.hasNext()
        ? Optional.of(iterator.next())
        : Optional.empty();
}
Hoons
la source
-1

Vous pouvez y parvenir en utilisant une file d'attente limitée pour stocker les éléments qui circulent dans le flux (qui repose sur l'idée que j'ai décrite en détail ici: est-il possible d'obtenir l'élément suivant dans le flux? )

L'exemple Belows définit d'abord l'instance de la classe BoundedQueue qui stockera les éléments passant par le flux (si vous n'aimez pas l'idée d'étendre la LinkedList, reportez-vous au lien mentionné ci-dessus pour une approche alternative et plus générique). Plus tard, vous combinez simplement deux éléments suivants dans l'instance de Pair:

public class TwoSubsequentElems {
  public static void main(String[] args) {
    List<Integer> input = new ArrayList<Integer>(asList(0, 1, 2, 3, 4));

    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<Pair<Integer>> answer = input.stream()
      .map(i -> streamHistory.save(i))
      .filter(e -> e.getPrevious() != null)
      .map(e -> new Pair<Integer>(e.getPrevious(), e.getCurrent()))
      .collect(Collectors.toList());

    answer.forEach(System.out::println);
  }
}
marcheurs
la source
-3

Je suis d'accord avec @aepurniet mais à la place, vous devez utiliser mapToObj

range(0, 100).mapToObj((i) -> new Pair(i, i+1)).forEach(System.out::println);
user190364
la source
1
Droite. Mais cela collecte simplement des paires d'entiers, pas des paires d'éléments d'un flux (de tout type).
Aleksandr Dubinsky
-5

Exécutez une forboucle qui va de 0 à length-1de votre flux

for(int i = 0 ; i < stream.length-1 ; i++)
{
    Pair pair = new Pair(stream[i], stream[i+1]);
    // then add your pair to an array
}
Savv
la source
3
et où est la partie lambda de la solution?
Olimpiu POP
Ce n'est pas le cas lorsque le flux est infini
mishadoff
@Olimpiu - Où avez-vous obtenu un lambda était une exigence? J'ai lu la question deux fois pour m'assurer de ne pas la manquer. J'ai également vérifié l'historique des modifications. Et la question n'est pas étiquetée avec.
jww