16

Based on the following answer: https://stackoverflow.com/a/30202075/8760211

How to sort each group by stud_id and then return a List with all Students as result of the grouping by stud_location and then sorting by stud_id)?

It would be great to have this as extension to the existing Lambda Expression:

Map<String, List<Student>> studlistGrouped =
    studlist.stream().collect(Collectors.groupingBy(w -> w.stud_location));

I need the Grouping based on the order of the Elements in the origin List.

First group: "New York"
Second group: "California"
Third group: "Los Angeles"

1726, "John", "New York"
4321, "Max", "California"
2234, "Andrew", "Los Angeles"
5223, "Michael", "New York"
7765, "Sam", "California"
3442, "Mark", "New York"

The result would then look like the following:

List<Student> groupedAndSorted = ....

    1726, "John", "New York"
    3442, "Mark", "New York"
    5223, "Michael", "New York"
    4321, "Max", "California"
    7765, "Sam", "California"
    2234, "Andrew", "Los Angeles"

I have tried the following:

studlistGrouped.entrySet().stream().sorted(Comparator.compar‌​ing(Map.Entry::getVa‌​lue))

But this doesn't work.

5
  • I tried this studlistGrouped.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getValue)) but it didn't work! Commented Dec 6, 2017 at 17:09
  • 2
    Someone has down voted this question without any comment!!! It seems that some People have fun down voting other people's questions! Commented Dec 6, 2017 at 17:09
  • If you hover over the down arrow you can see the instruction for when it's appropriate to downvote: "this question does not show any research effort". That would be my best bet for the downvote. I will edit your attempt into your question. Commented Dec 6, 2017 at 17:12
  • Hi Roddy, thanks so much for your time. Downvoting: I would only downvote silly questions like "What is the result of 2 + 2?" More serious questions shouldn't be downvoted, because other people are more advanced in that theme. Commented Dec 6, 2017 at 17:25
  • Feel free to bring it up on Meta. I just indicated the official guidance on this matter. Commented Dec 6, 2017 at 17:28

9 Answers 9

17

not 100% clear whether you're expected a Map<String, List<Student>> or just a List<Student>, nevertheless here are both solutions:

imports:

import static java.util.stream.Collectors.*;
import java.util.*;
import java.util.function.Function;

retrieving a Map<String, List<Student>> where each List<Student> contains students sorted by their ids.

Map<String, List<Student>> resultSet = studlist.stream()
      .collect(groupingBy(Student::getLocation,
             mapping(Function.identity(),
                  collectingAndThen(toList(),
                      e -> e.stream().sorted(Comparator.comparingInt(Student::getId))
                                            .collect(toList())))));

on the other hand, if you want to retrieve just a list of Student objects sorted by a given property then it would be a waste of resources to perform a groupingBy, sorted, collect and somehow reduce the map values into a single list. Rather just sort the Student objects within the list providing a sort key i.e.

studlist.sort(Comparator.comparingInt(Student::getId));

or

studlist.sort(Comparator.comparing(Student::getLocation));

or depending on whether you want to sort by more than one property then you could do something like shmosel's answer.

Sign up to request clarification or add additional context in comments.

1 Comment

Hi Aominè, thanks so much for your help. After comprehensive comparison, I found that Kirill's compact solution does solve the problem directly. Holger, thanks for your hint.
8

If I get you right, you want a List<Student> (not a map) where students are grouped by their locations and sorted by ids inside groups and where groups are also sorted by ids, not by location names. This is possible, but requires one grouping and two sortings:

//first, use your function to group students
Map<String, List<Student>> studlistGrouped = students.stream()
        .collect(Collectors.groupingBy(Student::getLocation, Collectors.toList()));

//then sort groups by minimum id in each of them
List<Student> sorted = studlistGrouped.entrySet().stream()
        .sorted(Comparator.comparing(e -> e.getValue().stream().map(Student::getId).min(Comparator.naturalOrder()).orElse(0)))
        //and also sort each group before collecting them in one list
        .flatMap(e -> e.getValue().stream().sorted(Comparator.comparing(Student::getId))).collect(Collectors.toList());

This will produce following:

Student{id='1726', name='John', location='New York'}
Student{id='3442', name='Mark', location='New York'}
Student{id='5223', name='Michael', location='New York'}
Student{id='2234', name='Andrew', location='Los Angeles'}
Student{id='4321', name='Max', location='California'}
Student{id='7765', name='Sam', location='California'}

Maybe this can be done more elegantly, suggestions are welcome

