Sort after values in an Object inside HashMap

I had to code my own ´Filter` for a medal table, which I put in a

HashMap <String, MedalsOfCountry> unsortedList.

The key is the code of a country, for example:”USA” and the MedalsOfCountry is the object for the specific country with the fields: goldCount, silverCount, bronzeCount. MedalsOfCountry is a static inner class, which I coded and looks like this:

static class MedalsOfCountry extends Filter {
    int goldCount;
    int silverCount;
    int bronzeCount;
    Medal medalType;

    MedalsOfCountry(Medal m, int count) {
        medalType = m;
        if (medalType == Medal.GOLD) {
            goldCount += count;
        } else if (medalType == Medal.SILVER) {
            silverCount+= count;
        } else {
            bronzeCount += count;
        }
    }

    public int compareTo(MedalOfCountry medals) {
        if(this.goldCount == medals.goldCount) {
            return this.silverCount - medals.silverCount;
        } else if(this.silverCount == medals.silverCount) {
            return this.bronzeCount - medals.bronzeCount;
        }
        return this.goldCount - medals.goldCount;
    }

Anyway I tried to sort my HashMap like this:

Map<String, MedalsOfCountry> sortedList = new TreeMap<String, MedalsOfCountry>(new Comparator<String>() {
        @Override
        public int compare(String land1, String land2) {
            return unsortedList.get(land1).compareTo(unsortedList.get(land2));
        }

Why does my sorting not work?

Answer

A TreeMap is sorted by keys:

The map is sorted according to the natural ordering of its keys, or by a Comparator provided at map creation time, depending on which constructor is used.

So using a TreeMap would not be the best choice in your case. You could either use a LinkedHashMap or changing your map to a List.

A LinkedHashMap is sorted by insertion order:

This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order).

Map<String, MedalsOfCountry> sorted = unsorted.entrySet().stream()
    .sorted((entry0, entry1) -> entry0.getValue().compareTo(entry1.getValue()))
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v0, v1) -> {
        throw new IllegalArgumentException("duplicate keys are not possible here");
    }, LinkedHashMap::new));

To improve this I would recommend to implement the Comparable interface in your Object class:

static class MedalsOfCountry implements Comparable<MedalsOfCountry> {
    int goldCount;
    int silverCount;
    int bronzeCount;

    MedalsOfCountry(int count) {
        goldCount += count;
    }

    @Override
    public int compareTo(MedalsOfCountry medals) {
        int compareGold = this.goldCount - medals.goldCount;
        if (compareGold != 0) {
            return compareGold;
        }
        int compareSilver = this.silverCount - medals.silverCount;
        if (compareSilver != 0) {
            return compareSilver;
        }
        return this.bronzeCount - medals.bronzeCount;
    }
}

You then can use the following to create the sorted map:

Map<String, MedalsOfCountry> sorted = unsorted.entrySet().stream()
    .sorted(Comparator.comparing(Map.Entry::getValue))
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v0, v1) -> {
        throw new IllegalArgumentException("duplicate keys are not possible here");
    }, LinkedHashMap::new));

Additionally you can improve your compareTo() method by using a comparator to define multiple steps:

@Override
public int compareTo(MedalsOfCountry medals) {
    return Comparator.comparingInt(MedalsOfCountry::getGoldCount)
        .thenComparingInt(MedalsOfCountry::getSilverCount)
        .thenComparingInt(MedalsOfCountry::getBronzeCount)
        .compare(this, medals);
}

To also can use the Comparator.reversed() method to reverse the order of your stream:

Map<String, MedalsOfCountry> sorted = unsorted.entrySet().stream()
    .sorted(Comparator.comparing(Map.Entry<String, MedalsOfCountry>::getValue).reversed())
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v0, v1) -> {
        throw new IllegalArgumentException("duplicate keys are not possible here");
    }, LinkedHashMap::new));

Leave a Reply

Your email address will not be published. Required fields are marked *