Search code examples
javacollectionstreemap

Ordering the Keys in a TreeMap dependant on the oldest value in its list of values?


I have a TreeMap as follows:

  TreeMap<Parent, List<Child>> mapOfParentsAndChilds = new TreeMap<Parent, List<Child>>();

I want to order the Map by the following condition:

The First key (Parent object) in the Map will be the one that has the oldest child in their list of values.

i.e. If the oldest child in Parent A's list of values was 20 years old, and the oldest child in Parent B's list of values was 19 years old then Parent A should be before Parent B in the map and so on.

How can I implement this solution?

Child entity:

@Entity
@Table(name = "CHILD")
public class Child
{
    //other vars 

    @Column(name = "AGE")
    private int age;

}

Solution

  • To create a map with custom sorting you can use the constructor which takes a Comparator. A comparator lets you decide how to order keys.

    In Java 8 Comparator.comparing was added, which makes creating a comparator using the new streaming feature easy. If you're not familiar with Java 8 streams, read up on them. They're incredibly powerful and convenient.

    Map<Parent, List<Children>> mapOfParentsAndChildren = new TreeMap<>(
        Comparator.comparing(parent ->
            parent.getChildren().stream()
                .mapToInt(Child::getAge)
                .min().orElse(0)
        )
        .thenComparing(System::identityHashCode)
    );
    

    Notice that the list of children needs to be accessible from the parent object. You can't order a map's keys by a property of the keys' corresponding values because map values can change.

    @assylias: If two parents have oldest children of the same age, only one will be kept in the map because they will be deemed equal.

    To fix that, we can chain an additional comparator using thenComparing(). This extra comparator is used if the first one says the oldest children are equal. You can add in whatever additional criteria you want to decide which parent wins in that case. I threw in System::identityHashCode as a tie breaker. identityHashCode returns an arbitrary, yet consistent integer. In essence I'm using it to ensure different parents compare unequal, but which one goes first is arbitrary.

    If you have some better criterion to use, I encourage you to change this. For instance, if each parent has a unique name, .thenComparing(Parent::getName) would be better.