From 0c7ec7e848c67c228c0a436a394fc25de593c739 Mon Sep 17 00:00:00 2001 From: Stephen Colebourne Date: Tue, 16 Oct 2018 11:36:03 +0100 Subject: [PATCH] Enhance MapStream Add `toMapGrouping` that provides more control over the terminal group --- .../opengamma/strata/collect/MapStream.java | 71 +++++++++++++++++-- .../strata/collect/MapStreamTest.java | 19 +++++ 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/modules/collect/src/main/java/com/opengamma/strata/collect/MapStream.java b/modules/collect/src/main/java/com/opengamma/strata/collect/MapStream.java index c0608f66ef..154d9999f9 100644 --- a/modules/collect/src/main/java/com/opengamma/strata/collect/MapStream.java +++ b/modules/collect/src/main/java/com/opengamma/strata/collect/MapStream.java @@ -6,11 +6,17 @@ package com.opengamma.strata.collect; import static com.opengamma.strata.collect.Guavate.entry; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toList; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.Spliterator; import java.util.function.BiConsumer; @@ -216,11 +222,14 @@ private MapStream(Stream> underlying) { //------------------------------------------------------------------------- /** * Returns the keys as a stream, dropping the values. + *

+ * A {@link MapStream} may contain the same key more than once, so callers + * may need to call {@link Stream#distinct()} on the result. * * @return a stream of the keys */ public Stream keys() { - return underlying.map(e -> e.getKey()); + return underlying.map(Entry::getKey); } /** @@ -229,7 +238,7 @@ public Stream keys() { * @return a stream of the values */ public Stream values() { - return underlying.map(e -> e.getValue()); + return underlying.map(Entry::getValue); } //------------------------------------------------------------------------- @@ -491,6 +500,8 @@ public MapStream sortedValues(Comparator comparator) { //----------------------------------------------------------------------- /** * Finds the minimum entry in the stream by comparing the keys using the supplied comparator. + *

+ * This is a terminal operation. * * @param comparator a comparator of keys * @return the minimum entry @@ -501,6 +512,8 @@ public Optional> minKeys(Comparator comparator) { /** * Finds the minimum entry in the stream by comparing the values using the supplied comparator. + *

+ * This is a terminal operation. * * @param comparator a comparator of values * @return the minimum entry @@ -511,6 +524,8 @@ public Optional> minValues(Comparator comparator) { /** * Finds the maximum entry in the stream by comparing the keys using the supplied comparator. + *

+ * This is a terminal operation. * * @param comparator a comparator of keys * @return the maximum entry @@ -521,6 +536,8 @@ public Optional> maxKeys(Comparator comparator) { /** * Finds the maximum entry in the stream by comparing the values using the supplied comparator. + *

+ * This is a terminal operation. * * @param comparator a comparator of values * @return the maximum entry @@ -535,11 +552,14 @@ public Optional> maxValues(Comparator comparator) { *

* The keys must be unique or an exception will be thrown. * Duplicate keys can be handled using {@link #toMap(BiFunction)}. + *

+ * This is a terminal operation. * * @return an immutable map built from the entries in the stream + * @throws IllegalArgumentException if the same key occurs more than once */ public ImmutableMap toMap() { - return underlying.collect(Guavate.toImmutableMap(e -> e.getKey(), e -> e.getValue())); + return underlying.collect(Guavate.toImmutableMap(Entry::getKey, Entry::getValue)); } /** @@ -556,25 +576,66 @@ public ImmutableMap toMap() { * MapStream.concat(mapStreamA, mapStreamB).toMap((a,b) -> a); * *

+ *

+ * This is a terminal operation. * * @param mergeFn function used to merge values when the same key appears multiple times in the stream * @return an immutable map built from the entries in the stream */ public ImmutableMap toMap(BiFunction mergeFn) { - return underlying.collect(Guavate.toImmutableMap(e -> e.getKey(), e -> e.getValue(), mergeFn)); + return underlying.collect(Guavate.toImmutableMap(Entry::getKey, Entry::getValue, mergeFn)); + } + + //------------------------------------------------------------------------- + /** + * Returns an immutable map built from the entries in the stream, grouping by key. + *

+ * Entries are grouped based on the equality of the key. + *

+ * This is a terminal operation. + * + * @return an immutable map built from the entries in the stream + * @throws IllegalArgumentException if the same key occurs more than once + */ + public ImmutableMap> toMapGrouping() { + return toMapGrouping(toList()); } + /** + * Returns an immutable map built from the entries in the stream, grouping by key. + *

+ * Entries are grouped based on the equality of the key. + * The collector allows the values to be flexibly combined. + *

+ * This is a terminal operation. + * + * @param the internal collector type + * @param the type of the combined values + * @param valueCollector the collector used to combined the values + * @return a stream where the values have been grouped + */ + public ImmutableMap toMapGrouping(Collector valueCollector) { + return underlying.collect(collectingAndThen( + groupingBy(Entry::getKey, mapping(Entry::getValue, valueCollector)), + ImmutableMap::copyOf)); + } + + //------------------------------------------------------------------------- /** * Returns an immutable list multimap built from the entries in the stream. + *

+ * This is a terminal operation. * * @return an immutable list multimap built from the entries in the stream */ public ImmutableListMultimap toListMultimap() { - return underlying.collect(Guavate.toImmutableListMultimap(e -> e.getKey(), e -> e.getValue())); + return underlying.collect(Guavate.toImmutableListMultimap(Entry::getKey, Entry::getValue)); } /** * Performs an action for each entry in the stream, passing the key and value to the action. + *

+ * This is a terminal operation. * * @param action an action performed for each entry in the stream */ diff --git a/modules/collect/src/test/java/com/opengamma/strata/collect/MapStreamTest.java b/modules/collect/src/test/java/com/opengamma/strata/collect/MapStreamTest.java index 474a8664d4..6347aab624 100644 --- a/modules/collect/src/test/java/com/opengamma/strata/collect/MapStreamTest.java +++ b/modules/collect/src/test/java/com/opengamma/strata/collect/MapStreamTest.java @@ -9,6 +9,8 @@ import static com.opengamma.strata.collect.Guavate.pairsToImmutableMap; import static com.opengamma.strata.collect.Guavate.toImmutableList; import static com.opengamma.strata.collect.TestHelper.assertThrowsIllegalArg; +import static com.opengamma.strata.collect.TestHelper.list; +import static java.util.stream.Collectors.reducing; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; @@ -384,6 +386,23 @@ public void toMapWithMerge() { assertThat(result).isEqualTo(expected); } + //------------------------------------------------------------------------- + public void toMapGrouping() { + Map map = ImmutableMap.of("a", 1, "aa", 2, "b", 10, "bb", 20, "c", 1); + Map> expected = ImmutableMap.of("a", list(1, 2), "b", list(10, 20), "c", list(1)); + Map> result = MapStream.of(map).mapKeys(s -> s.substring(0, 1)).toMapGrouping(); + assertThat(result).isEqualTo(expected); + } + + public void toMapGroupingWithCollector() { + Map map = ImmutableMap.of("a", 1, "aa", 2, "b", 10, "bb", 20, "c", 1); + Map expected = ImmutableMap.of("a", 3, "b", 30, "c", 1); + Map result = MapStream.of(map).mapKeys(s -> s.substring(0, 1)) + .toMapGrouping(reducing(0, Integer::sum)); + assertThat(result).isEqualTo(expected); + } + + //------------------------------------------------------------------------- public void toListMultimap() { Map map = ImmutableMap.of("a", 1, "aa", 2, "b", 10, "bb", 20, "c", 1); ListMultimap expected = ImmutableListMultimap.of("a", 1, "a", 2, "b", 10, "b", 20, "c", 1);