I have a list of Data object
public class Data{
private String id;
private String sharedId;
}
How to convert this list into Map<String,List<Data>>, collecting the data object sharing the same sharedId together
I have tried to use this line of code, but no luck
Map<String,List<Data>> dataMap= Flux.fromIterable(list)
.collectMap(item-> item.getSharedId(),
item-> item);
Stream API is not equal to Reactive API and they are not interchangeable.
Use Stream API for grouping into Map<String, List<Data>> from List<Data>:
Map<String,List<Data>> dataMap = list.stream()
.collect(Collectors.groupingBy(Data::getSharedId));
Using Reactive API to achieve you achieve Flux<GroupedFlux<String, Data>> analogically or expand the structure using Map.Entry:
Flux<GroupedFlux<String, Data>> groupedFlux = Flux.fromIterable(list)
.groupBy(Data::getSharedId);
Flux<Map.Entry<String, List<Data>>> groupedFlux = Flux.fromIterable(list)
.groupBy(Data::getSharedId)
.flatMap(group -> group
.collectList()
.map(listOfData -> Map.entry(group.key(), listOfData)));
The following should work:
Map<String,List<Data>> dataMap =
list.stream().collect(groupingBy(Data::getSharedId));
With regards to previous answers from Nikolas & Joao you could also use:
Mono<Map<String, Collection<Data>>> mapCollection = Flux.fromIterable(list)
.collectMultimap(Data::getSharedId, item -> item);
you can map further to specific collection if it needs to be a list:
Mono<Map<String, List<Data>>> mapList = mapCollection
.map(map -> map
.entrySet()
.stream()
.collect(Collectors.toMap(Entry::getKey, e -> List.copyOf(e.getValue()))))
or use:
Mono<Map<String, List<Data>>> mapList2 = Mono.just(list)
.map(l -> l.stream().collect(Collectors.groupingBy(Data::getSharedId)));
Related
I have a use case where I need to read a file and get the grouping of a sequence and a list of values associated with the sequence. The format of these records in the file are like sequence - val , example
10-A
10-B
11-C
11-A
I want the output to be a map (Map<String,List<String>>) with the sequence as the key and list of values associated with it as value, like below
10,[A,B]
11,[C,A]
Is there a way I can do this without creating a POJO for these records? I have been trying to explore the usage of Collectors.groupingBy and most of the examples I see are based on creating a POJO.
I have been trying to write something like this
Map<String, List<String>> seqCpcGroupMap = pendingCpcList.stream().map(rec ->{
String[] cpcRec = rec.split("-");
return new Tuple2<>(cpcRec[0],cpcRec[1])
}).collect(Collectors.groupingBy(x->x.))
or
Map<String, List<String>> seqCpcGroupMap = pendingCpcList.stream().map(rec ->{
String[] cpcRec = rec.split("-");
return Arrays.asList(cpcRec[0],cpcRec[1]);
}).collect(Collectors.groupingBy(x->(ArrayList<String>)x[0]));
I am unable to provide any key on which the groupingBy can happen for the groupingBy function, is there a way to do this or do I have to create a POJO to use groupingBy?
You may do it like so,
Map<String, List<String>> result = source.stream()
.map(s -> s.split("-"))
.collect(Collectors.groupingBy(a -> a[0],
Collectors.mapping(a -> a[1], Collectors.toList())));
Alternatively, you can use Map.computeIfAbsent directly as :
List<String> pendingCpcList = List.of("10-A","10-B","11-C","11-A");
Map<String, List<String>> seqCpcGroupMap = new HashMap<>();
pendingCpcList.stream().map(rec -> rec.split("-"))
.forEach(a -> seqCpcGroupMap.computeIfAbsent(a[0], k -> new ArrayList<>()).add(a[1]));
So there might be one abc for several payments, now I have:
//find abc id for each payment id
Map<Long, Integer> abcIdToPmtId = paymentController.findPaymentsByIds(pmtIds)
.stream()
.collect(Collectors.toMap(Payment::getAbcId, Payment::getPaymentId));
But then I reallize this could have duplicate keys, so I want it to return a
Map<Long, List<Integer>> abcIdToPmtIds
which an entry will contain one abc and his several payments.
I know I might can use groupingBy but then I think I can only get Map<Long, List<Payments>> .
Use the other groupingBy overload.
paymentController.findPaymentsByIds(pmtIds)
.stream()
.collect(
groupingBy(Payment::getAbcId, mapping(Payment::getPaymentId, toList());
Problem statement: Converting SimpleImmutableEntry<String, List<String>> -> Map<String, List<String>>.
For Instance you have a SimpleImmutableEntry of this form [A,[1]], [B,[2]], [A, [3]] and you want your map to looks like this: A -> [1,3] , B -> [2].
This can be done with Collectors.toMap but Collectors.toMap works only with unique keys unless you provide a merge function to resolve the collision as said in java docs.
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#toMap-java.util.function.Function-java.util.function.Function-java.util.function.BinaryOperator-
So the example code looks like this:
.map(returnSimpleImmutableEntries)
.collect(Collectors.toMap(SimpleImmutableEntry::getKey,
SimpleImmutableEntry::getValue,
(oldList, newList) -> { oldList.addAll(newList); return oldList; } ));
returnSimpleImmutableEntries method returns you entries of the form [A,[1]], [B,[2]], [A, [3]] on which you can use your collectors.
With Collectors.toMap:
Map<Long, Integer> abcIdToPmtId = paymentController.findPaymentsByIds(pmtIds)
.stream()
.collect(Collectors.toMap(
Payment::getAbcId,
p -> new ArrayList<>(Arrays.asList(p.getPaymentId())),
(o, n) -> { o.addAll(n); return o; }));
Though it's more clear and readable to use Collectors.groupingBy along with Collectors.mapping.
You don't need streams to do it though:
Map<Long, Integer> abcIdToPmtId = new HashMap<>();
paymentController.findPaymentsByIds(pmtIds).forEach(p ->
abcIdToPmtId.computeIfAbsent(
p.getAbcId(),
k -> new ArrayList<>())
.add(p.getPaymentId()));
Maybe it's perversion but I want to merge results of inner stream to stream on the level above.
For example we have some complex map with data:
Map<String, List<Map<String, Object>>> dataMap
and I need to collect all Objects to List. For now I'm doing like this:
Set<Object> segmentIds = new HashSet<>();
dataMap.values().forEach(maps -> maps.forEach(map -> segmentIds.add(map.get("object"))));
But it's not prettily way. But I can't understand how to transfer data from inner cycle to outer to collect them in the end.
Is it possible to do it without any outer objects?
What about it:
Set<Object> collect = dataMap.values()
.stream()
.flatMap(Collection::stream)
.map(map -> map.get("object"))
.collect(Collectors.toSet());
You have to use flatMapof the Stream-API.
List<Object> allObjects = dataMap.values().stream()
.flatMap(l -> l.stream())
.flatMap(m -> m.values().stream())
.collect(Collectors.toList())
Code is not tested
I am trying to get list of Long from list of maps using Stream API.
This is how class looks like:
class Report Bean{
private MarshallableMap data;
}
Object data contains records like this: ("ID", 1), ("Name", "TestName").
I need to get list of IDs from list of ReportBeans.
This is what I've tried:
List<Long> ids = reportBeans.stream().flatMap(
m -> m.getData().entrySet().stream()
.filter(e -> e.getKey().equals("ID"))
.map(Map.Entry::getValue)
.map(Long.class::cast))
.collect(Collectors.toList());
I am getting empty list. Please, I need advice. Thank you
There is a lot of unnecessary streaming going on. I think this is more suited for your needs:
reportBeans.stream()
.map(r -> r.getData().get("ID"))
.filter(Objects::nonNull)
.map(Long.class:cast)
.collect(toList());
You have not shown how your class 'MarshallableMap' looks like. It is necessary for us to provide an exact answer. But if your 'data' field is of type Map<String, String>, you can get the list of IDs the following way:
List<Long> ids = reportBeans.stream()
.flatMap(
m -> (m.getData().entrySet().stream()
.filter(e -> "ID".equals(e.getKey()))
.map(e -> Long.valueOf(e.getValue())))
)
.collect(Collectors.toList());
I have a class like this:
class MultiDataPoint {
private DateTime timestamp;
private Map<String, Number> keyToData;
}
and i want to produce , for each MultiDataPoint
class DataSet {
public String key;
List<DataPoint> dataPoints;
}
class DataPoint{
DateTime timeStamp;
Number data;
}
of course a 'key' can be the same across multiple MultiDataPoints.
So given a List<MultiDataPoint>, how do I use Java 8 streams to convert to List<DataSet>?
This is how I am currently doing the conversion without streams:
Collection<DataSet> convertMultiDataPointToDataSet(List<MultiDataPoint> multiDataPoints)
{
Map<String, DataSet> setMap = new HashMap<>();
multiDataPoints.forEach(pt -> {
Map<String, Number> data = pt.getData();
data.entrySet().forEach(e -> {
String seriesKey = e.getKey();
DataSet dataSet = setMap.get(seriesKey);
if (dataSet == null)
{
dataSet = new DataSet(seriesKey);
setMap.put(seriesKey, dataSet);
}
dataSet.dataPoints.add(new DataPoint(pt.getTimestamp(), e.getValue()));
});
});
return setMap.values();
}
It's an interesting question, because it shows that there are a lot of different approaches to achieve the same result. Below I show three different implementations.
Default methods in Collection Framework: Java 8 added some methods to the collections classes, that are not directly related to the Stream API. Using these methods, you can significantly simplify the implementation of the non-stream implementation:
Collection<DataSet> convert(List<MultiDataPoint> multiDataPoints) {
Map<String, DataSet> result = new HashMap<>();
multiDataPoints.forEach(pt ->
pt.keyToData.forEach((key, value) ->
result.computeIfAbsent(
key, k -> new DataSet(k, new ArrayList<>()))
.dataPoints.add(new DataPoint(pt.timestamp, value))));
return result.values();
}
Stream API with flatten and intermediate data structure: The following implementation is almost identical to the solution provided by Stuart Marks. In contrast to his solution, the following implementation uses an anonymous inner class as intermediate data structure.
Collection<DataSet> convert(List<MultiDataPoint> multiDataPoints) {
return multiDataPoints.stream()
.flatMap(mdp -> mdp.keyToData.entrySet().stream().map(e ->
new Object() {
String key = e.getKey();
DataPoint dataPoint = new DataPoint(mdp.timestamp, e.getValue());
}))
.collect(
collectingAndThen(
groupingBy(t -> t.key, mapping(t -> t.dataPoint, toList())),
m -> m.entrySet().stream().map(e -> new DataSet(e.getKey(), e.getValue())).collect(toList())));
}
Stream API with map merging: Instead of flattening the original data structures, you can also create a Map for each MultiDataPoint, and then merge all maps into a single map with a reduce operation. The code is a bit simpler than the above solution:
Collection<DataSet> convert(List<MultiDataPoint> multiDataPoints) {
return multiDataPoints.stream()
.map(mdp -> mdp.keyToData.entrySet().stream()
.collect(toMap(e -> e.getKey(), e -> asList(new DataPoint(mdp.timestamp, e.getValue())))))
.reduce(new HashMap<>(), mapMerger())
.entrySet().stream()
.map(e -> new DataSet(e.getKey(), e.getValue()))
.collect(toList());
}
You can find an implementation of the map merger within the Collectors class. Unfortunately, it is a bit tricky to access it from the outside. Following is an alternative implementation of the map merger:
<K, V> BinaryOperator<Map<K, List<V>>> mapMerger() {
return (lhs, rhs) -> {
Map<K, List<V>> result = new HashMap<>();
lhs.forEach((key, value) -> result.computeIfAbsent(key, k -> new ArrayList<>()).addAll(value));
rhs.forEach((key, value) -> result.computeIfAbsent(key, k -> new ArrayList<>()).addAll(value));
return result;
};
}
To do this, I had to come up with an intermediate data structure:
class KeyDataPoint {
String key;
DateTime timestamp;
Number data;
// obvious constructor and getters
}
With this in place, the approach is to "flatten" each MultiDataPoint into a list of (timestamp, key, data) triples and stream together all such triples from the list of MultiDataPoint.
Then, we apply a groupingBy operation on the string key in order to gather the data for each key together. Note that a simple groupingBy would result in a map from each string key to a list of the corresponding KeyDataPoint triples. We don't want the triples; we want DataPoint instances, which are (timestamp, data) pairs. To do this we apply a "downstream" collector of the groupingBy which is a mapping operation that constructs a new DataPoint by getting the right values from the KeyDataPoint triple. The downstream collector of the mapping operation is simply toList which collects the DataPoint objects of the same group into a list.
Now we have a Map<String, List<DataPoint>> and we want to convert it to a collection of DataSet objects. We simply stream out the map entries and construct DataSet objects, collect them into a list, and return it.
The code ends up looking like this:
Collection<DataSet> convertMultiDataPointToDataSet(List<MultiDataPoint> multiDataPoints) {
return multiDataPoints.stream()
.flatMap(mdp -> mdp.getData().entrySet().stream()
.map(e -> new KeyDataPoint(e.getKey(), mdp.getTimestamp(), e.getValue())))
.collect(groupingBy(KeyDataPoint::getKey,
mapping(kdp -> new DataPoint(kdp.getTimestamp(), kdp.getData()), toList())))
.entrySet().stream()
.map(e -> new DataSet(e.getKey(), e.getValue()))
.collect(toList());
}
I took some liberties with constructors and getters, but I think they should be obvious.