Packrat is a Java library that provides various Gatherer implementations for the Stream API. Gatherers can enhance streams with custom intermediate operations.
Introduction to the Gatherers by Viktor Klang
Important
You will need a very fresh JDK version with preview features enabled to actually use Gatherers.
JEP | JDK | Status |
---|---|---|
461 | 22 | Preview |
473 | 23 | Second Preview |
485 | 24 | Final |
Build scripts expect to run on JDK version not lower than 24.
<dependency>
<groupId>io.github.jhspetersson</groupId>
<artifactId>packrat</artifactId>
<version>0.1.0</version>
</dependency>
implementation("io.github.jhspetersson:packrat:0.1.0")
Name | Description |
---|---|
distinctBy | Distinct values with custom mapper |
filterBy | Filter with custom mapper and (optionally) predicate |
minBy | The smallest element compared after mapping applied |
maxBy | The greatest element compared after mapping applied |
removeBy | Remove with custom mapper and (optionally) predicate |
removeDuplicates | Removes consecutive duplicates from a stream |
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 |
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 |
reverse | All elements in reverse order |
rotate | All elements rotated left or right |
shuffle | All elements in random order |
sample | Sample of the specified size |
nth | Takes nth element from the stream |
dropNth | Drops every nth element from the stream |
last | Last n elements |
lastUnique | Last n unique elements |
dropLast | Drops last n elements |
chars | String split by Unicode graphemes |
words | String split by words |
sentences | String split by sentences |
nCopies | Copies every element n times |
atLeast | Distinct values that appear at least n times |
atMost | Distinct values that appear at most n times |
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. |
flatMapIf | Optional flatMap depending on predicate |
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 |
asGatherer | Converts Collector into Gatherer |
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]
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.
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]]
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]]
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
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]]
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
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]]
equalChunks(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]]
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.
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.
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.
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?]
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]
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.
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]
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]
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]
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]]
Apache-2.0
Supported by JetBrains IDEA open source license.