EDIT: At the time this answer was written there was no mention about Grouping based on the order of the Elements in the origin List in the OPs question. So my assumption was to sort both list and groups by ids. For solutions based on the order in the original list see other answers, for example, the Holgers one

7 Comments

Hi Kirill, thanks so much for your help. Your compact solution does solve the problem directly without the need of further coding.
Hmm, I would never have assumed that “grouping based on the order of the elements in the origin List” implies “sort groups based on their minimum id”, especially as the question’s example contradicts that assumption, as it shows “California”, smallest id 4321, before “Los Angeles”, smallest id 2234. You must have read the OP’s mind…
Hi Holger, with yours and Kirril's solution I get the same correct result 1726, 3442, 5223, 4321, 7765, 2234!!! This differs from the result list which Kirill wrote in his answer! Please put me to the right direction, if you see any issue in Kirill's solution. Many thanks in advance.
My result list is different because I used a different input to experiment with various id combinations
@ThomasMuller, by the way, my solution gives you 1726, 3442, 5223, 2234, 4321, 7765, because minimum id in New York group is 1726 and next id is Andrew's 2234 in Los Angeles group, and only then goes California
|
5

Since the result is supposed to be a list, you‘re not grouping but simply sorting (in the sense of changing the order according to a defined rule). The main obstacle is that you want the locations to be ordered after their first encounter in the original list.

The straight-forward approach is to fix this location order first, followed by a single sort operation:

Map<String,Integer> locationOrder = studlist.stream()
    .collect(HashMap::new,
             (m,s)->m.putIfAbsent(s.stud_location, m.size()),
             (m1,m2)->m2.keySet().forEach(l->m1.putIfAbsent(l, m1.size())));

studlist.sort(Comparator.comparingInt((Student s) -> locationOrder.get(s.stud_location))
                        .thenComparing(s -> s.stud_id));

If you can not or do not want to modify the original list, you can simply use a copy:

List<Student> result = new ArrayList<>(studlist);
result.sort(Comparator.comparingInt((Student s) -> locationOrder.get(s.stud_location))
                      .thenComparing(s -> s.stud_id));

It’s also possible to solve this with a grouping operation, but that’s not easier:

List<Student> result = studlist.stream()
    .collect(Collectors.collectingAndThen(
                Collectors.groupingBy(s -> s.stud_location,
                                      LinkedHashMap::new, Collectors.toList()),
                m -> m.values().stream()
                      .flatMap(l -> l.stream().sorted(Comparator.comparing(s->s.stud_id)))
                      .collect(Collectors.toList())));

Note that you have to collect into a LinkedHashMap to ensure that the order of the groups is retained.

2 Comments

Hi Holger, your solution is absolutely great! I feel bad, not being able to accept two Answers!! Many many thanks for your efforts. I've upvoted your correct answer.
very nice approach
2

You can add one line:

studlistGrouped.values().forEach(list -> list.sort(Comparator.comparing(Student::getId)));

Or you can write your own collector.

I know which one I would choose.

2 Comments

There is no guaranty that the result lists are mutable list, so you should change the collector to Collectors.groupingBy(w -> w.stud_location, Collectors.toCollection(ArrayList::new)) to ensure that this post-processing is possible.
Hi Bohemian, thanks so much for your help. After comprehensive comparison, I found that Kirill's compact solution does solve the problem directly. Holger, thanks for your hint.
2

You don't need to group by location, as the input and output have same datatype. You can just chain multiple Comparators and sort the input.

List<Student> groupedAndSorted = studlist.stream()
       .sorted(Comparator.comparing(Student::getStudLocation)
                .thenComparing(Comparator.comparing(Student::getStudId)))
                .thenComparing(Comparator.comparing(Student::getStudName)))
       .collect(Collectors.toList());

As an offtopic, I would seggest that you change you data structure to use Lombok to auto-generate getters/setters/constructors https://projectlombok.org/features/Data . You should also use a more generic naming conversion, i.e. remove the "stud_" prefix of attributes.

import lombok.Data;
@Data
class Student {
    String id;
    String name;
    String location;
}

1 Comment

thanks for your answer. I will test your solution ASAP. And thanks for the hint at Lombok.
1

try sort first and then groupinBy it works well. The below code sorts the Students within the location.

students.stream().sorted().collect(Collectors.groupingBy(Student::getLocation))
The output in this case is

{New York=[1726  John  New York, 3442  Mark  New York, 5223  Michael  New York], Los Angeles=[2234  Andrew  Los Angeles], California=[4321  Max  California, 7765  Sam  California]}

If you would like to have locations also to be sorted, use a code snippet like below

