I was trying to filter a list based on multiple conditions, sorting.
class Student{
private int Age;
private String className;
privat
If you need a grouping only sorted, it is quite simple:
Map<String, List<Student>> collect = students.stream() // stream capabilities
.sorted(Comparator.comparingInt(Student::getAge).reversed()) // sort by age, descending
.collect(Collectors.groupingBy(Student::getName)); // group by name.
Output in collect:
Use the toMap collector:
Collection<Student> values = students.stream()
.collect(toMap(Student::getName,
Function.identity(),
BinaryOperator.maxBy(Comparator.comparingInt(Student::getAge))))
.values();
Explanation
We're using this overload of toMap
:
toMap(Function<? super T,? extends K> keyMapper,
Function<? super T,? extends U> valueMapper,
BinaryOperator<U> mergeFunction)
Student::getName
above is the keyMapper
function used to extract the values for the map keys.Function.identity()
above is the valueMapper
function used to extract the values for the map values where Function.identity()
simply returns the elements in the source them selves i.e. the Student
objects.BinaryOperator.maxBy(Comparator.comparingInt(Student::getAge))
above is the merge function used to "decide which Student object to return in the case of a key collission i.e. when two given students have the same name" in this case taking the oldest Student
.values()
returns us a collection of students.The equivalent C# code being:
var values = students.GroupBy(s => s.Name, v => v,
(a, b) => b.OrderByDescending(e => e.Age).Take(1))
.SelectMany(x => x);
Explanation (for those unfamiliar with .NET)
We're using this extension method of GroupBy:
System.Collections.Generic.IEnumerable<TResult> GroupBy<TSource,TKey,TElement,TResult>
(this System.Collections.Generic.IEnumerable<TSource> source,
Func<TSource,TKey> keySelector,
Func<TSource,TElement> elementSelector,
Func<TKey,System.Collections.Generic.IEnumerable<TElement>,TResult> resultSelector);
s => s.Name
above is the keySelector
function used to extract the value to group by.v => v
above is the elementSelector
function used to extract the values i.e. the Student
objects them selves.b.OrderByDescending(e => e.Age).Take(1)
above is the resultSelector
which given an IEnumerable<Student>
represented as b
takes the oldest student..SelectMany(x => x);
to collapse the resulting IEnumerable<IEnumerable<Student>>
into a IEnumerable<Student>
.Or without streams:
Map<String, Student> map = new HashMap<>();
students.forEach(x -> map.merge(x.getName(), x, (oldV, newV) -> oldV.getAge() > newV.getAge() ? oldV : newV));
Collection<Student> max = map.values();
Just to mix and merge the other solutions, you could alternatively do :
Map<String, Student> nameToStudentMap = new HashMap<>();
Set<Student> finalListOfStudents = students.stream()
.map(x -> nameToStudentMap.merge(x.getName(), x, (a, b) -> a.getAge() > b.getAge() ? a : b))
.collect(Collectors.toSet());