2

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}]
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
wannaBeDev
  • 516
  • 3
  • 14

2 Answers2

4

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}]
Nowhere Man
  • 19,170
  • 9
  • 17
  • 42
3

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));
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • Thank you very much. This works except for one little issue, but which is very likely my fault. I get a compile error at `this::group` cannot be referenced from a static context. I get it working using lambda `p -> group(p)` – wannaBeDev Oct 22 '21 at 20:19
  • 1
    If you run the Stream in a `static` method, then the method `group` must be `static` as well: `static String group (Person person) { ... }`. Then in the Stream, you can use a method reference as well (assuming the `Foo` class the methods are in): `Collectors.groupingBy(Foo::group)` – Nikolas Charalambidis Oct 22 '21 at 20:58