Reputation: 648
I have a class like below.
class Student {
public Student(Set<String> seminars) {
this.seminar = seminars;
}
Set<String> seminar;
public Set<String> getSeminar()
{
return seminar;
}
}
And created a set of students like below.
List<Student> students = new ArrayList<Student>();
Set<String> seminars = new HashSet<String>();
seminars.add("SeminarA");
seminars.add("SeminarB");
seminars.add("SeminarC");
students.add(new Student(seminars)); //Student 1 - 3 seminars
students.add(new Student(seminars)); //Student 2 - 3 seminars
seminars = new HashSet<String>();
seminars.add("SeminarA");
seminars.add("SeminarB");
students.add(new Student(seminars)); //Student 3 - 2 seminars
seminars = new HashSet<String>();
students.add(new Student(seminars)); //Student 4 - 0 seminars
Now the question is "I'm trying to get the count of students who has atteneded maximus seminars" As you can see there are 2 students who has attended to 3(maximum) seminars, So I needed to get that count.
I achieved the same using 2 different statements using stream
OptionalInt max = students.stream()
.map(Student::getSeminar)
.mapToInt(Set::size)
.max();
long count = students.stream()
.map(Student::getSeminar)
.filter(size -> size.size() == max.getAsInt())
.count();
is there a way to achieve the same using one statement?
Upvotes: 4
Views: 7376
Reputation: 139039
To solve this, please use the follwing code:
students.stream()
.map(Student::getSeminar)
.collect(Collectors.groupingBy(Set::size, Collectors.counting()));
Your output will be:
{0=1, 2=1, 3=2}
As you can see, there are two students
that are going to the maximum number of seminars which is three.
First we changed all the students objects from the stream with the actual sets and then we used Collectors groupingBy()
method to group the sets by size.
If you want to get only the number of the students, please use following code:
students.stream()
.map(Student::getSeminar)
.collect(Collectors.groupingBy(Set::size, Collectors.counting()))
.values().stream()
.max(Comparator.comparing(a->a))
.get();
Your output will be: 2
.
Upvotes: 8
Reputation: 28183
In most practical situations, your approach of traversing the list twice is the best one. It is simple and the code is easy to understand. Fight the urge to conserve statements -- you don't get charged per semicolon!
However, there are some conceivable scenarios when the source data cannot be traversed twice. Perhaps it comes from some slow or once-only source like a network stream.
If you have such a situation, you might want to define a custom collector allMaxBy
that collects all max elements into a downstream collector.
Then you would be able to write
long maxCount = students.stream()
.collect(allMaxBy(
comparingInt(s -> s.getSeminar().size()),
counting()
));
Here's an implementation for allMaxBy
:
public static <T, A, R> Collector<T, ?, R> allMaxBy(Comparator<? super T> cmp, Collector<? super T, A, R> downstream) {
class AllMax {
T val;
A acc = null; // null means empty
void add(T t) {
int c = acc == null ? 1 : cmp.compare(t, val);
if (c > 0) {
val = t;
acc = downstream.supplier().get();
downstream.accumulator().accept(acc, t);
} else if (c == 0) {
downstream.accumulator().accept(acc, t);
}
}
AllMax merge(AllMax other) {
if (other.acc == null) {
return this;
} else if (this.acc == null) {
return other;
}
int c = cmp.compare(this.val, other.val);
if (c == 0) {
this.acc = downstream.combiner().apply(this.acc, other.acc);
}
return c >= 0 ? this : other;
}
R finish() {
return downstream.finisher().apply(acc);
}
}
return Collector.of(AllMax::new, AllMax::add, AllMax::merge, AllMax::finish);
}
Upvotes: 1
Reputation: 12817
Little modified @Alex's solution, to sort the keys while doing the grouping
TreeMap<Integer, Long> agg = students.stream()
.map(Student::getSeminar)
.collect(Collectors.groupingBy(Set::size, TreeMap::new, Collectors.counting()));
System.out.println(agg);
System.out.println(agg.lastEntry().getKey() + " - " + agg.lastEntry().getValue());
output
3 - 2
Upvotes: 0
Reputation: 60
This is ugly, but the following would work:
long count = students.stream()
.filter(s -> s.getSeminar().size() == students
.stream().mapToInt(a -> a.getSeminar().size())
.max().orElse(0))
.count();
Explanation: This streams the students list and filters the students so that only students with the maximum number of seminars (which is what the nested lambda is) remain and then takes the count of that stream.
Upvotes: 0