Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/tginsberg/gatherers4j
A library of useful Stream Gatherers (custom intermediate operations) for Java.
https://github.com/tginsberg/gatherers4j
gatherer gatherers java java-23 java23
Last synced: 2 days ago
JSON representation
A library of useful Stream Gatherers (custom intermediate operations) for Java.
- Host: GitHub
- URL: https://github.com/tginsberg/gatherers4j
- Owner: tginsberg
- License: apache-2.0
- Created: 2024-05-09T19:22:52.000Z (8 months ago)
- Default Branch: main
- Last Pushed: 2024-12-27T13:56:34.000Z (3 days ago)
- Last Synced: 2024-12-27T14:36:38.462Z (3 days ago)
- Topics: gatherer, gatherers, java, java-23, java23
- Language: Java
- Homepage:
- Size: 232 KB
- Stars: 24
- Watchers: 4
- Forks: 1
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Gatherers4j
A library of useful [Stream Gatherers](https://openjdk.org/jeps/473) (custom intermediate operations) for Java 23+.
# Installing
To use this library, add it as a dependency to your build. This library has no additional dependencies.
**Maven**
Add the following dependency to `pom.xml`.
```xml
com.ginsberg
gatherers4j
0.6.0```
**Gradle**
Add the following dependency to `build.gradle` or `build.gradle.kts`
```groovy
implementation("com.ginsberg:gatherers4j:0.6.0")
```# Gatherers In This Library
### Streams
| Function | Purpose |
|------------------------------|--------------------------------------------------------------------------------------------------------------------------------|
| `debounce(amount, duration)` | Limit stream elements to `amount` elements over `duration`, dropping any elements over the limit until a new `duration` starts |
| `dedupeConsecutive()` | Remove consecutive duplicates from a stream |
| `dedupeConsecutiveBy(fn)` | Remove consecutive duplicates from a stream as returned by `fn` |
| `distinctBy(fn)` | Emit only distinct elements from the stream, as measured by `fn` |
| `dropLast(n)` | Keep all but the last `n` elements of the stream |
| `exactSize(n)` | Ensure the stream is exactly `n` elements long, or throw an `IllegalStateException` |
| `filterWithIndex(predicate)` | Filter the stream with the given `predicate`, which takes an `element` and its `index` |
| `grouping()` | Group consecute identical elements into lists |
| `groupingBy(fn)` | Group consecutive elements that are identical according to `fn` into lists |
| `interleave(iterable)` | Creates a stream of alternating objects from the input stream and the argument iterable |
| `interleave(iterator)` | Creates a stream of alternating objects from the input stream and the argument iterator |
| `interleave(stream)` | Creates a stream of alternating objects from the input stream and the argument stream |
| `last(n)` | Constrain the stream to the last `n` values |
| `maxBy(fn)` | Return a stream containing a single element, which is the maximum value returned by the mapping function `fn` |
| `minBy(fn)` | Return a stream containing a single element, which is the minimum value returned by the mapping function `fn` |
| `reverse()` | Reverse the order of the stream |
| `shuffle()` | Shuffle the stream into a random order using the platform default `RandomGenerator` |
| `shuffle(rg)` | Shuffle the stream into a random order using the specified `RandomGenerator` |
| `throttle(amount, duration)` | Limit stream elements to `amount` elements over `duration`, pausing until a new `duration` period starts |
| `withIndex()` | Maps all elements of the stream as-is along with their 0-based index |
| `zipWith(iterable)` | Creates a stream of `Pair` objects whose values come from the input stream and argument iterable |
| `zipWith(iterator)` | Creates a stream of `Pair` objects whose values come from the input stream and argument iterator |
| `zipWith(stream)` | Creates a stream of `Pair` objects whose values come from the input stream and argument stream |
| `zipWithNext()` | Creates a stream of `List` objects via a sliding window of width 2 and stepping 1 |### Mathematics/Statistics
| Function | Purpose |
|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|
| `runningPopulationStandardDeviation()` | Create a stream of `BigDecimal` objects representing the running population standard deviation. |
| `runningPopulationStandardDeviationBy(fn)` | Create a stream of `BigDecimal` objects as mapped from the input via `fn`, representing the running population standard deviation. |
| `runningProduct()` | Create a stream of `BigDecimal` objects representing the running product. | |
| `runningProductBy(fn)` | Create a stream of `BigDecimal` objects as mapped from the input via `fn`, representing the running product. |
| `runningSampleStandardDeviation()` | Create a stream of `BigDecimal` objects representing the running sample standard deviation. |
| `runningSampleStandardDeviationBy(fn)` | Create a stream of `BigDecimal` objects as mapped from the input via `fn`, representing the running sample standard deviation. |
| `runningSum()` | Create a stream of `BigDecimal` objects representing the running sum. |
| `runningSumBy(fn)` | Create a stream of `BigDecimal` objects as mapped from the input via `fn`, representing the running sum. |
| `simpleMovingAverage(window)` | Create a moving average of `BigDecimal` values over the previous `window` values. See below for options. |
| `simpleMovingAverageBy(fn, window)` | Create a moving average of `BigDecimal` values over the previous `window` values, as mapped via `fn`. |
| `simpleRunningAverage()` | Create a running average of `BigDecimal` values. See below for options. |
| `simpleRunningAverageBy(fn)` | Create a running average of `BigDecimal` values as mapped via `fn`. |# Use Cases
#### Running average of `Stream`
```java
Stream
.of("1.0", "2.0", "10.0")
.map(BigDecimal::new)
.gather(Gatherers4j.simpleRunningAverage())
.toList();// [1, 1.5, 4.3333333333333333]
```#### Moving average of `Stream`
```java
Stream
.of("1.0", "2.0", "10.0", "20.0", "30.0")
.map(BigDecimal::new)
.gather(Gatherers4j.simpleMovingAverage(2))
.toList();// [1.5, 6, 15, 25]
```#### Remove consecutive duplicate elements
```java
Stream
.of("A", "A", "A", "B", "B", "C", "C", "D", "A", "B", "C")
.gather(Gatherers4j.dedupeConsecutive())
.toList();// ["A", "B", "C", "D", "A", "B", "C"]
```#### Remove consecutive duplicate elements, where duplicate is measured by a function
```java
record Person(String firstName, String lastName) {}Stream
.of(
new Person("Todd", "Ginsberg"),
new Person("Emma", "Ginsberg"),
new Person("Todd", "Smith")
)
.gather(Gatherers4j.dedupeConsecutiveBy(Person::lastName))
.toList();// [Person("Todd", "Ginsberg"), Person("Todd", "Smith")]
```#### Remove duplicate elements, where duplicate is measured by a function
```java
record Person(String firstName, String lastName) {}Stream
.of(
new Person("Todd", "Ginsberg"),
new Person("Emma", "Ginsberg"),
new Person("Todd", "Smith")
)
.gather(Gatherers4j.distinctBy(Person::firstName))
.toList();// [Person("Todd", "Ginsberg"), Person("Emma", "Ginsberg")]
```#### Keep all but the last `n` elements
```java
Stream.of("A", "B", "C", "D", "E")
.gather(Gatherers4j.dropLast(2))
.toList();// ["A", "B", "C"]
```#### Ensure the stream is exactly `n` elements long
```java
// GoodStream.of("A", "B", "C").gather(Gatherers4j.exactSize(3)).toList();
// ["A", "B", "C"]// Bad
Stream.of("A").gather(Gatherers4j.exactSize(3)).toList();
// IllegalStateException
```#### Filter a stream, knowing the index of each element
```java
Stream.of("A", "B", "C", "D")
.gather(Gatherers4j.filterWithIndex((index, element) -> index % 2 == 0 || element.equals("D")))
.toList();// ["A", "C", "D"]
```### Group identical elements
```java
Stream.of("A", "A", "B", "B", "B", "C")
.gather(Gatherers4j.grouping())
.toList();// [["A", "A"], ["B", "B", "B"], ["C"]]
```### Group identical elements as measured by a function
```java
Stream.of("A", "B", "AA", "BB", "CC", "DDD")
.gather(Gatherers4j.groupingBy(String::length))
.toList();// [["A", "B"], ["AA", "BB", "CC"], ["DDD"]]
```#### Interleave streams of the same type into one stream
```java
final Stream left = Stream.of("A", "B", "C");
final Stream right = Stream.of("D", "E", "F");left.gather(Gatherers4j.interleave(right)).toList();
// ["A", "D", "B", "E", "C", "F"]
```#### Limit the stream to the `last` _n_ elements
```java
Stream
.of("A", "B", "C", "D", "E", "F", "G")
.gather(Gatherers4j.last(3))
.toList();// ["E", "F", "G"]
```#### Find the object with the maximum mapped value
```java
record Employee(String name, int salary) {}streamOfEmployees
.gather(Gatherers4j.maxBy(Employee:salary))
.toList();// Employee("Big Shot", 1_000_000)
```#### Find the object with the minimum mapped value
```java
record Person(String name, int age) {}streamOfPeople
.gather(Gatherers4j.minBy(Person:age))
.toList();// Person("Baby", 1)
```#### Reverse the order of the stream
```java
Stream
.of("A", "B", "C")
.gather(Gatherers4j.reverse())
.toList();// ["C", "B", "A"]
```#### Include index with original stream values
```java
Stream
.of("A", "B", "C")
.gather(Gatherers4j.withIndex())
.toList();// [IndexedValue(0, "A"), IndexedValue(1, "B"), IndexedValue(2, "C")]
```#### Shuffle the stream into a random order
```java
Stream
.of("A", "B", "C", "D" ,"E")
.gather(Gatherers4j.shuffle())
.toList();// ex: ["B", "E", "A", "C", "D"] -- or some other randomly arranged stream
```#### Shuffle the stream into a random order, with a specific `RandomGenerator`
```java
Stream
.of("A", "B", "C", "D" ,"E")
.gather(Gatherers4j.shuffle(RandomGenerator.of("someGenerator")))
.toList();// ex: ["B", "E", "A", "C", "D"] -- or some other randomly arranged stream
```#### Throttle the number of elements consumed in a period
```java
Stream
.of("A", "B", "C")
.gather(Gatherers4j.throttle(2, Duration.ofSeconds(1))) // Two per second
.toList();// ["A", "B", "C"]
^
|
+----------- Pause
```#### Zip two streams of together into a `Stream`
The left and right streams can be of different types.
```java
final Stream left = Stream.of("A", "B", "C");
final Stream right = Stream.of(1, 2, 3);left.gather(Gatherers4j.zip(right)).toList();
// [Pair("A", 1), Pair("B", 2), Pair("C", 3)]
```
#### Zip elements of a stream together
This converts a `Stream` to a `Stream>`
```java
Stream
.of("A", "B", "C", "D", "E")
.gather(Gatherers4j.zipWitNext())
.toList();// [["A", "B"], ["B", "C"], ["C", "D"], ["D", "E"]]
```## Streams of `BigDecimal`
Functions which modify output and are available on all `BigDecimal` gatherers (simple average, moving average, and standard deviation).
| Function | Purpose |
|--------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|
| `treatNullAsZero()` | When an element in the `Stream` is `null` treat it as `BigDecimal.ZERO` instead of skipping it in the calculation. |
| `treatNullAs(BigDecimal)` | When an element in the `Stream` is `null` treat it as the `BigDecimal` value given instead of skipping it in the calculation. |
| `withMathContext(MathContext)` | Switch the `MathContext` for all calculations to the non-null `MathContext` given. The default is `MathContext.DECIMAL64`. |
| `withOriginal()` | Include the original stream value in addition to the calculated value. |Note that rounding mode, precision, and scale are derived from the `MathContext`.
### Example of `simpleRunningAverage()`
This example creates a stream of `double`, converts each value to a `BigDecmial`, and takes a `simpleMovingAverage` over 10 trailing values.
It will `includePartialValues` and sets the `MathContext` to the values given. Additionally, nulls
are treated as zeros, and the calculated average is returned along with the original value.```java
someStreamOfBigDecimal()
.gather(Gatherers4j
.simpleMovingAverage(10)
.includePartialValues()
.withMathContext(MathContext.DECIMAL32)
.treatNullAsZero()
.withOriginal()
)
.toList();// Example output:
[
WithOriginal[original=0.8462487, calculated=0.8462487],
WithOriginal[original=0.8923297, calculated=0.8692890],
WithOriginal[original=0.2556937, calculated=0.6647573],
WithOriginal[original=0.2901778, calculated=0.5711125],
WithOriginal[original=0.4945578, calculated=0.5558016],
WithOriginal[original=0.3173066, calculated=0.5160525],
WithOriginal[original=0.6377766, calculated=0.5334417],
WithOriginal[original=0.1729199, calculated=0.4883765],
WithOriginal[original=0.7408201, calculated=0.5164258],
WithOriginal[original=0.7169926, calculated=0.5364825],
WithOriginal[original=0.5174489, calculated=0.5036025],
WithOriginal[original=0.5895662, calculated=0.4733262],
WithOriginal[original=0.4458275, calculated=0.4923396],
// etc...
]
```# Project Philosophy
1. Consider adding a gatherer if it cannot be implemented with `map`, `filter`, or a collector without enclosing outside state.
2. Resist the temptation to add functions that only exist to provide an alias. They seem fun/handy but add surface area to the API and must be maintained forever.
3. All features should be documented and tested.# Contributing
Please feel free to file issues for change requests or bugs. If you would like to contribute new functionality, please contact me before starting work!
Copyright © 2024 by Todd Ginsberg