Java8 Stream List<Map<String,Object>> groupingBy and counting value - java

i want get result like { "key1" : 4 ,"key2" :2 }
i known i can use map and groupby and such as
list.stream()
.map(map -> map2Entity(map))
.collect(Collectors.groupingBy(Entity::getKey,Collectors.summarizingInt(Entity::getCnt)) )
This is my code and how implements (todo) code
public void test() {
List<Map<String, Object>> list = Arrays.asList(
createNewMap("key1", 1),
createNewMap("key2", 2),
createNewMap("key1", 3)
);
// i want get result like {"key1":4,"key2":2}
// how can i get the result don't use map()
list.stream()
.collect(Collectors.groupingBy(this::getKey),....(todo));
}
private String getKey(Map<String,Object> map){
return (String) map.get("key");
}
private Map<String, Object> createNewMap(String key, Integer val) {
Map<String, Object> map = new HashMap<>();
map.put("key", key);
map.put(key, val);
return map;
}

You have to use the flatMap operator along with the groupingBy collector. Here's how it looks.
Map<String, Integer> keyToSumValuesMap = list.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.summingInt(Map.Entry::getValue)));
Moreover do not use Object type to represent integers since that is not type safe. Consider declaring method level generics to overcome that issue. Here's how it looks.
private static <S, T> Map<S, T> createNewMap(S key, T val) {
Map<S, T> map = new HashMap<>();
map.put(key, val);
return map;
}
And the output now looks like this:
{key1=4, key2=2}

Related

How to filter a HashMap with a value Predicate?

