I need to find a method (using streams) to return a Map<Category,Optional<ToDo>
, which help me group an ArrayList
and give me a ToDo
object with the highest priority
of each category
.
public record ToDo(String name, Category category,
int priority, LocalDate date) {}
public enum Category { HOME, WORK }
An example of the input data:
List<ToDo> todo = List.of(
new ToDo("Eat", Category.HOME, 1, LocalDate.of(2022, 8, 29)),
new ToDo("Sleep", Category.HOME, 2, LocalDate.of(2022, 8, 30)),
new ToDo("Learn", Category.WORK, 2, LocalDate.of(2022, 9, 3)),
new ToDo("Work", Category.WORK, 3, LocalDate.of(2022, 10, 3))
);
And in the end, I want to have something like this as a result:
{HOME=[ToDo{Description='Eat', category=HOME, priority=1, deadline=2022-08-29},
WORK=[ToDo{Description='Learn', category=WORK, priority=2, deadline=2022-09-03]}
I was trying to use
.collect(Collectors.groupingBy(p -> p.getCategory()));
and
.sorted(Comparator.comparing(ToDo::getPriority)).findFirst();
But I can't do it in a single method and get Optional
as a result. How can I resolve this problem?
CodePudding user response:
The practice of storing Optional
s in a Collection
is discouraged. Apart from being cumbersome because of the need to deal with optional after retrieving value a Map, it goes against the design goal of Optional
which is intended to be used as a return type.
And while using collector groupingBy()
it's always guaranteed that a ToDo
object mapped to a particular Category
will always be present.
To find a ToDo
with the highest priority in a particular category, you can use collector maxBy()
, which expects a Comparator
, as a downstream collector of groupingBy()
. It would be way more efficient than combination .sorted().findFirst()
.
List<ToDo> todo = List.of(
new ToDo("Eat", Category.HOME, 1, LocalDate.of(2022, 8, 29)),
new ToDo("Sleep", Category.HOME, 2, LocalDate.of(2022, 8, 30)),
new ToDo("Learn", Category.WORK, 2, LocalDate.of(2022, 9, 3)),
new ToDo("Work", Category.WORK, 3, LocalDate.of(2022, 10, 3))
);
Map<Category, ToDo> highestPriorityTaskByCategory = todo.stream()
.collect(Collectors.groupingBy(
ToDo::category,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparingInt(ToDo::priority)),
Optional::orElseThrow)
));
highestPriorityTaskByCategory.forEach((k, v) -> System.out.println(k " -> " v));
Output:
HOME -> ToDo[name=Sleep, category=HOME, priority=2, date=2022-08-30]
WORK -> ToDo[name=Work, category=WORK, priority=3, date=2022-10-03]
CodePudding user response:
I assume that the DOM
should be HOME
, because that's the category inside your object. I also assume that the ToDoList
should be an Optional<ToDo>
.
The groupingBy
you used will return a Map<Category, List<ToDo>>
. That's the right key but the wrong value. You need to solve that by also supplying a downstream collector, that will collect ToDo
elements:
Map<Category, Optional<ToDo>> result = todo.stream()
.collect(Collectors.groupingBy(
ToDo::getCategory,
Collectors.minBy(Comparator.comparingInt(ToDo::getPriority))
));
You can improve the comparator to Comparator.comparingInt(ToDo::getPriority).thenComparing(ToDo::getDeadline)
to find the entry with the earliest deadline in case some have the same lowest priority.
Note that this will only include entries for categories that are part of your input. If you need all, use an additional loop:
for (Category category : Category.values()) {
result.computeIfAbsent(category, c -> Optional.empty());
}