Here is a sample source data structure to simplify the problem case data for my scenario. This Map has duplicate descriptions for different codes.
Map<Integer, String> descriptionsByCode = new HashMap();
descriptionsByCode.put(1, "description 1");
descriptionsByCode.put(2, "description 2");
descriptionsByCode.put(4, "description 3");
descriptionsByCode.put(5, "description 4");
descriptionsByCode.put(6, "description 5");
descriptionsByCode.put(7, "description 5");
descriptionsByCode.put(8, "description 5");
descriptionsByCode.put(9, "description 2");
descriptionsByCode.put(10, "description 2");
descriptionsByCode.put(11, "description 3");
descriptionsByCode.put(12, "description 4");
descriptionsByCode.put(13, "description 1");
descriptionsByCode.put(14, "description 6");
descriptionsByCode.put(15, "description 8");
I also have a complex business object, but for simplicity purpose lets assume there is an object with only 2 field.
@Data
@AllArgsConstructor
class StatusCounts {
private String description;
private Map<Integer, Integer> someIemCountByStatusCode; //defaults to zero for our example
}
If, I want to have a map of StatusCount
object By Description.
I can have it easily. Below is a working code, added here to concentrate on the main problem mentioned after this
Map<String, StatusCounts> statusCountsByDescription
= descriptionsByCode
.entrySet()
.stream()
.collect(Collectors.toMap(
e -> e.getValue(), //key function
e -> new StatusCounts(e.getValue(), //value function
Collections.singletonMap(e.getKey(), 0)),
(e, v) -> // merge function for collision
{ // is there a better way to write below code ?
Map m = new HashMap();
m.putAll(e.getItemCountByStatusCode());
m.putAll(v.getItemCountByStatusCode());
e.setItemCountByStatusCode(m);
return e;
}
));
Above code is not clean, but working well. What I actually want is, a Set instead of Map
I am unable to find a way, in reduce
or collect
functions, to provide a handling for collision detection
(like merge function does in case of Collectors.toMap
).
The combiner function in both reduce or collect, doesn't work for collision.
Collectors.toSet
doesn't even accept any parameter.
So I though I can write collision detection handling in accumulator
in case of reduce
or in or Biconsumer
in case of collect
. So I tried below using collect
& BiConsumer
But this one is uncompilable same is happening with reduce
Set<StatusCounts> statusCounts
= descriptionsByCode
.entrySet()
.stream()
.collect(() -> new HashSet<StatusCounts>(),
(set, entry) -> {
Optional<StatusCounts> statusCount
= findStatusCountWithSameDescription(set, entry.getValue());
if (statusCount.isPresent()) {
statusCount.get()
.getItemCountByStatusCode()
.put(entry.getKey(), 0);
} else {
set.add(new StatusCounts(
entry.getValue(),
Collections.singletonMap(entry.getKey(), 0)));
}
return set;
}
);
private static Optional<StatusCounts> findStatusCountWithSameDescription
(HashSet<StatusCounts> set, String description) {
return set.stream()
.filter(e -> e.getDescription()
.equalsIgnoreCase(description))
.findFirst();
}
This is the compilation error
Cannot resolve method 'collect(<lambda expression>, <lambda expression>)'
You could have used Collectors.groupingBy
to solve this:
Map<String, Set<Integer>> map
= descMap.entrySet()
.stream()
.collect(Collectors.groupingBy(Entry::getValue,
Collectors.mapping(Entry::getKey, Collectors.toSet())));
Here, a map would be generated with the description as the key. Collectors.mapping
maps each entry to the status code and then combines them into a Set
I think that a Set<Integer>
instead of Map<Integer, Integer>
would have been sufficient within the StatusCounts
class. This is because from the code, it seems like you just want to know the different codes under a description. In that case you could try the below:
return map.entrySet()
.stream()
.map(entry -> new StatusCounts(entry.getKey(), entry.getValue()))
.collect(Collectors.toSet());
If you are not allowed to change the existing StatusCounts
class, then you could try the below:
Map<String, Map<Integer, Integer>> tempMap
= descMap.entrySet()
.stream()
.collect(Collectors.groupingBy(Entry::getValue,
Collectors.mapping(Entry::getKey, Collectors.toMap(Function.identity(), v -> 0))));
// You may combine the below code with above instead of using a tempMap.
// I have separated it for simplicity
Set<StatusCounts> result
= tempMap.entrySet()
.stream()
.map(entry -> new StatusCounts(entry.getKey(), entry.getValue()))
.collect(Collectors.toSet());