I have the following query header method:
public Map<String, List<String>> query(Predicate<String> valuePredicate)
Before this, I implementated another method with a specific column (label). It was:
public Map<String, List<String>> query(String keySelector,Predicate<String> valuePredicate) {
try {
final List<String> row = frameInfo.get(keySelector);
List<Integer> indices = IntStream.range(0, row.size()).filter(columnIndex -> valuePredicate.test(row.get(columnIndex))).boxed().collect(Collectors.toList());
Map<String, List<String>> auxMap = new HashMap<>();
for (Map.Entry<String, List<String>> entry : frameInfo.entrySet()) {
for (int columnIndex : indices) {
auxMap.putIfAbsent(entry.getKey(), new ArrayList<>());
auxMap.get(entry.getKey()).add(entry.getValue().get(columnIndex));
}
}
return auxMap;
}catch (Exception e){
return null;
}
How could I implementate the new method with just 1 argument (valuePredicate)?
It seems to me that you could do it like so. Since the predicate tests a string from a list which can be streamed, I don't see why you need to iterate the indices.
Stream the entrySet from frameInfo
then flatmap e.getValue() (a list) and apply the predicate
preserve the key and filtered value in a String array
then group based on the key
public Map<String, List<String>> queryAll(Predicate<String> valuePredicate) {
return frameInfo.entrySet().stream()
.flatMap(e -> e.getValue().stream()
.filter(valuePredicate)
.map(s -> new String[] { e.getKey(), s }))
.collect(Collectors.groupingBy(arr -> arr[0],
Collectors.mapping(arr -> arr[1],
Collectors.toList())));
}
I'm tossing this one in as well, it's a rewrite of your existing method.
it simply streams the list for the supplied key, applies the filter and populates the map. Since there is only one key, you could just return a list.
public Map<String, List<String>> query(String keySelector,
Predicate<String> valuePredicate) {
return frameInfo.get(keySelector).stream()
.filter(valuePredicate)
.collect(Collectors.groupingBy(a -> keySelector));
}
If I misunderstood something, let me know and I will try to correct it.

Map of maps to a single map by aggregating keys

I have a structure like this one:
Map<KeyType1, Map<KeyType2, List<ValueType>>>
And also a class holding both KeyType1 and KeyType2, let's call it AggregatedKey. It can be instantiated using its constructor:
public AggregatedKey(KeyType1 keyType1, KeyType2 keyType2)
My goal is to map the structure above to something like:
Map<AggregatedKey, List<ValueType>>
So, basically, the keys should be mapped to a single aggregated key.
How can I achieve that using Java 9?
This will do the trick
Map<KeyType1, Map<KeyType2, List<String>>> m = new HashMap<>();
Map<AggregatedKey, List<String>> result = new HashMap<>();
m.entrySet().forEach(entry -> {
entry.getValue().entrySet().forEach(nestedEntry -> {
result.put(new AggregatedKey(entry.getKey(), nestedEntry.getKey()), nestedEntry.getValue());
});
});
Don't forget to implement hashcode/equals in your AggregatedKey, otherwise you'll have some trouble using the result map.
You can do it like so using streams.
first stream the entry set of the outer map
then invoke flatMap to stream the inner map's entrySet
create the AggregatedKey instance using outerEntry.getKey() and innerEntry.getKey() Note this requires that class to have a constructor accepting the keys.
then put that instance and the value from the inner map (List<ValueType>) in an AbstractMap.SimpleEntry instance to pass to the collector.
create the new map with the key and value of the SimpleEntry
Given the following source map.
Map<KeyType1, Map<KeyType2, List<ValueType>>> map =
new HashMap<>(); // contains the info to be remapped.
Here is the result
Map<AggregatedKey, List<ValueType>> result = map.entrySet()
.stream()
.flatMap(outerEntry-> outerEntry
.getValue().entrySet().stream()
.map(innerEntry -> new AbstractMap.SimpleEntry<>(
new AggregatedKey(outerEntry.getKey(),innerEntry.getKey()),
innerEntry.getValue())))
.collect(Collectors.toMap(
AbstractMap.SimpleEntry::getKey,
AbstractMap.SimpleEntry::getValue));
}
This is one of the ways:
public static void main(String[] args) {
Map<String, Map<String, String>> map = new HashMap<>();
Map<String, String> innerMap1 = new HashMap<>();
Map<String, String> innerMap2 = new HashMap<>();
innerMap1.put("k11", "v11");
innerMap1.put("k12", "v12");
innerMap1.put("k13", "v13");
innerMap2.put("k21", "v22");
innerMap2.put("k22", "v22");
map.put("k1", innerMap1);
map.put("k2", innerMap2);
Map<String, String> result = map
.entrySet()
.stream()
.flatMap(stringMapEntry ->
stringMapEntry
.getValue()
.entrySet()
.stream()
.map(stringStringEntry ->
new AbstractMap.SimpleEntry<String, String>(
buildAggregatedKey(
stringMapEntry.getKey(),
stringStringEntry.getKey()
),
stringStringEntry.getValue()
)
)
).collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
System.out.println(result);
}
private static String buildAggregatedKey(String key1, String key2){
return key1 + "_" + key2;
}
Where you change this buildAggregatedKey to meet your aggregation logic.
this is a sample test using streams where in the first step it transforms the inner element of the map and in the second it collects to a Map:
package prove.aggregatemap;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class TestAggregator {
#Test
public void aggregate() {
Map<String, List<String>> letter_map= Map.of("first", List.of("one","two","three"),
"second", List.of("four","five","six"),
"third", List.of("seven","eight","nine"));
Map<String, List<String>> num_map= Map.of("first_num", List.of("1","2","3"), "second_num", List.of("4","5","6"), "third_num", List.of("7","8","9"));
Map<String,Map<String,List<String>>> mapOfMaps=Map.of("letter",letter_map,"num",num_map);
Map<AggregateKey, List<String>> result=mapOfMaps.entrySet().stream().flatMap(entry ->
entry.getValue().entrySet().stream().collect(Collectors.toMap(
inner_entry -> new AggregateKey(entry.getKey(), inner_entry.getKey()),
inner_entry -> inner_entry.getValue())).entrySet().stream()
).collect(Collectors.toMap(entry->entry.getKey(),entry->entry.getValue()));
Assert.assertEquals(List.of("one","two","three"),result.get(new AggregateKey("letter","first")));
Assert.assertEquals(List.of("four","five","six"),result.get(new AggregateKey("letter","second")));
Assert.assertEquals(List.of("seven","eight","nine"),result.get(new AggregateKey("letter","third")));
}
}
public static final class AggregatedKey<K1, K2> {
private final K1 one;
private final K2 two;
public AggregatedKey(K1 one, K2 two) {
this.one = one;
this.two = two;
}
}
public static <K1, K2, V> Map<AggregatedKey<K1, K2>, List<V>> convert1(Map<K1, Map<K2, List<V>>> map) {
Map<AggregatedKey<K1, K2>, List<V>> res = new HashMap<>();
for (Map.Entry<K1, Map<K2, List<V>>> one : map.entrySet())
for (Map.Entry<K2, List<V>> two : one.getValue().entrySet())
res.put(new AggregatedKey<>(one.getKey(), two.getKey()), two.getValue());
return res;
}
public static <K1, K2, V> Map<AggregatedKey<K1, K2>, List<V>> convert2(Map<K1, Map<K2, List<V>>> map) {
return map.entrySet().stream()
.flatMap(e1 -> e1.getValue().entrySet().stream()
.map(e2 -> new AggregatedKey<>(new AggregatedKey<>(e1.getKey(), e2.getKey()), e2.getValue())))
.collect(Collectors.toMap(tuple -> tuple.one, tuple -> tuple.two));
}

