package de.jungblut.online.stream; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Spliterator; import java.util.Spliterators; import java.util.stream.Stream; import java.util.stream.StreamSupport; import com.google.common.base.Preconditions; import com.google.common.collect.AbstractIterator; /** * A shuffled iterator. The implementation buffers a fixed amount of data by * consuming the stream, shuffles it and makes it available as a stream again. */ public final class ShuffledIterator<T> extends AbstractIterator<T> { private final Iterator<T> baseStreamIterator; private final int bufferedItems; private final List<T> buffer; private int currentIndex; ShuffledIterator(Stream<T> baseStream, int bufferedItems) { Preconditions.checkState(bufferedItems > 0, "bufferedItems > 0"); Preconditions.checkNotNull(baseStream, "baseStream"); this.bufferedItems = bufferedItems; this.baseStreamIterator = baseStream.iterator(); this.buffer = new ArrayList<>(bufferedItems); bufferAndShuffle(); } @Override protected T computeNext() { if (currentIndex >= buffer.size()) { bufferAndShuffle(); if (buffer.isEmpty()) { return endOfData(); } } return buffer.get(currentIndex++); } public Stream<T> asStream() { return StreamSupport.stream( Spliterators.spliteratorUnknownSize(this, Spliterator.NONNULL), false); } private void bufferAndShuffle() { buffer.clear(); currentIndex = 0; for (int i = 0; i < bufferedItems; i++) { if (baseStreamIterator.hasNext()) { buffer.add(baseStreamIterator.next()); } else { break; } } Collections.shuffle(buffer); } /** * Creates a new shuffled iterator to "proxy shuffle" a stream with the given * shuffle buffer. * * @param baseStream the base stream to load elements from. * @param bufferedItems the buffer size used to shuffle items. * @return a shuffled stream. */ public static <T> Stream<T> fromStream(Stream<T> baseStream, int bufferedItems) { return new ShuffledIterator<>(baseStream, bufferedItems).asStream(); } }