Packrat is a Java library that provides various Gatherer implementations for the Stream API. Gatherers can enhance streams with custom intermediate operations.
You can think of the gatherers like of the custom map, filter, etc. stateful operations which you can insert into the usual streams. No need to use external AtomicLong for counting, for example.
This library can be used as is or as a source of inspiration for writing your own gatherers.
Introduction to the Gatherers by Viktor Klang
Requires JDK 24 or later.
JDK 22 and 23 support gatherers as a preview feature.
<dependency>
<groupId>io.github.jhspetersson</groupId>
<artifactId>packrat</artifactId>
<version>0.2.1</version>
</dependency>implementation("io.github.jhspetersson:packrat:0.2.1")| Name | Description |
|---|---|
| distinctBy | Distinct values with custom mapper |
| filterBy | Filter with custom mapper and (optionally) predicate |
| filterEntries | Filter Map.Entry elements using a BiPredicate on key and value |
| removeBy | Remove with custom mapper and (optionally) predicate |
| removeEntries | Remove Map.Entry elements using a BiPredicate on key and value |
| removeDuplicates | Removes consecutive duplicates from a stream |
| flatMapIf | Optional flatMap depending on predicate |
| minBy | The smallest element compared after mapping applied |
| maxBy | The greatest element compared after mapping applied |
| Name | Description |
|---|---|
| increasing | Increasing sequence, other elements dropped |
| increasingOrEqual | Increasing (or equal) sequence, other elements dropped |
| decreasing | Decreasing sequence, other elements dropped |
| decreasingOrEqual | Decreasing (or equal) sequence, other elements dropped |
| reverse | All elements in reverse order |
| rotate | All elements rotated left or right |
| shuffle | All elements in random order |
| Name | Description |
|---|---|
| mapFirst | Maps first element with mapper, other unchanged |
| mapN | Maps n elements, other unchanged |
| skipAndMap | Skips n elements, maps others |
| skipAndMapN | Skips skipN elements, maps mapN others |
| mapWhile | Maps elements using the supplied function while the predicate evaluates to true. |
| mapUntil | Maps elements using the supplied function until the predicate evaluates to false. |
| Name | Description |
|---|---|
| increasingChunks | Lists of increasing values |
| increasingOrEqualChunks | Lists of increasing or equal values |
| equalChunks | Lists of equal values |
| decreasingChunks | Lists of decreasing values |
| decreasingOrEqualChunks | Lists of decreasing or equal values |
| nCopies | Copies every element n times |
| repeat | Collects the whole stream and repeats it n times |
| atLeast | Distinct values that appear at least n times |
| atMost | Distinct values that appear at most n times |
| Name | Description |
|---|---|
| zip | Zips values with zipper, leftovers dropped |
| mapWithIndex or zipWithIndex | Maps/zips values with an increasing index |
| peekWithIndex | Peek at each element with its index |
| filterWithIndex | Filter elements based on their index and a predicate |
| removeWithIndex | Remove elements based on their index and a predicate |
| windowSlidingWithIndex | Returns fixed-size windows of elements along with their indices |
| windowFixedWithIndex | Returns fixed-size non-overlapping windows of elements along with their indices |
| Name | Description |
|---|---|
| sample | Sample of the specified size |
| randomFilter | Randomly accepts each element with a given probability |
| nth | Takes nth element from the stream |
| dropNth | Drops every nth element from the stream |
| last | Last n elements |
| lastUnique | Last n unique elements |
| lastUniqueBy | Last n unique elements by a mapping function |
| dropLast | Drops last n elements |
| Name | Description |
|---|---|
| chars | String split by Unicode graphemes |
| words | String split by words |
| sentences | String split by sentences |
| Name | Description |
|---|---|
| asGatherer | Converts Collector into Gatherer |
| identity | Passes elements through unchanged |
| Name | Description |
|---|---|
| throwIfNotIncreasing | Validates that stream is strictly increasing, throws on violation |
| throwIfNotIncreasingBy | Validates that stream is strictly increasing, uses mapping function, throws on violation |
| throwIfNotIncreasingOrEqual | Validates that stream is non-decreasing, throws on violation |
| throwIfNotIncreasingOrEqualBy | Validates that stream is non-decreasing, uses mapping function, throws on violation |
| throwIfNotDecreasing | Validates that stream is strictly decreasing, throws on violation |
| throwIfNotDecreasingBy | Validates that stream is strictly decreasing, uses mapping function, throws on violation |
| throwIfNotDecreasingOrEqual | Validates that stream is non-increasing, throws on violation |
| throwIfNotDecreasingOrEqualBy | Validates that stream is non-increasing, uses mapping function, throws on violation |
distinctBy(mapper) - returns elements with distinct values that result from a mapping by the supplied function
import static io.github.jhspetersson.packrat.Packrat.distinctBy;
var oneOddOneEven = IntStream.range(1, 10).boxed().gather(distinctBy(i -> i % 2)).toList();
System.out.println(oneOddOneEven);[1, 2]
filterBy(mapper, value) - filters mapped elements based on the equality to the value, stream continues with original elements
import static io.github.jhspetersson.packrat.Packrat.filterBy;
var oneDigitNumbers = IntStream.range(0, 100).boxed().gather(filterBy(i -> i.toString().length(), 1)).toList();
System.out.println(oneDigitNumbers);[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
filterBy(mapper, value, predicate) - filters mapped elements based on the predicate test against the value, stream continues with original elements
import static io.github.jhspetersson.packrat.Packrat.filterBy;
var ffValue = IntStream.range(0, 1000).boxed().gather(filterBy(Integer::toHexString, "ff", String::equalsIgnoreCase)).toList();
System.out.println(ffValue);[255]
filterEntries(predicate) - filters Map.Entry elements using a BiPredicate that tests the key and value of each entry
import static io.github.jhspetersson.packrat.Packrat.filterEntries;
var map = Map.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5);
// Filter entries where the value is even
var evenValues = map.entrySet().stream()
.gather(filterEntries((key, value) -> value % 2 == 0))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
System.out.println(evenValues);{two=2, four=4}
removeBy(mapper, value) - removes mapped elements based on the equality to the value, stream continues with original elements
import static io.github.jhspetersson.packrat.Packrat.removeBy;
var oneDigitNumbers = IntStream.range(0, 100).boxed().gather(removeBy(i -> i.toString().length(), 2)).toList();
System.out.println(oneDigitNumbers);[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
removeBy(mapper, value, predicate) - removes mapped elements based on the predicate test against the value, stream continues with original elements
import static io.github.jhspetersson.packrat.Packrat.removeBy;
var ageDivisibleByThree = getEmployees().gather(removeBy(emp -> emp.age() % 3, 0, (i, value) -> !Objects.equals(i, value))).toList();
System.out.println(ageDivisibleByThree);[Employee[name=Mark Bloom, age=21], Employee[name=Rebecca Schneider, age=24]]
removeEntries(predicate) - removes Map.Entry elements using a BiPredicate that tests the key and value of each entry
import static io.github.jhspetersson.packrat.Packrat.removeEntries;
var map = Map.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5);
// Remove entries where the value is even
var oddValues = map.entrySet().stream()
.gather(removeEntries((key, value) -> value % 2 == 0))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
System.out.println(oddValues);{one=1, three=3, five=5}
removeDuplicates() - removes consecutive duplicates from a stream, only adjacent elements that are equal will be considered duplicates
import static io.github.jhspetersson.packrat.Packrat.removeDuplicates;
var listWithCopies = List.of(0, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 8, 8, 9, 8, 7, 7, 6, 5, 4, 4, 4, 3, 2, 1, 0);
var unique = listWithCopies.stream().gather(removeDuplicates()).toList();
System.out.println(unique);[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
removeDuplicatesBy(mapper) - removes consecutive duplicates from a stream based on a mapping function, only adjacent elements that have equal mapped values will be considered duplicates
import static io.github.jhspetersson.packrat.Packrat.removeDuplicatesBy;
var people = List.of(
new Person("John", 25),
new Person("Alice", 30),
new Person("Bob", 30),
new Person("Charlie", 30),
new Person("David", 40),
new Person("Eve", 40)
);
var uniqueByAge = people.stream().gather(removeDuplicatesBy(Person::age)).toList();
System.out.println(uniqueByAge);[Person[name=John, age=25], Person[name=Alice, age=30], Person[name=David, age=40]]
flatMapIf(mapper, predicate) - optionally flattens elements mapped to streams depending on the supplied predicate
import static io.github.jhspetersson.packrat.Packrat.flatMapIf;
var strings = Stream.of("A", "BC", "DEF");
var result = strings.gather(flatMapIf(s -> Arrays.stream(s.split("")), s -> s.length() >= 3)).toList();
System.out.println(result);[A, BC, D, E, F]
minBy(mapper) - returns the smallest element in the stream, comparing is done after mapping function applied.
import static io.github.jhspetersson.packrat.Packrat.minBy;
var check = Stream.of("2", "1", "-12", "22", "10").gather(minBy(Long::parseLong)).toList();
System.out.println(check);[-12]
However, resulting list contains an original element of type String;
minBy(mapper, comparator) - returns the smallest element in the stream, comparing with given comparator is done after mapping function applied.
Caution
This gatherer will consume the entire stream before producing any output.
maxBy(mapper) - returns the greatest element in the stream, comparing is done after mapping function applied.
import static io.github.jhspetersson.packrat.Packrat.maxBy;
var check = Stream.of("2", "1", "-12", "22", "10").gather(maxBy(Long::parseLong)).toList();
System.out.println(check);[22]
However, resulting list contains an original element of type String;
maxBy(mapper, comparator) - returns the greatest element in the stream, comparing with given comparator is done after mapping function applied.
Caution
This gatherer will consume the entire stream before producing any output.
increasing() - returns elements in an increasing sequence, elements out of the sequence, as well as repeating values, are dropped
import static io.github.jhspetersson.packrat.Packrat.increasing;
var numbers = Stream.of(1, 2, 2, 5, 4, 2, 6, 9, 3, 11, 0, 1, 20);
var increasingNumbers = numbers.gather(increasing()).toList();
System.out.println(increasingNumbers);[1, 2, 5, 6, 9, 11, 20]
increasingOrEqual() - returns elements in an increasing sequence, repeating values are preserved, elements out of the sequence are dropped
decreasing() - returns elements in a decreasing sequence, elements out of the sequence, as well as repeating values, are dropped
decreasingOrEqual() - returns elements in a decreasing sequence, repeating values are preserved, elements out of the sequence are dropped
reverse() - reverses the elements
import static io.github.jhspetersson.packrat.Packrat.reverse;
var reverseOrdered = IntStream.range(0, 10).boxed().gather(reverse()).toList();
System.out.println(reverseOrdered);[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Caution
This gatherer will consume the entire stream before producing any output.
rotate(distance) - rotates the elements
import static io.github.jhspetersson.packrat.Packrat.rotate;
var positiveRotation = IntStream.range(0, 10).boxed().gather(rotate(3)).toList();
System.out.println(positiveRotation);
var negativeRotation = IntStream.range(0, 10).boxed().gather(rotate(-4)).toList();
System.out.println(negativeRotation);[7, 8, 9, 0, 1, 2, 3, 4, 5, 6]
[4, 5, 6, 7, 8, 9, 0, 1, 2, 3]
Caution
This gatherer will consume the entire stream before producing any output.
shuffle() - shuffle the elements
import static io.github.jhspetersson.packrat.Packrat.shuffle;
var randomlyOrdered = IntStream.range(0, 10).boxed().gather(shuffle()).toList();
System.out.println(randomlyOrdered);[2, 7, 6, 9, 8, 5, 1, 3, 0, 4]
Caution
This gatherer will consume the entire stream before producing any output.
mapFirst(mapper) - returns all elements, the first element is mapped with the supplied mapping function
import static io.github.jhspetersson.packrat.Packrat.mapFirst;
var mapped = IntStream.rangeClosed(1, 10).boxed().gather(mapFirst(n -> n * 10)).toList();
System.out.println(mapped);[10, 2, 3, 4, 5, 6, 7, 8, 9, 10]
mapN(n, mapper) - returns all elements, the first n elements are mapped with the supplied mapping function
import static io.github.jhspetersson.packrat.Packrat.mapN;
var mapped = IntStream.rangeClosed(1, 10).boxed().gather(mapN(5, n -> n * 10)).toList();
System.out.println(mapped);[10, 20, 30, 40, 50, 6, 7, 8, 9, 10]
skipAndMap(n, mapper) - returns all elements that after the first n are mapped with the supplied mapping function
import static io.github.jhspetersson.packrat.Packrat.skipAndMap;
var mapped = IntStream.rangeClosed(1, 10).boxed().gather(skipAndMap(3, n -> n * 10)).toList();
System.out.println(mapped);[1, 2, 3, 40, 50, 60, 70, 80, 90, 100]
skipAndMapN(skipN, mapN, mapper) - returns all elements, after skipN elements the first mapN elements are mapped with the supplied mapping function
import static io.github.jhspetersson.packrat.Packrat.skipAndMapN;
var mapped = IntStream.rangeClosed(1, 10).boxed().gather(skipAndMapN(3, 5, n -> n * 10)).toList();
System.out.println(mapped);[1, 2, 3, 40, 50, 60, 70, 80, 9, 10]
mapWhile(predicate, mapper) - maps elements using the supplied function while the predicate evaluates to true
import static io.github.jhspetersson.packrat.Packrat.mapWhile;
var numbers = IntStream.rangeClosed(1, 10).boxed().gather(mapWhile(n -> n * 10, n -> n < 5)).toList();
System.out.println(numbers);[10, 20, 30, 40, 5, 6, 7, 8, 9, 10]
mapUntil(predicate, mapper) - maps elements using the supplied function until the predicate evaluates to false
import static io.github.jhspetersson.packrat.Packrat.mapUntil;
var numbers = IntStream.rangeClosed(1, 10).boxed().gather(mapUntil(n -> n * 10, n -> n == 5)).toList();
System.out.println(numbers);[10, 20, 30, 40, 5, 6, 7, 8, 9, 10]
increasingChunks() - returns lists ("chunks") of elements, where each next element is greater than the previous one
import static io.github.jhspetersson.packrat.Packrat.increasingChunks;
var numbers = Stream.of(1, 2, 2, 5, 4, 2, 6, 9, 3, 11, 0, 1, 20);
var result = numbers.gather(increasingChunks()).toList();
System.out.println(result);[[1, 2], [2, 5], [4], [2, 6, 9], [3, 11], [0, 1, 20]]
increasingOrEqualChunks() - returns lists ("chunks") of elements, where each next element is greater or equal than the previous one
import static io.github.jhspetersson.packrat.Packrat.increasingOrEqualChunks;
var numbers = Stream.of(1, 2, 2, 5, 4, 2, 6, 9, 3, 11, 0, 1, 20);
var result = numbers.gather(increasingOrEqualChunks()).toList();
System.out.println(result);[[1, 2, 2, 5], [4], [2, 6, 9], [3, 11], [0, 1, 20]]
equalChunks() - returns lists ("chunks") of elements, where all elements in a chunk are equal to each other
import static io.github.jhspetersson.packrat.Packrat.equalChunks;
var numbers = Stream.of(1, 1, 2, 2, 2, 3, 4, 4, 5, 5, 5, 5, 6);
var result = numbers.gather(equalChunks()).toList();
System.out.println(result);[[1, 1], [2, 2, 2], [3], [4, 4], [5, 5, 5, 5], [6]]
equalChunksBy(mapper) - returns lists ("chunks") of elements, where all elements in a chunk have equal values after applying the mapper function
import static io.github.jhspetersson.packrat.Packrat.equalChunks;
var strings = Stream.of("apple", "apricot", "banana", "blueberry", "cherry", "date");
var result = strings.gather(equalChunks(s -> s.charAt(0))).toList();
System.out.println(result);[[apple, apricot], [banana, blueberry], [cherry], [date]]
equalChunks(comparator) - returns lists ("chunks") of elements, where all elements in a chunk are equal according to the supplied comparator
import static io.github.jhspetersson.packrat.Packrat.equalChunks;
// Case-insensitive string comparison
var strings = Stream.of("Apple", "apple", "Banana", "banana", "Cherry", "cherry");
var result = strings.gather(equalChunks(String.CASE_INSENSITIVE_ORDER)).toList();
System.out.println(result);[[Apple, apple], [Banana, banana], [Cherry, cherry]]
equalChunksBy(mapper, comparator) - returns lists ("chunks") of elements, where all elements in a chunk have equal values after applying the mapper function, with equality determined by the supplied comparator
import static io.github.jhspetersson.packrat.Packrat.equalChunks;
record Person(String name, String id) {}
// Group people by the first letter of their ID, case-insensitive
var people = Stream.of(
new Person("John", "A123"),
new Person("Alice", "a456"),
new Person("Bob", "B789"),
new Person("Charlie", "b012"),
new Person("David", "C345"),
new Person("Eve", "c678")
);
var result = people.gather(equalChunks(
p -> p.id().substring(0, 1), // Map to first letter of ID
String.CASE_INSENSITIVE_ORDER // Compare case-insensitive
)).toList();
System.out.println(result);[[Person[name=John, id=A123], Person[name=Alice, id=a456]], [Person[name=Bob, id=B789], Person[name=Charlie, id=b012]], [Person[name=David, id=C345], Person[name=Eve, id=c678]]]
decreasingChunks() - returns lists ("chunks") of elements, where each next element is less than the previous one
decreasingOrEqualChunks() - returns lists ("chunks") of elements, where each next element is less or equal than the previous one
nCopies(n) - returns n copies of every element, n less than or equal to zero effectively empties the stream
import static io.github.jhspetersson.packrat.Packrat.nCopies;
var numbers = IntStream.of(5).boxed().gather(nCopies(10)).toList();
System.out.println(numbers);[5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
repeat(n) - collects the whole stream and repeats it n times, n equal to zero effectively empties the stream
import static io.github.jhspetersson.packrat.Packrat.repeat;
var numbers = Stream.of(1, 2, 3).gather(repeat(2)).toList();
System.out.println(numbers);[1, 2, 3, 1, 2, 3]
Caution
This gatherer will consume the entire stream before producing any output.
atLeast(n) - returns distinct elements that appear at least n times in the stream
import static io.github.jhspetersson.packrat.Packrat.atLeast;
var numbers = Stream.of(1, 2, 3, 3, 3, 4, 5, 5, 6, 7, 8, 8, 8, 8, 9, 10);
var atLeastThree = numbers.gather(atLeast(3)).toList();
System.out.println(atLeastThree);[3, 3, 3, 8, 8, 8, 8]
atMost(n) - returns distinct elements that appear at most n times in the stream
import static io.github.jhspetersson.packrat.Packrat.atMost;
var numbers = Stream.of(1, 2, 3, 3, 3, 4, 5, 5, 6, 7, 8, 8, 8, 8, 9, 10);
var atMostTwo = numbers.gather(atMost(2)).toList();
System.out.println(atMostTwo);[1, 2, 4, 5, 5, 6, 7, 9, 10]
atMostBy(n, mapper) - returns distinct elements mapped by the supplied function that appear at most n times in the stream
import static io.github.jhspetersson.packrat.Packrat.atMostBy;
var strings = Stream.of("apple", "banana", "cherry", "date", "elderberry", "fig", "grape");
var uniqueLengths = strings.gather(atMostBy(1, String::length)).toList();
System.out.println(uniqueLengths);[date, elderberry, fig]
Caution
This gatherer will consume the entire stream before producing any output.
zip(input, mapper) - returns elements mapped ("zipped") with the values from some other stream, iterable or iterator.
import static io.github.jhspetersson.packrat.Packrat.zip;
var names = List.of("Anna", "Mike", "Sandra");
var ages = Stream.of(20, 30, 40, 50, 60, 70, 80, 90);
var users = names.stream().gather(zip(ages, User::new)).toList();
System.out.println(users);[User[name=Anna, age=20], User[name=Mike, age=30], User[name=Sandra, age=40]]
zip(input) - zips current stream and input into Map entries.
import static io.github.jhspetersson.packrat.Packrat.zip;
var names = List.of("Anna", "Mike", "Sandra");
var ages = Stream.of(20, 30, 40);
var users = names.stream().gather(zip(ages)).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
System.out.println(users);{Mike=30, Anna=20, Sandra=40}
zipWithIndex() - zips current stream with an increasing index into Map entries.
import static io.github.jhspetersson.packrat.Packrat.zipWithIndex;
var names = List.of("Anna", "Mike", "Sandra");
var users = names.stream().gather(zipWithIndex()).toList();[0=Anna, 1=Mike, 2=Sandra]
zipWithIndex(startIndex) - zips current stream with an increasing index (beginning with startIndex) into Map entries.
import static io.github.jhspetersson.packrat.Packrat.zipWithIndex;
var names = List.of("Anna", "Mike", "Sandra");
var users = names.stream().gather(zipWithIndex(10)).toList();[10=Anna, 11=Mike, 12=Sandra]
mapWithIndex(mapper) or zipWithIndex(mapper) - maps/zips current stream with an increasing index, mapping function receives the index as the first argument.
import static io.github.jhspetersson.packrat.Packrat.zipWithIndex;
var names = List.of("Anna", "Mike", "Sandra");
var users = names.stream().gather(zipWithIndex(User::new)).toList();[User[index=0, name=Anna], User[index=1, name=Mike], User[index=2, name=Sandra]]
mapWithIndex(mapper, startIndex) or zipWithIndex(mapper, startIndex) - maps/zips current stream with an increasing index (beginning with startIndex), mapping function receives the index as the first argument.
import static io.github.jhspetersson.packrat.Packrat.zipWithIndex;
var names = List.of("Anna", "Mike", "Sandra");
var users = names.stream().gather(zipWithIndex(User::new, 10)).toList();[User[index=10, name=Anna], User[index=11, name=Mike], User[index=12, name=Sandra]]
peekWithIndex(consumer) - peeks at each element along with its index (starting from 0), but passes the original element downstream unchanged
import static io.github.jhspetersson.packrat.Packrat.peekWithIndex;
var names = List.of("Anna", "Mike", "Sandra");
var result = names.stream().gather(peekWithIndex((index, name) ->
System.out.println("Element at index " + index + ": " + name))).toList();
System.out.println(result);Element at index 0: Anna Element at index 1: Mike Element at index 2: Sandra [Anna, Mike, Sandra]
peekWithIndex(consumer, startIndex) - peeks at each element along with its index (beginning with startIndex), but passes the original element downstream unchanged
import static io.github.jhspetersson.packrat.Packrat.peekWithIndex;
var names = List.of("Anna", "Mike", "Sandra");
var result = names.stream().gather(peekWithIndex((index, name) ->
System.out.println("Element at index " + index + ": " + name), 10)).toList();
System.out.println(result);Element at index 10: Anna Element at index 11: Mike Element at index 12: Sandra [Anna, Mike, Sandra]
filterWithIndex(predicate) - filters elements based on their index and a predicate, the index starts from 0
import static io.github.jhspetersson.packrat.Packrat.filterWithIndex;
var numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
var result = numbers.gather(filterWithIndex((index, element) -> index % 2 == 0)).toList();
System.out.println(result);[1, 3, 5, 7, 9]
filterWithIndex(predicate, startIndex) - filters elements based on their index and a predicate, the index starts from startIndex
import static io.github.jhspetersson.packrat.Packrat.filterWithIndex;
var numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
var result = numbers.gather(filterWithIndex((index, element) -> index % 2 == 0, 1)).toList();
System.out.println(result);[2, 4, 6, 8, 10]
removeWithIndex(predicate) - removes elements based on their index and a predicate, the index starts from 0
import static io.github.jhspetersson.packrat.Packrat.removeWithIndex;
var numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
var result = numbers.gather(removeWithIndex((index, element) -> index % 2 == 0)).toList();
System.out.println(result);[2, 4, 6, 8, 10]
removeWithIndex(predicate, startIndex) - removes elements based on their index and a predicate, the index starts from startIndex
import static io.github.jhspetersson.packrat.Packrat.removeWithIndex;
var numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
var result = numbers.gather(removeWithIndex((index, element) -> index % 2 == 0, 1)).toList();
System.out.println(result);[1, 3, 5, 7, 9]
windowSlidingWithIndex(windowSize) - returns fixed-size windows of elements along with their indices, the index starts from 0
import static io.github.jhspetersson.packrat.Packrat.windowSlidingWithIndex;
var numbers = IntStream.rangeClosed(1, 5).boxed();
var result = numbers.gather(windowSlidingWithIndex(3)).toList();
System.out.println(result);[0=[1, 2, 3], 1=[2, 3, 4], 2=[3, 4, 5]]
windowSlidingWithIndex(windowSize, startIndex) - returns fixed-size windows of elements along with their indices, the index starts from startIndex
import static io.github.jhspetersson.packrat.Packrat.windowSlidingWithIndex;
var numbers = IntStream.rangeClosed(1, 5).boxed();
var result = numbers.gather(windowSlidingWithIndex(3, 10)).toList();
System.out.println(result);[10=[1, 2, 3], 11=[2, 3, 4], 12=[3, 4, 5]]
windowSlidingWithIndex(windowSize, mapper) - returns fixed-size windows of elements along with their indices
windowSlidingWithIndex(windowSize, mapper, startIndex) - returns fixed-size windows of elements along with their indices, the index starts from startIndex
windowFixedWithIndex(windowSize) - returns fixed-size non-overlapping windows of elements along with their indices, the index starts from 0
import static io.github.jhspetersson.packrat.Packrat.windowFixedWithIndex;
var numbers = IntStream.rangeClosed(1, 10).boxed();
var result = numbers.gather(windowFixedWithIndex(3)).toList();
System.out.println(result);[0=[1, 2, 3], 1=[4, 5, 6], 2=[7, 8, 9]]
windowFixedWithIndex(windowSize, startIndex) - returns fixed-size non-overlapping windows of elements along with their indices, the index starts from startIndex
import static io.github.jhspetersson.packrat.Packrat.windowFixedWithIndex;
var numbers = IntStream.rangeClosed(1, 6).boxed();
var result = numbers.gather(windowFixedWithIndex(2, 10)).toList();
System.out.println(result);[10=[1, 2], 11=[3, 4], 12=[5, 6]]
windowFixedWithIndex(windowSize, mapper) - returns fixed-size non-overlapping windows of elements along with their indices
windowFixedWithIndex(windowSize, mapper, startIndex) - returns fixed-size non-overlapping windows of elements along with their indices, the index starts from startIndex
sample(n) - returns a sample of the specified size from the stream of elements.
import static io.github.jhspetersson.packrat.Packrat.sample;
var source = IntStream.range(0, 100).boxed().gather(sample(10)).toList();
System.out.println(source);[0, 8, 27, 33, 65, 66, 88, 90, 93, 96]
sample(n, maxSpan) - returns a sample of the specified size from the stream of elements, inspects first maxSpan elements.
randomFilter(probability) - accepts each incoming element independently with the given probability in the inclusive range [0.0, 1.0].
Note
When probability is 0.0 no elements pass; when it is 1.0 all elements pass.
Elements that are not accepted are skipped; the relative order of accepted elements is preserved.
import static io.github.jhspetersson.packrat.Packrat.randomFilter;
var some = IntStream.range(0, 20).boxed().gather(randomFilter(0.3)).toList();
System.out.println(some);[3, 6, 9, 14, 15, 18]
nth(n) - takes every nth element from the stream
import static io.github.jhspetersson.packrat.Packrat.nth;
var numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
var result = numbers.stream().gather(nth(3)).toList();
System.out.println(result);[3, 6, 9]
dropNth(n) - drops every nth element from the stream
import static io.github.jhspetersson.packrat.Packrat.dropNth;
var numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
var result = numbers.stream().gather(dropNth(3)).toList();
System.out.println(result);[1, 2, 4, 5, 7, 8, 10]
last() - returns last element from the stream.
import static io.github.jhspetersson.packrat.Packrat.last;
var integers = IntStream.range(0, 100).boxed().gather(last()).toList();
System.out.println(integers);[99]
last(n) - returns n last elements from the stream.
import static io.github.jhspetersson.packrat.Packrat.last;
var integers = IntStream.range(0, 100).boxed().gather(last(10)).toList();
System.out.println(integers);[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
Caution
This gatherer will consume the entire stream before producing any output.
lastUnique(n) - returns n last unique elements from the stream.
import static io.github.jhspetersson.packrat.Packrat.lastUnique;
var integers = List.of(1, 2, 3, 4, 5, 4, 1, 1, 1, 2, 2, 6).stream().gather(lastUnique(3)).toList();
System.out.println(integers);[1, 2, 6]
Caution
This gatherer will consume the entire stream before producing any output.
lastUniqueBy(n, mapper) - returns n last unique elements from the stream determined by the supplied mapping function.
import static io.github.jhspetersson.packrat.Packrat.lastUniqueBy;
record Employee(String name, int age) {}
var employees = java.util.List.of(
new Employee("Ann Smith", 35),
new Employee("John Rodgers", 40),
new Employee("Mark Bloom", 21),
new Employee("Rebecca Schneider", 24),
new Employee("Luke Norman", 21)
);
var result = employees.stream().gather(lastUniqueBy(3, Employee::age)).toList();
System.out.println(result);[Employee[name=John Rodgers, age=40], Employee[name=Mark Bloom, age=21], Employee[name=Rebecca Schneider, age=24]]
Caution
This gatherer will consume the entire stream before producing any output.
dropLast() - drops last element.
dropLast(n) - drops last n elements from the stream.
import static io.github.jhspetersson.packrat.Packrat.dropLast;
var integers = IntStream.range(0, 10).boxed().gather(dropLast(3)).toList();
System.out.println(integers);[0, 1, 2, 3, 4, 5, 6]
Caution
This gatherer will consume the entire stream before producing any output.
chars() - returns characters as strings parsed from the stream elements
import static io.github.jhspetersson.packrat.Packrat.chars;
var charStrings = Stream.of("Hello, \uD83D\uDC22!").gather(chars()).toList();
System.out.println(charStrings);[H, e, l, l, o, ,, , π’, !]
words() - returns words as strings parsed from the stream elements
import static io.github.jhspetersson.packrat.Packrat.words;
var wordStrings = Stream.of("Another test!").gather(words()).toList();
System.out.println(wordStrings);[Another, test, !]
sentences() - returns sentences as strings parsed from the stream elements
import static io.github.jhspetersson.packrat.Packrat.sentences;
var sentenceStrings = Stream.of("And another one. How many left?").gather(sentences()).toList();
System.out.println(sentenceStrings);[And another one. , How many left?]
asGatherer(collector) - provides the result of the supplied collector as a single element into the stream, effectively converts any Collector into a Gatherer
import static io.github.jhspetersson.packrat.Packrat.asGatherer;
var numbers = Stream.of(1, 2, 3, 4, 5);
var listOfCollectedList = numbers.gather(asGatherer(Collectors.toList())).toList();
System.out.println(listOfCollectedList);[[1, 2, 3, 4, 5]]
identity() - returns a gatherer that passes elements through unchanged
import static io.github.jhspetersson.packrat.Packrat.identity;
var numbers = IntStream.range(0, 5).boxed();
var sameNumbers = numbers.gather(identity()).toList();
System.out.println(sameNumbers);[0, 1, 2, 3, 4]
throwIfNotIncreasingOrEqual() β validates that the incoming elements are ordered in non-decreasing order, if a violation is detected, an exception is thrown immediately, terminating the pipeline
import static io.github.jhspetersson.packrat.Packrat.throwIfNotIncreasingOrEqual;
var result = Stream.of(1, 1, 2, 3, 5)
.gather(Packrat.throwIfNotIncreasingOrEqual())
.toList();The stream is ordered, no exception thrown.
throwIfNotIncreasingOrEqualBy(mapper) β validates that the incoming mapped elements are ordered in non-decreasing order, if a violation is detected, an exception is thrown immediately, terminating the pipeline
import static io.github.jhspetersson.packrat.Packrat.throwIfNotIncreasingOrEqualBy;
record Person(String name, int age) {}
var people = Stream.of(new Person("Ann", 20), new Person("Bob", 25), new Person("Cara", 25));
var ordered = people
.gather(Packrat.throwIfNotIncreasingOrEqualBy(Person::age))
.toList();The stream is ordered by the age of the employees, no exception thrown.
throwIfNotIncreasing() β validates that the incoming elements are strictly increasing (no equal neighbors), if a violation is detected, an exception is thrown immediately, terminating the pipeline
import static io.github.jhspetersson.packrat.Packrat.throwIfNotIncreasing;
// OK
var ok = Stream.of(1, 2, 3, 5)
.gather(throwIfNotIncreasing())
.toList();
// Violates due to equal neighbors (2, 2)
assertThrows(IllegalStateException.class, () ->
Stream.of(1, 2, 2, 3).gather(throwIfNotIncreasing()).toList());throwIfNotIncreasingBy(mapper) β validates that the incoming elements are strictly increasing by a mapped comparable key (no equal neighbors by the mapped value), if a violation is detected, an exception is thrown immediately, terminating the pipeline
import static io.github.jhspetersson.packrat.Packrat.throwIfNotIncreasingBy;
record Person(String name, int age) {}
// OK β strictly increasing by age
var ok = Stream.of(new Person("Ann", 20), new Person("Bob", 25), new Person("Cara", 30))
.gather(throwIfNotIncreasingBy(Person::age))
.toList();
// Violates due to equal ages (25, 25)
assertThrows(IllegalStateException.class, () ->
Stream.of(new Person("Ann", 20), new Person("Bob", 25), new Person("Cara", 25))
.gather(throwIfNotIncreasingBy(Person::age))
.toList());throwIfNotDecreasing() β validates that the incoming elements are strictly decreasing (no equal neighbors), if a violation is detected, an exception is thrown immediately, terminating the pipeline
import static io.github.jhspetersson.packrat.Packrat.throwIfNotDecreasing;
// OK
var ok = Stream.of(5, 3, 1).gather(throwIfNotDecreasing()).toList();
// Violates due to 3 -> 4 step
assertThrows(IllegalStateException.class, () ->
Stream.of(5, 3, 4).gather(throwIfNotDecreasing()).toList());throwIfNotDecreasingBy(mapper) β validates that the incoming elements are strictly decreasing by a mapped comparable key (no equal neighbors by the mapped value), if a violation is detected, an exception is thrown immediately, terminating the pipeline
import static io.github.jhspetersson.packrat.Packrat.throwIfNotDecreasingBy;
record Person(String name, int age) {}
// OK β strictly decreasing by age
var ok = Stream.of(new Person("Zoe", 30), new Person("Yuri", 25), new Person("Xena", 20))
.gather(throwIfNotDecreasingBy(Person::age))
.toList();
// Violates due to equal ages (25, 25)
assertThrows(IllegalStateException.class, () ->
Stream.of(new Person("Zoe", 30), new Person("Yuri", 25), new Person("Yara", 25))
.gather(throwIfNotDecreasingBy(Person::age))
.toList());throwIfNotDecreasingOrEqual() β validates that the incoming elements are non-increasing (decreasing or equal), if a violation is detected, an exception is thrown immediately, terminating the pipeline
import static io.github.jhspetersson.packrat.Packrat.throwIfNotDecreasingOrEqual;
// OK (equal allowed)
var ok = Stream.of(5, 5, 3, 3, 1).gather(throwIfNotDecreasingOrEqual()).toList();
// Violates due to 3 -> 4 step
assertThrows(IllegalStateException.class, () ->
Stream.of(5, 3, 4).gather(throwIfNotDecreasingOrEqual()).toList());throwIfNotDecreasingOrEqualBy(mapper) β validates that the incoming elements are non-increasing (decreasing or equal) by a mapped comparable key, if a violation is detected, an exception is thrown immediately, terminating the pipeline
import static io.github.jhspetersson.packrat.Packrat.throwIfNotDecreasingOrEqualBy;
record Person(String name, int age) {}
// OK β non-increasing by age (equals allowed)
var ok = Stream.of(new Person("Zoe", 30), new Person("Yuri", 25), new Person("Yara", 25))
.gather(throwIfNotDecreasingOrEqualBy(Person::age))
.toList();
// Violates due to increase by mapped key (25 -> 26)
assertThrows(IllegalStateException.class, () ->
Stream.of(new Person("Zoe", 30), new Person("Yuri", 25), new Person("Xena", 26))
.gather(throwIfNotDecreasingOrEqualBy(Person::age))
.toList());Apache-2.0
Supported by JetBrains IDEA open source license.