students.stream().sorted().collect(Collectors.groupingBy(Student::getLocation, TreeMap::new, Collectors.toList()))

The output in this case is {California=[4321 Max California, 7765 Sam California], Los Angeles=[2234 Andrew Los Angeles], New York=[1726 John New York, 3442 Mark New York, 5223 Michael New York]}

Student class implements Comparable and the compareTo method is is based on the id.

1 Comment

Hi Practprogrammer, thanks so much for your help. After comprehensive comparison, I found that Kirill's compact solution does solve the problem directly.
1

First, about sorting within each group. Collectors.groupingBy has a second variant which allows you to specify a collector which is used to generate the groups. The collector you specify can be one which collects the items in a sorted way (e.g. a TreeSet), and as a finishing operation transforms it to a sorted list. Such a collector can be created with Collectors.collectingAndThen().

For example, with Integers I tried:

List<Integer> list = Arrays.asList(9, 2, 43, 6, 5, 3, 87, 56, 87, 67, 77, 22, 23, 1);
System.out.println(
    list.stream().collect(Collectors.groupingBy(
        i -> i % 3,                                         // classifier
        Collectors.collectingAndThen(
            Collectors.toCollection(() -> new TreeSet<>()), // intermediate collector 
            set -> new ArrayList<>(set)))));                // finishing operation

Output:

{0=[3, 6, 9, 87], 1=[1, 22, 43, 67], 2=[2, 5, 23, 56, 77]}

I'm sure you manage to translate this to your case. You might need to create a TreeSet with a custom comparator so that the students in each group are ordered the way you want, or, if students are sorted in the same way always, make Student implement Comparable.

Second about sorting the groups. Collectors.groupingBy by default creates a HashMap, which does not have a specified order of the keys (above, the keys are ordered correctly by chance). So, to also order the keys, you need to use the Collectors.groupingBy variant which also lets you create the result map, which luckily also exists:

System.out.println(
    list.stream().collect(Collectors.groupingBy(
        i -> i % 3,                                         // classifier
        new TreeMap<>((a, b) -> b.compareTo(a)),            // map creator
        Collectors.collectingAndThen(
            Collectors.toCollection(() -> new TreeSet<>()), // intermediate collector
            set -> new ArrayList<>(set)))));                // finishing operation

I specified a custom comparator for the map to show that the ordering is indeed different. The result is:

{2=[2, 5, 23, 56, 77], 1=[1, 22, 43, 67], 0=[3, 6, 9, 87]}

Now, whether this is more readable and maintainable than a good-old pre-Java 8 solution is a matter of taste...

2 Comments

new TreeMap<>((a, b) -> b.compareTo(a)) is equivalent to new TreeMap<>(Comparator.reverseOrder()). To keep the encounter order, you need new LinkedHashMap<>() instead. Note that Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>()), set -> new ArrayList<>(set)), resp. Collectors.collectingAndThen(Collectors.toCollection(TreeSet::new), ArrayList::new) is tempting, but using Collectors.collectingAndThen(Collectors.toCollection(ArrayList::new), l->{ l.sort(null); return l; }) is more efficient for most use cases.
Hi Hoopje, thanks so much for your detailed Explanation. After comprehensive comparison, I found that Kirill's compact solution does solve the problem directly. Holger, thanks for your hint!
1

If you just want to group and sort, you don't need a groupingBy() collector, or even a stream at all. Just use a composite sort:

studlist.sort(Comparator.comparing(Student::getLocation).thenComparing(Student::getId));

5 Comments

Almost. The OP doesn’t want the list to be sorted by the location’s natural order, but the order of their first encounter.
Hi Shmosel, thanks so much for your help. After comprehensive comparison, I found that Kirill's compact solution does solve the problem directly. Holger, thanks for your hint.
@Holger OP added that after I posted this. Most of the answers here don't respect encounter order. Anyway, it seems like an odd requirement, one that OP himself ignored when selecting the answer.
@shmosel: indeed, I already commented on this at that answer.
Hi Holger, Hi shmosel: I added a comment to Kirill's answer. His answer and that from Holger lead to the same correct result! thanks again for your help.
1

I was facing same issue and I tried all the solutions but not able to fix the problem, Then I tried following way and finally I am able to fix my problem.

In above example my studlist was already sorted but still its generating map in any order that was main question.

Map<String, List<Student>> studlistGrouped =
    studlist.stream().collect(Collectors.groupingBy(w -> w.stud_location));
**studlistGrouped = new TreeMap<String, List<Student>>(studlistGrouped);**

So using this above solution it sorted all the keys in Map. Our goal to get result into Map only then why people try to converting it into list?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.