Skip to content

Commit

Permalink
Release v0.5.0 (#41)
Browse files Browse the repository at this point in the history
+ Implement `reverse()`
+ Implement `maxBy(fn)` and `minBy(fn)`
+ Implement `exactSize(n)`
  • Loading branch information
tginsberg authored Oct 13, 2024
1 parent 4350621 commit 0416276
Show file tree
Hide file tree
Showing 12 changed files with 606 additions and 46 deletions.
21 changes: 16 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
name: Build and Test

on:
[ push ]
[ pull_request, push ]

jobs:
build:

runs-on: ubuntu-latest
permissions:
contents: read
packages: write

pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -22,10 +21,22 @@ jobs:
distribution: 'temurin'
cache: 'gradle'

- name: Build and Publish
run: ./gradlew build publish --no-daemon
- name: Build
run: ./gradlew build --no-daemon

- name: Publish
if: github.ref != 'refs/heads/main'
run: ./gradlew publish --no-daemon
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_TOKEN: ${{ secrets.SONATYPE_TOKEN }}
SONATYPE_SIGNING_KEY: ${{ secrets.SONATYPE_SIGNING_KEY }}
SONATYPE_SIGNING_PASSPHRASE: ${{ secrets.SONATYPE_SIGNING_PASSPHRASE }}

- name: Add coverage to PR
if: github.event_name == 'pull_request'
id: jacoco
uses: madrapps/[email protected]
with:
paths: ${{ github.workspace }}/**/build/reports/jacoco/test/jacocoTestReport.xml
token: ${{ secrets.GITHUB_TOKEN }}
33 changes: 0 additions & 33 deletions .github/workflows/coverage.yml

This file was deleted.

5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
### 0.5.0
+ Implement `reverse()`
+ Implement `maxBy(fn)` and `minBy(fn)`
+ Implement `exactSize(n)`
+
### 0.4.0
+ Implement `suffle()` and `shuffle(RandomGenerator)`
+ Implement `filterWithIndex()`
Expand Down
58 changes: 55 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Add the following dependency to `pom.xml`.
<dependency>
<groupId>com.ginsberg</groupId>
<artifactId>gatherers4j</artifactId>
<version>0.4.0</version>
<version>0.5.0</version>
</dependency>
```

Expand All @@ -24,7 +24,7 @@ Add the following dependency to `pom.xml`.
Add the following dependency to `build.gradle` or `build.gradle.kts`

```groovy
implementation("com.ginsberg:gatherers4j:0.4.0")
implementation("com.ginsberg:gatherers4j:0.5.0")
```

# Gatherers In This Library
Expand All @@ -37,9 +37,13 @@ implementation("com.ginsberg:gatherers4j:0.4.0")
| `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` |
| `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` |
| `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 |
Expand All @@ -54,7 +58,7 @@ implementation("com.ginsberg:gatherers4j:0.4.0")
| `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. |
| `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. |
Expand Down Expand Up @@ -135,6 +139,19 @@ Stream
// [Person("Todd", "Ginsberg"), Person("Emma", "Ginsberg")]
```

#### Ensure the stream is exactly `n` elements long

```java
// Good

Stream.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
Expand Down Expand Up @@ -167,6 +184,41 @@ Stream
// ["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
Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.4.0-SNAPSHOT
0.5.0
58 changes: 54 additions & 4 deletions src/main/java/com/ginsberg/gatherers4j/Gatherers4j.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,21 @@ public static <INPUT> ThrottlingGatherer<INPUT> debounce(final int amount, final
return new DistinctGatherer<>(function);
}

/**
* Ensure the input stream is exactly <code>size</code> elements long, and emit all elements
* if so. If not, throw an <code>IllegalStateException</code>.
*
* @param size Exact number of elements the stream must have
*/
public static <INPUT> SizeGatherer<INPUT> exactSize(final long size) {
return new SizeGatherer<>(size);
}

/**
* Filter a stream according to the given <code>predicate</code>, which takes both the item being examined, and its index.
*
* @param predicate A non-null <code>BiPredicate&lt;Long,INPUT&gt;</code> where the <code>Long</code> is the zero-based index of
* the element being filtered, and the <code>INPUT</code> is the element itself.
*
* @return A <code>FilterWithIndexGatherer</code>
*/
public static <INPUT> FilteringWithIndexGatherer<INPUT> filterWithIndex(
Expand All @@ -107,6 +116,46 @@ public static <INPUT> LastGatherer<INPUT> last(final int count) {
return new LastGatherer<>(count);
}

/**
* Return a Stream containing the single maximum value of the input stream, according to
* the given mapping function. In the case where a stream has more than one mapped value
* that is the maximum, the first one encountered makes up the stream. This does not
* evaluate null values or null mappings.
*
* @param function A mapping function, the results of which must implement <code>Comparable</code>
*/
public static <INPUT, MAPPED extends Comparable<MAPPED>> MinMaxGatherer<INPUT, MAPPED> maxBy(
final Function<INPUT, MAPPED> function
) {
mustNotBeNull(function, "Mapping function must not be null");
return new MinMaxGatherer<>(true, function);
}

/**
* Return a Stream containing the single minimum value of the input stream, according to
* the given mapping function. In the case where a stream has more than one mapped value
* that is the minimum, the first one encountered makes up the stream. This does not
* evaluate null values or null mappings.
*
* @param function A mapping function, the results of which must implement <code>Comparable</code>
*/
public static <INPUT, MAPPED extends Comparable<MAPPED>> MinMaxGatherer<INPUT, MAPPED> minBy(
final Function<INPUT, MAPPED> function
) {
mustNotBeNull(function, "Mapping function must not be null");
return new MinMaxGatherer<>(false, function);
}

/**
* Reverse the order of the input Stream.
* <p>
* Note: This consumes the entire stream and holds it in memory, so it will not work on
* infinite streams and may cause memory pressure on very large streams.
*/
public static <INPUT> ReversingGatherer<INPUT> reverse() {
return new ReversingGatherer<>();
}

/**
* Create a <code>Stream&lt;BigDecimal&gt;</code> that represents the running population standard deviation of a <code>Stream&lt;BigDecimal&gt;</code>.
*/
Expand Down Expand Up @@ -203,9 +252,10 @@ public static BigDecimalSimpleAverageGatherer<BigDecimal> simpleRunningAverage()
}

/**
* Shuffle the input stream into a random order. This consumes the entire stream and
* holds it in memory, so it will not work on infinite streams and may cause memory
* pressure on very large streams.
* Shuffle the input stream into a random order.
* <p>
* Note: This consumes the entire stream and holds it in memory, so it will not work on
* infinite streams and may cause memory pressure on very large streams.
*
* @return A non-null <code>ShufflingGatherer</code>ShufflingGatherer`
*/
Expand Down
74 changes: 74 additions & 0 deletions src/main/java/com/ginsberg/gatherers4j/MinMaxGatherer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2024 Todd Ginsberg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.ginsberg.gatherers4j;

import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Gatherer;

public class MinMaxGatherer<INPUT, MAPPED extends Comparable<MAPPED>>
implements Gatherer<INPUT, MinMaxGatherer.State<INPUT, MAPPED>, INPUT> {

private final Function<INPUT, MAPPED> mappingFunction;
private final boolean max;

MinMaxGatherer(final boolean max, final Function<INPUT, MAPPED> mappingFunction) {
this.mappingFunction = mappingFunction;
this.max = max;
}

@Override
public Supplier<State<INPUT, MAPPED>> initializer() {
return State::new;
}

@Override
public Integrator<State<INPUT, MAPPED>, INPUT, INPUT> integrator() {
return (state, element, downstream) -> {
final MAPPED mapped = element == null ? null : mappingFunction.apply(element);
if (mapped == null) {
return !downstream.isRejecting();
}
if (state.bestSoFar == null) {
state.bestSoFar = element;
state.mappedField = mapped;
} else {
final int compared = mapped.compareTo(state.mappedField);
if ((compared > 0 && max) || (compared < 0 && !max)) {
state.bestSoFar = element;
state.mappedField = mapped;
}
}
return !downstream.isRejecting();
};
}

@Override
public BiConsumer<State<INPUT, MAPPED>, Downstream<? super INPUT>> finisher() {
return (state, downstream) -> {
if (state.bestSoFar != null) {
downstream.push(state.bestSoFar);
}
};
}

public static class State<INPUT, MAPPED extends Comparable<MAPPED>> {
private INPUT bestSoFar;
private MAPPED mappedField;
}
}
Loading

0 comments on commit 0416276

Please sign in to comment.