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());