Comment transformer un stream Java en fenêtre glissante?

Serait-ce la méthode recommandée pour transformer un stream en fenêtre glissante?

Par exemple, en Ruby, vous pouvez utiliser each_cons :

irb(main):020:0> [1,2,3,4].each_cons(2) { |x| puts x.inspect } [1, 2] [2, 3] [3, 4] => nil irb(main):021:0> [1,2,3,4].each_cons(3) { |x| puts x.inspect } [1, 2, 3] [2, 3, 4] => nil 

En goyave, je n’ai trouvé que la partition Iterators # , qui est liée mais pas de fenêtre glissante:

 final Iterator> partition = Iterators.partition(IntStream.range(1, 5).iterator(), 3); partition.forEachRemaining(System.out::println); --> [1, 2, 3] [4] 

Cette fonction n’existe pas dans l’API car elle prend en charge le parallel processing et séquentiel et il est très difficile de fournir un parallel processing efficace pour la fonction de fenêtre glissante pour une source de stream arbitraire (même le parallel processing de paires efficace est assez difficile, je l’ai donc implémenté, alors je sais. ).

Toutefois, si votre source est la List avec access aléatoire rapide, vous pouvez utiliser la méthode subList() pour obtenir le comportement souhaité, comme subList() :

 public static  Stream> sliding(List list, int size) { if(size > list.size()) return Stream.empty(); return IntStream.range(0, list.size()-size+1) .mapToObj(start -> list.subList(start, start+size)); } 

Une méthode similaire est actuellement disponible dans ma bibliothèque StreamEx : voir StreamEx.ofSubLists() .

Il existe également d’autres solutions tierces qui ne se soucient pas du traitement en parallèle et fournissent une fonctionnalité glissante utilisant un tampon interne. Par exemple, protonpack StreamUtils.windowed .

Si vous souhaitez utiliser une bibliothèque tierce et que vous n’avez pas besoin de parallélisme, alors jOOλ propose des fonctions de fenêtre de type SQL, comme suit

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

cédant

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

Et

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

cédant

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

Voici un article de blog sur la façon dont cela fonctionne .

Disclaimer: Je travaille pour la société derrière jOOλ

Une autre option, cyclops- react, se construit au-dessus de l’interface Seq de jOOλ (et de JDK 8 Stream), mais simple-react reconstruit la concurrence et le parallélisme (si cela est important pour vous – en créant Streams of Futures).

Vous pouvez utiliser les puissantes fonctions de fenêtrage de Lukas avec l’une ou l’autre bibliothèque (car nous étendons l’impressionnant jOOλ), mais il existe également un opérateur glissant qui, à mon avis, simplifie les choses et convient à une utilisation dans des stream infinis (c’est-à-dire qu’il ne consum pas le stream, mais les valeurs tampons au fur et à mesure qu’ils traversent).

Avec ReactiveSeq, cela ressemblerait à ceci:

 ReactiveSeq.of(1, 2, 3, 4) .sliding(2) .forEach(System.out::println); 

Avec LazyFutureStream pourrait ressembler à l’exemple ci-dessous –

  LazyFutureStream.iterate(1,i->i+1) .sliding(3,2) //lists of 3, increment 2 .forEach(System.out::println); 

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

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

Si vous souhaitez travailler directement avec chaque vue glissante, vous pouvez utiliser l’opérateur glissantT qui renvoie un transformateur de liste. Par exemple, pour append un numéro à chaque élément dans chaque vue glissante, puis réduire chaque fenêtre glissante à la sum de ses éléments, nous pouvons faire: –

  ReactiveSeq windowsSummed = ReactiveSeq.fromIterable(data) .slidingT(3) .map(a->a+toAdd) .reduce(0,(a,b)->a+b) .stream(); 

Disclaimer: Je travaille pour la société derrière cyclops-react

Si vous souhaitez apporter toute la puissance des collections persistantes de Scala à Java, vous pouvez utiliser la bibliothèque Javaslang .

 // this imports List, Stream, Iterator, ... import javaslang.collection.*; Iterator.range(1, 5).sliding(3) .forEach(System.out::println); // ---> // List(1, 2, 3) // List(2, 3, 4) Iterator.range(1, 5).sliding(2, 3) .forEach(System.out::println); // ---> // List(1, 2) // List(4) Iterator.ofAll(javaStream).sliding(3); 

Vous pouvez non seulement utiliser Iterator, cela fonctionne également pour presque toutes les autres collections de Javaslang: Array, Vector, List, Stream, Queue, HashSet, LinkedHashSet, TreeSet, …

entrez la description de l'image ici

(Présentation Javaslang 2.1.0-alpha)

Disclaimer: Je suis le créateur de Javaslang

