Skip to content

Instantly share code, notes, and snippets.

@reitzig
Last active January 16, 2020 13:56
Show Gist options
  • Save reitzig/1d28952895cda22bc40685e8a80f6407 to your computer and use it in GitHub Desktop.
Save reitzig/1d28952895cda22bc40685e8a80f6407 to your computer and use it in GitHub Desktop.
Head and tail variants on (finite) Java streams (Java 11, JUnit 5)
import com.google.common.base.Preconditions;
import javax.annotation.Nonnull;
import java.util.Iterator;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class StreamUtils {
/**
* Creates a new stream from the given one by dropping all but the {@code n}
* first elements.
*
* @param n must be non-negative.
*/
public static <T> Stream<T> head(@Nonnull Stream<T> stream, long n) {
return stream.limit(n);
}
/**
* Creates a new stream from the given one by dropping all but the {@code n}
* last elements.
*/
public static <T> Stream<T> tail(@Nonnull Stream<T> stream, long streamLength, long n) {
return stream.skip(streamLength - n);
}
/**
* Creates a new stream from the given one by dropping the first {@code n} elements.
*
* @param n must be non-negative.
*/
public static <T> Stream<T> drop(@Nonnull Stream<T> stream, long n) {
return stream.skip(n);
}
/**
* Creates a new stream from the given one by dropping all but the
* {@code dropFront} first and the {@code dropBack} last elements.
*
* @param dropFront must not be negative.
* @param dropBack must not be larger than {@code streamLength}.
*/
public static <T> Stream<T> drop(@Nonnull Stream<T> stream, long streamLength, long dropFront, long dropBack) {
return stream.skip(dropFront).limit(streamLength - dropFront - dropBack);
}
/**
* Creates a new stream from the given one by taking the first {@code headLength} and
* last {@code tailLength} elements.
*
* @param streamLength must be non-negative.
* @param headLength must be non-negative.
* @param tailLength must be non-negative
*/
public static <T> Stream<T> headAndTail(@Nonnull Stream<T> stream, long streamLength, long headLength, long tailLength) {
Preconditions.checkArgument(headLength >= 0);
Preconditions.checkArgument(tailLength >= 0);
if (headLength + tailLength < streamLength) {
return headAndDrop(stream, headLength, streamLength - headLength - tailLength);
} else {
return stream;
}
}
/**
* Creates a new stream from the given one by dropping {@code skipping} elements after the
* {@code headLength} first ones.
*
* @param headLength must be non-negative.
*/
@Nonnull
public static <T> Stream<T> headAndDrop(@Nonnull final Stream<T> stream, final long headLength, final long skipping) {
Preconditions.checkArgument(headLength >= 0);
return Stream.generate(new Supplier<Optional<T>>() {
private long index = 0;
private Iterator<T> iterator = stream.iterator();
@Override
public Optional<T> get() {
if (index < headLength) {
index += 1;
} else if (index == headLength) {
index += 1;
// Skip forward:
for (var i = 0; i < skipping && iterator.hasNext(); i++) {
iterator.next();
}
}
// Dev note: stop incrementing after skipping to avoid re-entering skip branch after overflow
if (!iterator.hasNext()) {
return Optional.empty();
} else {
return Optional.of(iterator.next());
}
}
}).takeWhile(Optional::isPresent) // make finite
.map(Optional::get); // flatten
}
}
import org.junit.jupiter.api.Test;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
class StreamUtilsTest {
@Test
void head_should_take_correctly() {
// Given:
var values = Stream.of(1,2,3,4,5,6,7,8,9,10);
// When:
var head = StreamUtils.head(values, 6);
// Then:
assertThat(head).containsExactly(1,2,3,4,5,6);
}
@Test
void tail_should_take_correctly() {
// Given:
var values = Stream.of(1,2,3,4,5,6,7,8,9,10);
// When:
var tail = StreamUtils.tail(values, 10,7);
// Then:
assertThat(tail).containsExactly(4,5,6,7,8,9,10);
}
@Test
void drop_should_drop_correctly_from_the_front() {
// Given:
var values = Stream.of(1,2,3,4,5,6,7,8,9,10);
// When:
var tail = StreamUtils.drop(values, 4);
// Then:
assertThat(tail).containsExactly(5,6,7,8,9,10);
}
@Test
void drop_should_drop_correctly_from_the_front_and_back() {
// Given:
var values = Stream.of(1,2,3,4,5,6,7,8,9,10);
// When:
var tail = StreamUtils.drop(values, 10, 2, 3);
// Then:
assertThat(tail).containsExactly(3,4,5,6,7);
}
@Test
void headAndTail_should_take_correctly() {
// Given:
var values = Stream.of(1,2,3,4,5,6,7,8,9,10);
// When:
var headAndTail = StreamUtils.headAndTail(values, 10, 3, 2);
// Then:
assertThat(headAndTail).containsExactly(1,2,3,9,10);
}
@Test
void headAndDrop_should_take_correctly() {
// Given:
var values = Stream.of(1,2,3,4,5,6,7,8,9,10);
// When:
var headAndTail = StreamUtils.headAndDrop(values, 2, 4);
// Then:
assertThat(headAndTail).containsExactly(1,2,7,8,9,10);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment