Last active
January 16, 2020 13:56
-
-
Save reitzig/1d28952895cda22bc40685e8a80f6407 to your computer and use it in GitHub Desktop.
Head and tail variants on (finite) Java streams (Java 11, JUnit 5)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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