wannaBeDev
wannaBeDev

Reputation: 739

How to aggregate grouped entities after stream groupingBy

I have a simple class Person:

class Person {
    String firstName;
    String lastName;
    //getter, setter, constructor, toString
}

And an input list of Persons like:

List<Person> myList = List.of(
        new Person("Helena", "Graves"),
        new Person("Jasmine", "Knight"),
        new Person("Phoebe", "Reyes"),
        new Person("Aysha", "Graham"),
        new Person("Madeleine", "Jenkins"),
        new Person("Christina", "Johnson"),
        new Person("Melissa", "Carpenter"),
        new Person("Marie", "Daniel"),
        new Person("Robin", "French"),
        new Person("Tamara", "Wyatt"),
        new Person("Freya", "Montgomery"),
        new Person("Lacey", "Todd"),
        new Person("Heather", "Parker"),
        new Person("Lauren", "Wright"),
        new Person("Annie", "Bradley")
);

Now I need to group the above list by the first character of the person's lastnames and again group the groups such that all last names which start between A-H fall in one group, the next group for those which start with I-N and lastly with O-Z.

I can already group the list by first character of last name:

myList.stream()
        .collect(Collectors.groupingBy(p -> String.valueOf(p.getLastName().charAt(0))))
        .entrySet()
        .forEach(System.out::println);

Which gives me :

P=[Person{Heather, Parker}]
B=[Person{Annie, Bradley}]
R=[Person{Phoebe, Reyes}]
C=[Person{Melissa, Carpenter}]
T=[Person{Lacey, Todd}]
D=[Person{Marie, Daniel}]
F=[Person{Robin, French}]
W=[Person{Tamara, Wyatt}, Person{Lauren, Wright}]
G=[Person{Helena, Graves}, Person{Aysha, Graham}]
J=[Person{Madeleine, Jenkins}, Person{Christina, Johnson}]
K=[Person{Jasmine, Knight}]
M=[Person{Freya, Montgomery}]

Have difficulties how to proceed from here since I need to aggregate the above further to get a map with three entries/keys. Desired output:

Map<String, List<Person>> result = ...

A-H = [Person{Helena, Graves}, Person{Aysha, Graham}, Person{Melissa, Carpenter}, Person{Marie, Daniel}, Person{Robin, French}, Person{Annie, Bradley}]
I-N = [Person{Jasmine, Knight}, Person{Madeleine, Jenkins}, Person{Christina, Johnson}, Person{Freya, Montgomery}]
O-Z = [Person{Phoebe, Reyes}, Person{Tamara, Wyatt}, Person{Lacey, Todd}, Person{Heather, Parker}, Person{Lauren, Wright}]

Upvotes: 2

Views: 375

Answers (2)

Nowhere Man
Nowhere Man

Reputation: 19545

You should just slightly change the classifier function to combine a range of chars.

Also, it may be needed to sort the entrySet() (or use SortedMap/TreeMap when collecting to the map):

myList.stream()
      .collect(Collectors.groupingBy(
          p -> p.getLastName().charAt(0) < 'I' ? "A-H" : 
               p.getLastName().charAt(0) < 'O' ? "I-N" : "O-Z"
      ))
      .entrySet()
      .stream()
      .sorted(Map.Entry.comparingByKey())
      .forEach(System.out::println);

Output:

A-H=[Person {Helena Graves}, Person {Aysha Graham}, Person {Melissa Carpenter}, Person {Marie Daniel}, Person {Robin French}, Person {Annie Bradley}]
I-N=[Person {Jasmine Knight}, Person {Madeleine Jenkins}, Person {Christina Johnson}, Person {Freya Montgomery}]
O-Z=[Person {Phoebe Reyes}, Person {Tamara Wyatt}, Person {Lacey Todd}, Person {Heather Parker}, Person {Lauren Wright}]

Upvotes: 4

Nikolas
Nikolas

Reputation: 44368

Basically, all you need is grouping using Collectors.groupBy(Function) and a function that assigns each Person into a correct group:

/**
 * This method is null-friendly
 */
String group(Person person) {
    return Optional.ofNullable(person)
        .map(Person::getFirstName)
        .filter(name -> name.length() > 0)
        .map(name -> name.charAt(0))
        .map(ch -> {
            if (ch >= 'A' && ch <= 'H') {
                return "A-H";
            } else if (ch > 'H' && ch <= 'N') {
                return "I-N";
            } else if (ch > 'N' && ch <= 'Z') {
                return "O-Z";
            }
            return "*";   // In case the name starts with a character out of A-Z range
        })
        .orElse("none");  // In case there is empty/null firstName
}
Map<String, List<Person>> map = myList
        .stream()
        .collect(Collectors.groupingBy(this::group));

Upvotes: 3

Related Questions