Skip to content

Commit

Permalink
Introduce dedicated GroupingByGatherer
Browse files Browse the repository at this point in the history
  • Loading branch information
pivovarit committed Dec 17, 2024
1 parent 9790926 commit 44fb9c4
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ Provided `Gatherers`:
- creates a sliding window of a fixed size with a fixed step, extends `Gatherers.windowSliding(int)` by adding a step parameter
- `MoreGatherers.filteringByIndex(BiPredicate<Long, T>)`
- filters elements based on their index and value
- `MoreGatherers.groupingBy(Function<T, K>, Collector<T, ?, R>)`
- groups elements by a key extractor function and applies a custom collector

### Philosophy

Expand Down
40 changes: 40 additions & 0 deletions src/main/java/com/pivovarit/gatherers/GroupingByGatherer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.pivovarit.gatherers;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Gatherer;
import java.util.stream.Stream;

record GroupingByGatherer<T, K, R>(Function<? super T, ? extends K> classifier,
Collector<? super T, ?, ? extends R> collector)
implements Gatherer<T, HashMap<K, Stream.Builder<T>>, Map.Entry<K, R>> {

GroupingByGatherer {
Objects.requireNonNull(classifier, "classifier can't be null");
Objects.requireNonNull(collector, "collector can't be null");
}

@Override
public Supplier<HashMap<K, Stream.Builder<T>>> initializer() {
return HashMap::new;
}

@Override
public Integrator<HashMap<K, Stream.Builder<T>>, T, Map.Entry<K, R>> integrator() {
return Integrator.ofGreedy((state, element, _) -> {
state.computeIfAbsent(classifier.apply(element), _ -> Stream.builder()).accept(element);
return true;
});
}

@Override
public BiConsumer<HashMap<K, Stream.Builder<T>>, Downstream<? super Map.Entry<K, R>>> finisher() {
return (map, downstream) -> map.forEach((key, builder) -> downstream.push(Map.entry(key, builder.build()
.collect(collector))));
}
}
30 changes: 30 additions & 0 deletions src/main/java/com/pivovarit/gatherers/MoreGatherers.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Gatherer;
import java.util.stream.Stream;

Expand Down Expand Up @@ -263,4 +265,32 @@ private MoreGatherers() {
public static <T> Gatherer<T, ?, T> filteringByIndex(BiPredicate<Long, ? super T> predicate) {
return new FilterByIndexGatherer<>(predicate);
}

/**
* Creates a {@link Gatherer} that groups elements based on a key extracted by the given {@code classifier}.
*
* @param classifier the function used to extract the key for grouping elements
* @param collector the {@link Collector} used to accumulate the elements of each group
* @param <T> the type of the input elements
* @param <K> the type of the key extracted from the input elements
* @param <R> the type of the result of the collector
*
* @return a {@link Gatherer} that groups elements based on the extracted key
*/
public static <T, K, R> Gatherer<T, ?, Map.Entry<K, R>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, ?, ? extends R> collector) {
return new GroupingByGatherer<>(classifier, collector);
}

/**
* Creates a {@link Gatherer} that groups elements based on a key extracted by the given {@code classifier}.
*
* @param classifier the function used to extract the key for grouping elements
* @param <T> the type of the input elements
* @param <K> the type of the key extracted from the input elements
*
* @return a {@link Gatherer} that groups elements based on the extracted key
*/
public static <T, K> Gatherer<T, ?, Map.Entry<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier) {
return groupingBy(classifier, Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.pivovarit.gatherers.blackbox;

import com.pivovarit.gatherers.MoreGatherers;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class GroupingByGathererTest {

@Test
void shouldRejectNullClassifier() {
assertThatThrownBy(() -> MoreGatherers.groupingBy(null))
.isInstanceOf(NullPointerException.class)
.hasMessageContaining("classifier");
}

@Test
void shouldRejectNullCollector() {
assertThatThrownBy(() -> MoreGatherers.groupingBy(i -> i, null))
.isInstanceOf(NullPointerException.class)
.hasMessageContaining("collector");
}

@Test
void shouldGroupEmpty() {
assertThat(List.<String>of().stream().gather(MoreGatherers.groupingBy(i -> i))).isEmpty();
}

@Test
void shouldGroupEmptyWithCustomCollector() {
assertThat(List.<String>of().stream().gather(MoreGatherers.groupingBy(i -> i, Collectors.toSet()))).isEmpty();
}

@Test
void shouldGroupToList() {
List<Map.Entry<Integer, List<String>>> results = Stream.of("a", "bb", "cc", "ddd", "ee", "fff")
.gather(MoreGatherers.groupingBy(String::length))
.toList();

assertThat(results)
.hasSize(3)
.containsExactlyInAnyOrder(
Map.entry(1, List.of("a")),
Map.entry(2, List.of("bb", "cc", "ee")),
Map.entry(3, List.of("ddd", "fff"))
);
}

@Test
void shouldGroupUsingCustomCollector() {
List<Map.Entry<Integer, Set<String>>> results = Stream.of("a", "bb", "cc", "ddd", "ee", "fff")
.gather(MoreGatherers.groupingBy(String::length, Collectors.toSet()))
.toList();

assertThat(results)
.hasSize(3)
.containsExactlyInAnyOrder(
Map.entry(1, Set.of("a")),
Map.entry(2, Set.of("bb", "cc", "ee")),
Map.entry(3, Set.of("ddd", "fff"))
);
}
}

0 comments on commit 44fb9c4

Please sign in to comment.