Efficient way to iterate and copy the values of HashMap

I want to convert:
Map<String, Map<String, List<Map<String, String>>>> inputMap
to:
Map<String, Map<String, CustomObject>> customMap
inputMap is provided in the config and is ready but I need to customMap Format. CustomObject will be derived from List<Map<String, String>> using few lines of code in a function.
I have tried a normal way of iterating input map and copying key values in customMap. Is there any efficient way of doing that using Java 8 or some other shortcut?
Map<String, Map<String, List<Map<String, String>>>> configuredMap = new HashMap<>();
Map<String, Map<String, CustomObj>> finalMap = new HashMap<>();
for (Map.Entry<String, Map<String, List<Map<String, String>>>> attributeEntry : configuredMap.entrySet()) {
Map<String, CustomObj> innerMap = new HashMap<>();
for (Map.Entry<String, List<Map<String, String>>> valueEntry : attributeEntry.getValue().entrySet()) {
innerMap.put(valueEntry.getKey(), getCustomeObj(valueEntry.getValue()));
}
finalMap.put(attributeEntry.getKey(), innerMap);
}
private CustomObj getCustomeObj(List<Map<String, String>> list) {
return new CustomObj();
}
One solution is to stream the entrySet of inputMap, and then use Collectors#toMap twice (once for the outer Map, and once for the inner Map):
Map<String, Map<String, CustomObj>> customMap = inputMap.entrySet()
.stream()
.collect(Collectors.toMap(Function.identity(), entry -> {
return entry.getValue()
.entrySet()
.stream()
.collect(Collectors.toMap(Function.identity(),
entry -> getCustomeObj(entry.getValue())));
}));
You could stream, but that ain't going to look readable; at least to me. So if you have a method:
static CustomObject fun(List<Map<String, String>> in) {
return .... // whatever processing you have here
}
you could still use the java-8 syntax, but in a different form:
Map<String, Map<String, CustomObject>> customMap = new HashMap<>();
inputMap.forEach((key, value) -> {
value.forEach((innerKey, listOfMaps) -> {
Map<String, CustomObject> innerMap = new HashMap<>();
innerMap.put(innerKey, fun(listOfMaps));
customMap.put(key, innerMap);
});
});
If you can make the inner map immutable, you could make that even shorter:
inputMap.forEach((key, value) -> {
value.forEach((innerKey, listOfMaps) -> {
customMap.put(key, Collections.singletonMap(innerKey, fun(listOfMaps)));
});
});
IMHO streaming is not so bad idea. There're no bad tools. It depends on how you're using them.
In this particular case I would extract the repeating pattern into an utility method:
public static <K, V1, V2> Map<K, V2> transformValues(Map<K, V1> map, Function<V1, V2> transformer) {
return map.entrySet()
.stream()
.collect(toMap(Entry::getKey, e -> transformer.apply(e.getValue())));
}
The method above can be implemented using any approach, though I think Stream API fits pretty well here.
Once you defined the utility method, it can be used as simple as follows:
Map<String, Map<String, CustomObj>> customMap =
transformValues(inputMap, attr -> transformValues(attr, this::getCustomObj));
The actual transformation is effectively one liner. So with proper JavaDoc for transformValues method the result code is pretty readable and maintainable.
How about Collectors.toMap for the entries both at an outer and inner level such as:
Map<String, Map<String, CustomObj>> finalMap = configuredMap.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
attributeEntry -> attributeEntry.getValue().entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
valueEntry -> getCustomeObj(valueEntry.getValue())))));

Java8 Streams - How to modify value of keys of inner map to null object from "null" string

