Search code examples
javacollectionslambdajava-8java-stream

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?


Solution

  • 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.