Home > database >  How to aggregate grouped entities after stream groupingBy
How to aggregate grouped entities after stream groupingBy

Time:10-23

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}]

CodePudding user response:

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}]

CodePudding user response:

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));
  • Related