Govinda Sakhare
Govinda Sakhare

Reputation: 5726

Collectors.toMap write a merge function on a different attribute of object than the one which is not used as value

I need to create Map<String, String> from List<Person> using Stream API.

persons.stream()
       .collect(Collectors
            .toMap(Person::getNationality, Person::getName, (name1, name2) -> name1)

But in the above case, I want to resolve conflict in name attribute by using Person's age. is there any way to pass merge function something around the lines (age1, age2) -> // if age1 is greater than age2 return name1, else return name2 ?

Upvotes: 13

Views: 28584

Answers (3)

Holger
Holger

Reputation: 298539

To select a person based on its age, you need the Person instance to query the age. You cannot reconstitute the information after you mapped the Person to a plain name String.

So you have to collect the persons first, to be able to select the oldest, followed by mapping them to their names:

persons.stream()
    .collect(Collectors.groupingBy(Person::getNationality, Collectors.collectingAndThen(
        Collectors.maxBy(Comparator.comparingInt(Person::getAge)),
        o -> o.get().getName())));

Upvotes: 14

Adrian
Adrian

Reputation: 3134

order the elements of stream by age and then just choose first:

persons.stream()
       .sorted(Comparator.comparing(Person::getAge).reversed())
       .collect(Collectors.toMap(Person::getNationality, Person::getName, (n1, n2) -> n1));

Upvotes: 5

sfiss
sfiss

Reputation: 2329

If you don't want to use a helper data structure, it is possible if you first keep your Person info and perform the merge based on it and apply the mapping afterwards:

public void test() {
    final List<Person> persons = new ArrayList<>();

    final BinaryOperator<Person> mergeFunction =
        (lhs, rhs) -> lhs.getAge() > rhs.getAge() ? lhs : rhs;

    final Function<Person, String> mapFunction = Person::getName;

    final Map<String, String> personNamesByNation =
        persons.stream()
            .collect(
                Collectors.groupingBy(Person::getNation, // KeyMapper Person.getNation: Map<String, List<Person>>
                    Collectors.collectingAndThen(
                        Collectors.collectingAndThen(
                            Collectors.reducing(mergeFunction), // Merge Persons into single value via merge function: Map<String, Optional<Person>>
                            Optional::get), // unwrap value: Map<String, Person>
                        mapFunction))); // apply map function afterwards: Map<String, String>
}

Upvotes: 3

Related Questions