Skip to content

Commit

Permalink
Enhance MapStream
Browse files Browse the repository at this point in the history
Add `toMapGrouping` that provides more control over the terminal group
  • Loading branch information
jodastephen committed Oct 16, 2018
1 parent 9883765 commit 0c7ec7e
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 5 deletions.
Expand Up @@ -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;
Expand Down Expand Up @@ -216,11 +222,14 @@ private MapStream(Stream<Map.Entry<K, V>> underlying) {
//-------------------------------------------------------------------------
/**
* Returns the keys as a stream, dropping the values.
* <p>
* 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<K> keys() {
return underlying.map(e -> e.getKey());
return underlying.map(Entry::getKey);
}

/**
Expand All @@ -229,7 +238,7 @@ public Stream<K> keys() {
* @return a stream of the values
*/
public Stream<V> values() {
return underlying.map(e -> e.getValue());
return underlying.map(Entry::getValue);
}

//-------------------------------------------------------------------------
Expand Down Expand Up @@ -491,6 +500,8 @@ public MapStream<K, V> sortedValues(Comparator<? super V> comparator) {
//-----------------------------------------------------------------------
/**
* Finds the minimum entry in the stream by comparing the keys using the supplied comparator.
* <p>
* This is a terminal operation.
*
* @param comparator a comparator of keys
* @return the minimum entry
Expand All @@ -501,6 +512,8 @@ public Optional<Map.Entry<K, V>> minKeys(Comparator<? super K> comparator) {

/**
* Finds the minimum entry in the stream by comparing the values using the supplied comparator.
* <p>
* This is a terminal operation.
*
* @param comparator a comparator of values
* @return the minimum entry
Expand All @@ -511,6 +524,8 @@ public Optional<Map.Entry<K, V>> minValues(Comparator<? super V> comparator) {

/**
* Finds the maximum entry in the stream by comparing the keys using the supplied comparator.
* <p>
* This is a terminal operation.
*
* @param comparator a comparator of keys
* @return the maximum entry
Expand All @@ -521,6 +536,8 @@ public Optional<Map.Entry<K, V>> maxKeys(Comparator<? super K> comparator) {

/**
* Finds the maximum entry in the stream by comparing the values using the supplied comparator.
* <p>
* This is a terminal operation.
*
* @param comparator a comparator of values
* @return the maximum entry
Expand All @@ -535,11 +552,14 @@ public Optional<Map.Entry<K, V>> maxValues(Comparator<? super V> comparator) {
* <p>
* The keys must be unique or an exception will be thrown.
* Duplicate keys can be handled using {@link #toMap(BiFunction)}.
* <p>
* 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<K, V> toMap() {
return underlying.collect(Guavate.toImmutableMap(e -> e.getKey(), e -> e.getValue()));
return underlying.collect(Guavate.toImmutableMap(Entry::getKey, Entry::getValue));
}

/**
Expand All @@ -556,25 +576,66 @@ public ImmutableMap<K, V> toMap() {
* MapStream.concat(mapStreamA, mapStreamB).toMap((a,b) -> a);
* </pre>
* </p>
* <p>
* 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<K, V> toMap(BiFunction<? super V, ? super V, ? extends V> 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.
* <p>
* Entries are grouped based on the equality of the key.
* <p>
* 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<K, List<V>> toMapGrouping() {
return toMapGrouping(toList());
}

/**
* Returns an immutable map built from the entries in the stream, grouping by key.
* <p>
* Entries are grouped based on the equality of the key.
* The collector allows the values to be flexibly combined.
* <p>
* This is a terminal operation.
*
* @param <A> the internal collector type
* @param <R> 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 <A, R> ImmutableMap<K, R> toMapGrouping(Collector<? super V, A, R> 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.
* <p>
* This is a terminal operation.
*
* @return an immutable list multimap built from the entries in the stream
*/
public ImmutableListMultimap<K, V> 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.
* <p>
* This is a terminal operation.
*
* @param action an action performed for each entry in the stream
*/
Expand Down
Expand Up @@ -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;

Expand Down Expand Up @@ -384,6 +386,23 @@ public void toMapWithMerge() {
assertThat(result).isEqualTo(expected);
}

//-------------------------------------------------------------------------
public void toMapGrouping() {
Map<String, Integer> map = ImmutableMap.of("a", 1, "aa", 2, "b", 10, "bb", 20, "c", 1);
Map<String, List<Integer>> expected = ImmutableMap.of("a", list(1, 2), "b", list(10, 20), "c", list(1));
Map<String, List<Integer>> result = MapStream.of(map).mapKeys(s -> s.substring(0, 1)).toMapGrouping();
assertThat(result).isEqualTo(expected);
}

public void toMapGroupingWithCollector() {
Map<String, Integer> map = ImmutableMap.of("a", 1, "aa", 2, "b", 10, "bb", 20, "c", 1);
Map<String, Integer> expected = ImmutableMap.of("a", 3, "b", 30, "c", 1);
Map<String, Integer> result = MapStream.of(map).mapKeys(s -> s.substring(0, 1))
.toMapGrouping(reducing(0, Integer::sum));
assertThat(result).isEqualTo(expected);
}

//-------------------------------------------------------------------------
public void toListMultimap() {
Map<String, Integer> map = ImmutableMap.of("a", 1, "aa", 2, "b", 10, "bb", 20, "c", 1);
ListMultimap<String, Integer> expected = ImmutableListMultimap.of("a", 1, "a", 2, "b", 10, "b", 20, "c", 1);
Expand Down

0 comments on commit 0c7ec7e

Please sign in to comment.