Lambda & Stream : collect in a Map - java

I would like to build a Map using the Stream & Lambda couple.
I've tried many ways but I'm stucked. Here's the classic Java code to do it using both Stream/Lambda and classic loops.
Map<Entity, List<Funder>> initMap = new HashMap<>();
List<Entity> entities = pprsToBeApproved.stream()
.map(fr -> fr.getBuyerIdentification().getBuyer().getEntity())
.distinct()
.collect(Collectors.toList());
for(Entity entity : entities) {
List<Funder> funders = pprsToBeApproved.stream()
.filter(fr -> fr.getBuyerIdentification().getBuyer().getEntity().equals(entity))
.map(fr -> fr.getDocuments().get(0).getFunder())
.distinct()
.collect(Collectors.toList());
initMap.put(entity, funders);
}
As you can see, I only know how to collect in a list, but I just can't do the same with a map. That's why I have to stream my list again to build a second list to, finally, put all together in a map.
I've also tried the 'collect.groupingBy' statement as it should too produce a map, but I failed.

It seems you want to map whatever is on the pprsToBeApproved list to your Funder instances, grouping them by buyer Entity.
You can do it as follows:
Map<Entity, List<Funder>> initMap = pprsToBeApproved.stream()
.collect(Collectors.groupingBy(
fr -> fr.getBuyerIdentification().getBuyer().getEntity(), // group by this
Collectors.mapping(
fr -> fr.getDocuments().get(0).getFunder(), // mapping each element to this
Collectors.toList()))); // and putting them in a list
If you don't want duplicate funders for a particular entity, you can collect to a map of sets instead:
Map<Entity, Set<Funder>> initMap = pprsToBeApproved.stream()
.collect(Collectors.groupingBy(
fr -> fr.getBuyerIdentification().getBuyer().getEntity(),
Collectors.mapping(
fr -> fr.getDocuments().get(0).getFunder(),
Collectors.toSet())));
This uses Collectors.groupingBy along with Collectors.mapping.

Related

Java Stream - Combine Two Streams

Is there a way I can combine these two streams into one?
Here's the first stream
Map<String, String> rawMapping = tokens.getColumnFamilies().stream()
.filter(family -> family.getName().equals("first_family"))
.findAny()
.map(columns -> columns.getColumns().stream()).get()
.collect(Collectors.toMap(
Column::getPrefix,
Column::getValue
));
Second stream
List<Token> tokenValues = tokens.getColumnFamilies().stream()
.filter(family -> family.getName().equals("second_family"))
.findAny()
.map(columns -> columns.getColumns().stream()).get()
.map(token -> {
return Token.builder()
.qualifier(token.getPrefix())
.raw(rawMapping.get(token.getPrefix()))
.token(token.getValue())
.build();
})
.collect(Collectors.toList());
Basically tokens is a list which has two column family, my goal is to create a list which will combine the value of the two-column family based on their qualifier. The first stream is storing the first column family into a map. The second stream is traversing the second family and getting the value thru the map using the qualifier and storing it into a new list.
you can use double filtering and then later you might use a flat map then to get a list:
Map<String, String> tokenvalues = tokens.getColumnFamilies().stream()
.filter(family -> family.getName().equals("first_family"))
.filter(family -> family.getName().equals("second_family"))
.map(columns -> columns.getColumns().stream())
//etc..
.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList()));
you can remake a stream out of it inline
https://www.baeldung.com/java-difference-map-and-flatmap

Flattening a list of lists within a map

I have a stream of orders (the source being a list of orders).
Each order has a Customer, and a list of OrderLine.
What I'm trying to achieve is to have a map with the customer as the key, and all order lines belonging to that customer, in a simple list, as value.
What I managed right now returns me a Map<Customer>, List<Set<OrderLine>>>, by doing the following:
orders
.collect(
Collectors.groupingBy(
Order::getCustomer,
Collectors.mapping(Order::getOrderLines, Collectors.toList())
)
);
I'm either looking to get a Map<Customer, List<OrderLine>>directly from the orders stream, or by somehow flattening the list from a stream of the Map<Customer>, List<Set<OrderLine>>> that I got above.
You can simply use Collectors.toMap.
Something like
orders
.stream()
.collect(Collectors
.toMap(Order::getCustomer
, Order::getOrderLines
, (v1, v2) -> { List<OrderLine> temp = new ArrayList<>(v1);
temp.addAll(v2);
return temp;});
The third argument to the toMap function is the merge function. If you don't explicitly provide that and it there is a duplicate key then it will throw the error while finishing the operation.
Another option would be to use a simple forEach call:
Map<Customer, List<OrderLine>> map = new HashMap<>();
orders.forEach(
o -> map.computeIfAbsent(
o.getCustomer(),
c -> new ArrayList<OrderLine>()
).addAll(o.getOrderLines())
);
You can then continue to use streams on the result with map.entrySet().stream().
For a groupingBy approach, try Flat-Mapping Collector for property of a Class using groupingBy

Grouping By without using a POJO in java 8

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]));

I try to find a simple way in Java 8 stream API to do the grouping

I try to find a simple way in Java 8 stream API to do the grouping.
Path caminho = Paths.get(System.getProperty("user.dir"), "log.txt");
Files.lines(caminho, StandardCharsets.ISO_8859_1)
.map(linha -> {
return getUrl(linha);
})
.filter(url -> url != null)
.collect(
Collectors.groupingBy(Arquivo::getUrl,
LinkedHashMap::new, Collectors.counting() ),
Collectors.groupingBy(Arquivo::getStatusCode,
LinkedHashMap::new, Collectors.counting()));
it is possible?
You didn’t describe your use case, so one possible answer is that you can just group both properties into a single map, assuming that these properties have no overlapping value, which would be always the case, if they have different type:
Path caminho = Paths.get(System.getProperty("user.dir"), "log.txt");
LinkedHashMap<Object, Long> map = Files.lines(caminho, StandardCharsets.ISO_8859_1)
.map(linha -> getUrl(linha))
.filter(Objects::nonNull)
.flatMap(arquivo -> Stream.of(arquivo.getUrl(), arquivo.getStatusCode()))
.collect(Collectors.groupingBy(Function.identity(),
LinkedHashMap::new, Collectors.counting() ));
for the resulting map, lookup of either key would succeed and also iterating over it would yield to the right order, you’ll only have to skip the keys of the type you’re not interested in.
If both properties have the same type, say e.g. String, and could have overlapping values, including a marker would help:
Map<Boolean, LinkedHashMap<String, Long>> map
= Files.lines(caminho, StandardCharsets.ISO_8859_1)
.map(linha -> getUrl(linha))
.filter(Objects::nonNull)
.flatMap(arquivo -> Stream.of(Arrays.asList(arquivo.getUrl(), true),
Arrays.asList(arquivo.getStatusCode(), false)))
.collect(Collectors.partitioningBy(l -> (Boolean)l.get(1),
Collectors.groupingBy(l -> (String)l.get(0),
LinkedHashMap::new, Collectors.counting() )));
LinkedHashMap<String, Long> urls = map.get(true);
LinkedHashMap<String, Long> status = map.get(false);
This does not only solve the overlapping, but also provides you with two distinct maps, but unfortunately, it doesn’t work so smoothly, if the two properties have different types.

Java 8 streams merge inner stream result to stream above

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

Categories