J’ai trouvé la solution sur le blog Nurkiewicz de Tomek ( https://www.nurkiewicz.com/2014/07/grouping-sampling-and-batching-custom.html ). Ci-dessous SlidingCollector que vous pouvez utiliser:

 public class SlidingCollector implements Collector>, List>> { private final int size; private final int step; private final int window; private final Queue buffer = new ArrayDeque<>(); private int totalIn = 0; public SlidingCollector(int size, int step) { this.size = size; this.step = step; this.window = max(size, step); } @Override public Supplier>> supplier() { return ArrayList::new; } @Override public BiConsumer>, T> accumulator() { return (lists, t) -> { buffer.offer(t); ++totalIn; if (buffer.size() == window) { dumpCurrent(lists); shiftBy(step); } }; } @Override public Function>, List>> finisher() { return lists -> { if (!buffer.isEmpty()) { final int totalOut = estimateTotalOut(); if (totalOut > lists.size()) { dumpCurrent(lists); } } return lists; }; } private int estimateTotalOut() { return max(0, (totalIn + step - size - 1) / step) + 1; } private void dumpCurrent(List> lists) { final List batch = buffer.stream().limit(size).collect(toList()); lists.add(batch); } private void shiftBy(int by) { for (int i = 0; i < by; i++) { buffer.remove(); } } @Override public BinaryOperator>> combiner() { return (l1, l2) -> { throw new UnsupportedOperationException("Combining not possible"); }; } @Override public Set characteristics() { return EnumSet.noneOf(Characteristics.class); } } 

Ci-dessous quelques exemples de Tomekin Spock (j’espère que c’est lisible):

 import static com.nurkiewicz.CustomCollectors.sliding @Unroll class CustomCollectorsSpec extends Specification { def "Sliding window of #input with size #size and step of 1 is #output"() { expect: input.stream().collect(sliding(size)) == output where: input | size | output [] | 5 | [] [1] | 1 | [[1]] [1, 2] | 1 | [[1], [2]] [1, 2] | 2 | [[1, 2]] [1, 2] | 3 | [[1, 2]] 1..3 | 3 | [[1, 2, 3]] 1..4 | 2 | [[1, 2], [2, 3], [3, 4]] 1..4 | 3 | [[1, 2, 3], [2, 3, 4]] 1..7 | 3 | [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7]] 1..7 | 6 | [1..6, 2..7] } def "Sliding window of #input with size #size and no overlapping is #output"() { expect: input.stream().collect(sliding(size, size)) == output where: input | size | output [] | 5 | [] 1..3 | 2 | [[1, 2], [3]] 1..4 | 4 | [1..4] 1..4 | 5 | [1..4] 1..7 | 3 | [1..3, 4..6, [7]] 1..6 | 2 | [[1, 2], [3, 4], [5, 6]] } def "Sliding window of #input with size #size and some overlapping is #output"() { expect: input.stream().collect(sliding(size, 2)) == output where: input | size | output [] | 5 | [] 1..4 | 5 | [[1, 2, 3, 4]] 1..7 | 3 | [1..3, 3..5, 5..7] 1..6 | 4 | [1..4, 3..6] 1..9 | 4 | [1..4, 3..6, 5..8, 7..9] 1..10 | 4 | [1..4, 3..6, 5..8, 7..10] 1..11 | 4 | [1..4, 3..6, 5..8, 7..10, 9..11] } def "Sliding window of #input with size #size and gap of #gap is #output"() { expect: input.stream().collect(sliding(size, size + gap)) == output where: input | size | gap | output [] | 5 | 1 | [] 1..9 | 4 | 2 | [1..4, 7..9] 1..10 | 4 | 2 | [1..4, 7..10] 1..11 | 4 | 2 | [1..4, 7..10] 1..12 | 4 | 2 | [1..4, 7..10] 1..13 | 4 | 2 | [1..4, 7..10, [13]] 1..13 | 5 | 1 | [1..5, 7..11, [13]] 1..12 | 5 | 3 | [1..5, 9..12] 1..13 | 5 | 3 | [1..5, 9..13] } def "Sampling #input taking every #nth th element is #output"() { expect: input.stream().collect(sliding(1, nth)) == output where: input | nth | output [] | 1 | [] [] | 5 | [] 1..3 | 5 | [[1]] 1..6 | 2 | [[1], [3], [5]] 1..10 | 5 | [[1], [6]] 1..100 | 30 | [[1], [31], [61], [91]] } } 

Une autre option serait d’implémenter un Spliterator personnalisé, comme cela a été fait ici :

 import java.util.*; public class SlidingWindowSpliterator implements Spliterator> { static  Stream> windowed(Collection stream, int windowSize) { return StreamSupport.stream( new SlidingWindowSpliterator<>(stream, windowSize), false); } private final Queue buffer; private final Iterator sourceIterator; private final int windowSize; private final int size; private SlidingWindowSpliterator(Collection source, int windowSize) { this.buffer = new ArrayDeque<>(windowSize); this.sourceIterator = Objects.requireNonNull(source).iterator(); this.windowSize = windowSize; this.size = calculateSize(source, windowSize); } @Override public boolean tryAdvance(Consumer> action) { if (windowSize < 1) { return false; } while (sourceIterator.hasNext()) { buffer.add(sourceIterator.next()); if (buffer.size() == windowSize) { action.accept(Arrays.stream((T[]) buffer.toArray(new Object[0]))); buffer.poll(); return sourceIterator.hasNext(); } } return false; } @Override public Spliterator> trySplit() { return null; } @Override public long estimateSize() { return size; } @Override public int characteristics() { return ORDERED | NONNULL | SIZED; } private static int calculateSize(Collection source, int windowSize) { return source.size() < windowSize ? 0 : source.size() - windowSize + 1; } }