Garret Wilson
Garret Wilson

Reputation: 21356

Java 8 re-map with modified value

I'm about to get a grasp on the new Java 8 stream and lambda options, but there are still a few subtleties that I haven't yet wrapped my mind around.

Let's say that I have a map where the keys are the names of people. The value for each name is a map of ages and Person instances. Further assume that there does not exist more than one person with the same name and age.

Map<String, NavigableMap<Long, Person>> names2PeopleAges = new HashMap<String, NavigableMap<Long, Person>>();

After populating that map (elsewhere), I want to produce another map of the oldest person for each name. I want to wind up with a Map<String, Person> in which the keys are identical to those in the first map, but the value for each entry is the value of the value map for which the key of the value map has the highest number.

Taking advantage of the fact that a NavigableMap sorts its keys, I can do this:

Map<String, Person> oldestPeopleByName = new HashMap<String, Person>();
names2PeopleAges.forEach((name, peopleAges) -> {
  oldestPeopleByName.put(name, peopleAges.lastEntry().getValue());
});

Question: Can I replace the last bit of code above with a single Java 8 stream/collect/map/flatten/etc. operation to produce the same result? In pseudo-code, my first inclination would be:

Map<String, Person> oldestPeopleByName = names2PeopleAges.forEachEntry().mapValue(value->value.lastEntry().getValue());

This question is meant to be straightforward without any tricks or oddities---just a simple question of how I can fully leverage Java 8!

Bonus: Let's say that the NavigableMap<Long, Person> above is instead merely a Map<Long, Person>. Could you extend the first answer so that it collects the person with the highest age value, now that NavigableMap.lastEntry() is not available?

Upvotes: 25

Views: 44419

Answers (2)

Hakan54
Hakan54

Reputation: 3871

A similar question is asked before, and I provided there my answers https://stackoverflow.com/a/75004577/6777695 The answer there is slighty different than the one accepted here. I think the remapping of a key or/and value should belong in the map part of a stream instead of the collect as the map function for a stream is designed to do the actual transforming of data. In your use case I would suggest the following code snippet:

import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

public class App {

    public static void main(String[] args) {
        Map<String, Person> oldestPeopleByName = namesToPeopleAges.entrySet().stream()
                .map(entry -> Map.entry((entry.getKey(), entry.getValue().lastEntry().getValue()))
                .collect(Collectors.toMap(Entry::getKey, Entry::getValue));                
    }

}

Upvotes: 1

Eran
Eran

Reputation: 393841

You can create a Stream of the entries and collect it to a Map :

Map<String, Person> oldestPeopleByName = 
    names2PeopleAges.entrySet()
                    .stream()
                    .collect (Collectors.toMap(e->e.getKey(),
                                               e->e.getValue().lastEntry().getValue())
                             );

Now, without lastEntry :

Map<String, Person> oldestPeopleByName = 
    names2PeopleAges.entrySet()
                    .stream()
                    .collect (Collectors.toMap(e->e.getKey(),
                                               e->e.getValue().get(e.getValue().keySet().stream().max(Long::compareTo)))
                             );

Here, instead of relying on lastEntry, we search for the max key in each of the internal Maps, and get the corresponding Person of the max key.

I might have some silly typos, since I haven't actually tested it, by in principle it should work.

Upvotes: 47

Related Questions