I have a utility method defined as below.
public static Map<String, Map<String, String>> convertRawMapToStringValues(Map<String, Map<String, Object>> cassandraRowsRawMap) {
Map<String, Map<String, String>> cassandraStrValuesMap = cassandraRowsRawMap.entrySet()
.stream()
.collect(Collectors.toMap(s -> s.getKey(),
s -> s.getValue().entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey(),
e -> String.valueOf(e.getValue())))));
return cassandraStrValuesMap;
}
The String.valueOf(e.getValue()) returns a "null" value from the call. I would like to get the null value for the string.
When I tried the below code, I get an NPE on first .collect call.
Map<String, Map<String, String>> cassandraStrValuesMap = cassandraRowsRawMap.entrySet()
.stream()
.collect(Collectors.toMap(s -> s.getKey(),
s -> s.getValue().entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey(),
e -> e.getValue() == null ? null : String.valueOf(e.getValue())))));
return cassandraStrValuesMap;
}
The toMap collector doesn’t support null values. But it doesn’t always have to be the Stream API:
public static <K,T,R> Map<K,R> changeValues(
Map<? extends K, T> in, Function<? super T, ? extends R> f) {
Map<K,R> result = new HashMap<>(in.size());
in.forEach((k,t) -> result.put(k, f.apply(t)));
return result;
}
public static Map<String, Map<String, String>> convertRawMapToStringValues(
Map<String, Map<String, Object>> in) {
return changeValues(in, inner -> changeValues(inner, v -> v==null? null: v.toString()));
}
The utility method returns a map with the same keys and transformed values and is flexible enough to allow a recursive application to do the inner map transformation.
Alternatively, we may adapt the solution of this answer to
public static Map<String, Map<String, String>> convertRawMapToStringValues(
Map<String, Map<String, Object>> in) {
return in.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().entrySet().stream()
.collect(
HashMap::new,
(m,e2) -> m.put(e2.getKey(),
e2.getValue() == null? null: e2.getValue().toString()),
Map::putAll)));
}
Unlike the original toMap collector, this won’t throw on duplicate keys, but for this specific case where the input is already a Map, there shouldn’t be duplicate keys anyway.

Switching Keys in a Map of Maps

How would you use Java 8 streams to swap keys in this map of maps? Or at least clean up this mess a little bit...
Map<Type1, Map<Type2, String>> to Map<Type2, Map<Type1, String>>
Using nested for loops (untested):
Map<Type1, Map<Type2, String>> map ...
Map<Type2, Map<Type1, String>> map2 = new HashMap<>();
for (Type 1 type1 : map.keySet()) {
for(Entry<Type2, String> entry : map.get(type1)) {
if (map2.get(entry.key() == null) {
map2.push(entry.key(), new HashMap<Type1, String>();
}
map2.get(entry.key()).put(type1, entry.value();
}
}
So far I think you would need to flap map into all unique combinations of Type1, Type2, and String and store this set in some sort of intermediate collection.
Definitely wrong:
map.entrySet().stream().flatMap(t -> <Type1, Type2,
String>).collect(Collectors.toMap(t -> t.Type2, Collectors.toMap(t ->
t.type1, t->t.String))
Streams aren't well-suited for this type of problem. Instead, consider using other java 8 additions -- Map#forEach and Map#computeIfAbsent:
map.forEach( (t1, e) ->
e.forEach( (t2, v) ->
result.computeIfAbsent(t2, x -> new HashMap<>()).put(t1, v)
)
);
Misha already showed you the straight forward solution. If you really want to use Streams it could look like this:
public static <S, T> Map<T, Map<S, String>> convertStream(Map<S, Map<T, String>> map) {
return map.entrySet().stream().flatMap(m1 -> m1.getValue().entrySet()
.stream().map(e -> new Object() {
final T outer = e.getKey();
final Map<S, String> map;
{
map = new HashMap<>();
map.put(m1.getKey(), e.getValue());
}
})).collect(Collectors.toMap(o -> o.outer, o -> o.map, (m1, m2) -> {
m1.putAll(m2);
return m1;
}));
}
Map<Type2, Map<Type1, Object>> finalAnswer = map.entrySet().stream()
.collect(()->new HashMap<Type2,Map<Type1,Object>>(),
(mapAccumulator, left)->{
for(Entry<?, ?> leftEntry : left.getValue().entrySet() ){
Map<Type1,Object> tempMap = new HashMap<>();
tempMap.put(left.getKey(), leftEntry.getValue());
mapAccumulator.put((Type2) leftEntry.getKey(), tempMap);
}
/*accumulator*/},
(mapLeft,mapRight)->{mapLeft.putAll(mapRight); /*combiner*/});
map.entrySet().forEach(System.out::println);

Categories

Resources