Reputation: 2301
I have the following (simplifying my real classes) HashMap<User, Counter>
and
class User {
private String name;
private Integer age;
}
class Counter {
private Integer numberOfQuestions;
private Long maxQuestionId;
}
I would like to find the sum of number of questions for the minimum age.
Ex.
UserA, 20 -> 11, 100L;
UserB, 25 -> 15, 100L;
UserC, 23 -> 30, 100L;
UserD, 20 -> 11, 100L,
UserE, 25 -> 15, 100L;
The result should be 22 -> sum of the minimum number of question per age (age: 20, nOfQuestions 11+11=22)
I tried with java streams:
Integer min = userQuestionHashMap.entrySet().stream()
// not working .collect(groupingBy(Map.Entry::getKey::getAge),
summingInt(Map.Entry::getValue::getNumOfQuestion))
)
// missing
.mapToInt(value -> value.getValue().getNumOfQuestion())
.min().orElse(0);
Upvotes: 2
Views: 292
Reputation: 2020
I think this is the fastest way:
int minAge = Integer.MAX_VALUE;
int total = 0;
for (Map.Entry me : map.entrySet()) {
if (me.getKey().age < minAge) {
minAge = me.getKey().age;
total = me.getValue().numberOfQuestions;
} else if (me.getKey().age.equals(minAge)) {
total += me.getValue().numberOfQuestions;
}
}
You only go once through the map.
Upvotes: 1
Reputation: 298123
An efficient solution not requiring to build an entire map, is to use the custom collector of this answer.
Since it’s written for max elements, we have to either, reverse the comparator:
int min = userQuestionHashMap.entrySet().stream()
.collect(maxAll(Collections.reverseOrder(
Map.Entry.comparingByKey(Comparator.comparingInt(User::getAge))),
Collectors.summingInt(e -> e.getValue().getNumberOfQuestions())));
or create a specialized min version
public static <T, A, D> Collector<T, ?, D> minAll(
Comparator<? super T> comparator, Collector<? super T, A, D> downstream) {
Supplier<A> downSupplier = downstream.supplier();
BiConsumer<A, ? super T> downAccumulator = downstream.accumulator();
BinaryOperator<A> downCombiner = downstream.combiner();
Function<A, D> downFinisher = downstream.finisher();
class Container {
A acc;
T obj;
boolean hasAny;
Container(A acc) {
this.acc = acc;
}
}
return Collector.of(() -> new Container(downSupplier.get()),
(c, t) -> {
int cmp = c.hasAny? comparator.compare(t, c.obj): -1;
if (cmp > 0) return;
if(cmp != 0) {
c.acc = downSupplier.get();
c.obj = t;
c.hasAny = true;
}
downAccumulator.accept(c.acc, t);
},
(c1, c2) -> {
if(!c1.hasAny) return c2;
int cmp = c2.hasAny? comparator.compare(c1.obj, c2.obj): -1;
if(cmp > 0) return c2;
if(cmp == 0) c1.acc = downCombiner.apply(c1.acc, c2.acc);
return c1;
},
c -> downFinisher.apply(c.acc));
}
Then, you can use it like
int min = userQuestionHashMap.entrySet().stream()
.collect(minAll(Map.Entry.comparingByKey(Comparator.comparingInt(User::getAge)),
Collectors.summingInt(e -> e.getValue().getNumberOfQuestions())));
or alternatively
int min = userQuestionHashMap.entrySet().stream()
.collect(minAll(Comparator.comparingInt(e -> e.getKey().getAge()),
Collectors.summingInt(e -> e.getValue().getNumberOfQuestions())));
Upvotes: 2
Reputation: 159086
You can do it like this:
static int questionsByYoungest(Map<User, Counter> inputMap) {
if (inputMap.isEmpty())
return 0;
return inputMap.entrySet().stream()
.collect(Collectors.groupingBy(
e -> e.getKey().getAge(),
TreeMap::new,
Collectors.summingInt(e -> e.getValue().getNumberOfQuestions())
))
.firstEntry().getValue();
}
Test
Map<User, Counter> inputMap = Map.of(
new User("UserA", 20), new Counter(11, 100L),
new User("UserB", 25), new Counter(15, 100L),
new User("UserC", 23), new Counter(30, 100L),
new User("UserD", 20), new Counter(11, 100L),
new User("UserE", 25), new Counter(15, 100L));
System.out.println(questionsByYoungest(inputMap)); // prints 22
Upvotes: 2
Reputation: 6290
You could group by user age and using summingInt
as a downstream. Then find min from result map by key:
Integer sum = map.entrySet().stream()
.collect(groupingBy(entry -> entry.getKey().getAge(),
summingInt(value -> value.getValue().getNumberOfQuestions())))
.entrySet().stream()
.min(Map.Entry.comparingByKey())
.orElseThrow(RuntimeException::new)
.getValue();
Upvotes: 2