167

I want to write a comparator that will let me sort a TreeMap by value instead of the default natural ordering.

I tried something like this, but can't find out what went wrong:

import java.util.*;

class treeMap {
    public static void main(String[] args) {
        System.out.println("the main");
        byValue cmp = new byValue();
        Map<String, Integer> map = new TreeMap<String, Integer>(cmp);
        map.put("de",10);
        map.put("ab", 20);
        map.put("a",5);

        for (Map.Entry<String,Integer> pair: map.entrySet()) {
            System.out.println(pair.getKey()+":"+pair.getValue());
        }
    }
}

class byValue implements Comparator<Map.Entry<String,Integer>> {
    public int compare(Map.Entry<String,Integer> e1, Map.Entry<String,Integer> e2) {
        if (e1.getValue() < e2.getValue()){
            return 1;
        } else if (e1.getValue() == e2.getValue()) {
            return 0;
        } else {
            return -1;
        }
    }
}

I guess what am I asking is: Can I get a Map.Entry passed to the comparator?

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
vito huang
  • 4,228
  • 7
  • 26
  • 24
  • possible duplicate of [How to sort a treemap based on its values?](http://stackoverflow.com/questions/1448369/how-to-sort-a-treemap-based-on-its-values) – nandeesh Aug 18 '12 at 15:41
  • perhaps this will help you http://stackoverflow.com/questions/109383/how-to-sort-a-mapkey-value-on-the-values-in-java – Inv3r53 May 19 '10 at 10:59
  • See also: http://stackoverflow.com/questions/30425836/java-8-stream-map-to-list-of-keys-sorted-by-values – Paul Jackson Jan 29 '16 at 20:24

13 Answers13

201

You can't have the TreeMap itself sort on the values, since that defies the SortedMap specification:

A Map that further provides a total ordering on its keys.

However, using an external collection, you can always sort Map.entrySet() however you wish, either by keys, values, or even a combination(!!) of the two.

Here's a generic method that returns a SortedSet of Map.Entry, given a Map whose values are Comparable:

static <K,V extends Comparable<? super V>>
SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
    SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
        new Comparator<Map.Entry<K,V>>() {
            @Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
                int res = e1.getValue().compareTo(e2.getValue());
                return res != 0 ? res : 1;
            }
        }
    );
    sortedEntries.addAll(map.entrySet());
    return sortedEntries;
}

Now you can do the following:

    Map<String,Integer> map = new TreeMap<String,Integer>();
    map.put("A", 3);
    map.put("B", 2);
    map.put("C", 1);   

    System.out.println(map);
    // prints "{A=3, B=2, C=1}"
    System.out.println(entriesSortedByValues(map));
    // prints "[C=1, B=2, A=3]"

Note that funky stuff will happen if you try to modify either the SortedSet itself, or the Map.Entry within, because this is no longer a "view" of the original map like entrySet() is.

Generally speaking, the need to sort a map's entries by its values is atypical.


Note on == for Integer

Your original comparator compares Integer using ==. This is almost always wrong, since == with Integer operands is a reference equality, not value equality.

    System.out.println(new Integer(0) == new Integer(0)); // prints "false"!!!

Related questions

Community
  • 1
  • 1
polygenelubricants
  • 376,812
  • 128
  • 561
  • 623
  • 6
    if you add map.put("D", 2); the result is still "[C=1, B=2, A=3]" and not "[C=1, B=2, D=2, A=3]" – Igor Milla Apr 21 '11 at 08:49
  • 3
    Fix: int res = e2.getValue().compareTo(e1.getValue()); return res != 0 ? res : 1; – beshkenadze Feb 24 '12 at 06:44
  • new Integer(0) == new Integer(0) You do compare a references to an objects, and You do create two objects! The out put is absolutely correct and predictable. – Yuriy Chernyshov Jun 12 '14 at 20:17
  • 3
    "Generally speaking, the need to sort a map's entries by its values is atypical" I’m a beginner here but i feel like it should be a very common thing to do? For example if i want to retrieve the 3 oldest persons in a map {"ana"=37, "peter"=43, "john"=4, "mary"=15, "Matt"=78} – fartagaintuxedo Jul 09 '15 at 11:50
  • @igormilla There is a simple reason, why your code works: Autoboxing uses Integer.valueOf. And that has a cache of instances for -128 to 127! – jokster Aug 24 '18 at 06:14
  • If I could suggest an updated code for that util, it would look like `static > SortedSet> entriesSortedByValues(Map map) { SortedSet> sortedEntries = new TreeSet<>(Map.Entry.comparingByValue()); sortedEntries.addAll(map.entrySet()); return sortedEntries; }` – Naman Jul 14 '20 at 11:58
  • I'd recommend that the test code here include a duplicate-value case like @IgorMilla's ("D", 2) so we can immediately confirm that we don't lose duplicate values on the sort (i.e., regression-test for the bug we fixed early on). – Daniel R. Collins Oct 27 '20 at 04:34
78

polygenelubricants answer is almost perfect. It has one important bug though. It will not handle map entries where the values are the same.

This code:...

Map<String, Integer> nonSortedMap = new HashMap<String, Integer>();
nonSortedMap.put("ape", 1);
nonSortedMap.put("pig", 3);
nonSortedMap.put("cow", 1);
nonSortedMap.put("frog", 2);

for (Entry<String, Integer> entry  : entriesSortedByValues(nonSortedMap)) {
    System.out.println(entry.getKey()+":"+entry.getValue());
}

Would output:

ape:1
frog:2
pig:3

Note how our cow dissapeared as it shared the value "1" with our ape :O!

This modification of the code solves that issue:

static <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
        SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
            new Comparator<Map.Entry<K,V>>() {
                @Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
                    int res = e1.getValue().compareTo(e2.getValue());
                    return res != 0 ? res : 1; // Special fix to preserve items with equal values
                }
            }
        );
        sortedEntries.addAll(map.entrySet());
        return sortedEntries;
    }
Olof Larsson
  • 901
  • 6
  • 7
  • 2
    -1. **AFASIK no `Set` implementation contain an element more than once.** You just violated that constraint. From the `SortedSet` API: *Note that the ordering maintained by a sorted set **must be consistent with equals**...*. The solution would be to change to a `List` implementation. – dacwe Dec 12 '12 at 17:42
  • 4
    @dacwe equal values not keys – bellum Jan 08 '13 at 13:45
  • 1
    @bellum: We are talking about `Set`s here. Since the `Comparator` violates the `Set` contract `Set.remove`, `Set.contains` etc **doesn't work**! Check this [example at ideone](http://ideone.com/1CPFxc). – dacwe Jan 08 '13 at 15:47
  • 3
    Just a note, if you switch `int res= e1.getValue().compareTo(e2.getValue());` into `int res= e2.getValue().compareTo(e1.getValue());`, you have a descending values order instead of ascending. – tugcem Mar 15 '13 at 09:55
  • 6
    I would rather return `res != 0 ? res : e1.getKey().compareTo(e2.getKey())` to preserve the order of the keys with equal values. – Marco Lackovic May 01 '13 at 15:53
  • most of this is necessary ... in combination withe baove answeer..becuse how do u sort back the sorted list intp k,V> value pair – Develop4Life Jul 14 '14 at 13:01
  • i think if depends how you define same or different. in this context cow and ape are totally different so this fix must be there . so this does not really violate any condition , you have control on what is same or different using a comparator @dacwe – human.js Nov 16 '16 at 04:29
70

In Java 8:

LinkedHashMap<Integer, String> sortedMap = map.entrySet().stream()
  .sorted(Map.Entry.comparingByValue(/* Optional: Comparator.reverseOrder() */))
  .collect(Collectors.toMap(Map.Entry::getKey,
                            Map.Entry::getValue,
                            (e1, e2) -> e1, LinkedHashMap::new));
Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
Vitalii Fedorenko
  • 110,878
  • 29
  • 149
  • 111
21

A TreeMap is always sorted by the keys, anything else is impossible. A Comparator merely allows you to control how the keys are sorted.

If you want the sorted values, you have to extract them into a List and sort that.

Michael Borgwardt
  • 342,105
  • 78
  • 482
  • 720
11

This can't be done by using a Comparator, as it will always get the key of the map to compare. TreeMap can only sort by the key.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
  • 2
    if the TreeMap will only pass the key to Comparator, would it feasible if i make a reference of the TreeMap in comparator's constructor, then using the key to get the value, something like this(not sure how to pass by reference) : class byValue implements Comparator { TreeMap theTree; public byValue(TreeMap theTree) { this.theTree = theTree; } public int compare(String k1, String k2) { //use getKey method of TreeMap to the value } } – vito huang May 19 '10 at 11:29
  • 1
    @vito: no, because usually one of the two keys will not yet be in the map and you won't be able to get its value. – Joachim Sauer May 19 '10 at 11:31
  • Isn't this an example of doing this within the Comparator of TreeMap? (even though, as mentioned above, this does break the specification for `SortedMap` which specifies sorting by keys) https://beginnersbook.com/2014/07/how-to-sort-a-treemap-by-value-in-java/ – Marcus Sep 13 '18 at 16:06
  • @Marcus: that `Comparator` uses an *existing* map to get the values to sort by. In other words, it can't sort arbitrary values put into the `TreeMap` later on, only values that are already in the original Map. – Joachim Sauer Nov 18 '19 at 09:46
4

Olof's answer is good, but it needs one more thing before it's perfect. In the comments below his answer, dacwe (correctly) points out that his implementation violates the Compare/Equals contract for Sets. If you try to call contains or remove on an entry that's clearly in the set, the set won't recognize it because of the code that allows entries with equal values to be placed in the set. So, in order to fix this, we need to test for equality between the keys:

static <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
    SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
        new Comparator<Map.Entry<K,V>>() {
            @Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
                int res = e1.getValue().compareTo(e2.getValue());
                if (e1.getKey().equals(e2.getKey())) {
                    return res; // Code will now handle equality properly
                } else {
                    return res != 0 ? res : 1; // While still adding all entries
                }
            }
        }
    );
    sortedEntries.addAll(map.entrySet());
    return sortedEntries;
}

"Note that the ordering maintained by a sorted set (whether or not an explicit comparator is provided) must be consistent with equals if the sorted set is to correctly implement the Set interface... the Set interface is defined in terms of the equals operation, but a sorted set performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the sorted set, equal." (http://docs.oracle.com/javase/6/docs/api/java/util/SortedSet.html)

Since we originally overlooked equality in order to force the set to add equal valued entries, now we have to test for equality in the keys in order for the set to actually return the entry you're looking for. This is kinda messy and definitely not how sets were intended to be used - but it works.

Halogen
  • 551
  • 6
  • 11
3

I know this post specifically asks for sorting a TreeMap by values, but for those of us that don't really care about implementation but do want a solution that keeps the collection sorted as elements are added, I would appreciate feedback on this TreeSet-based solution. For one, elements are not easily retrieved by key, but for the use case I had at hand (finding the n keys with the lowest values), this was not a requirement.

  TreeSet<Map.Entry<Integer, Double>> set = new TreeSet<>(new Comparator<Map.Entry<Integer, Double>>()
  {
    @Override
    public int compare(Map.Entry<Integer, Double> o1, Map.Entry<Integer, Double> o2)
    {
      int valueComparison = o1.getValue().compareTo(o2.getValue());
      return valueComparison == 0 ? o1.getKey().compareTo(o2.getKey()) : valueComparison;
    }
  });
  int key = 5;
  double value = 1.0;
  set.add(new AbstractMap.SimpleEntry<>(key, value));
Paul Jackson
  • 2,077
  • 2
  • 19
  • 29
2

A lot of people hear adviced to use List and i prefer to use it as well

here are two methods you need to sort the entries of the Map according to their values.

    static final Comparator<Entry<?, Double>> DOUBLE_VALUE_COMPARATOR = 
        new Comparator<Entry<?, Double>>() {
            @Override
            public int compare(Entry<?, Double> o1, Entry<?, Double> o2) {
                return o1.getValue().compareTo(o2.getValue());
            }
        };

        static final List<Entry<?, Double>> sortHashMapByDoubleValue(HashMap temp)
        {
            Set<Entry<?, Double>> entryOfMap = temp.entrySet();

            List<Entry<?, Double>> entries = new ArrayList<Entry<?, Double>>(entryOfMap);
            Collections.sort(entries, DOUBLE_VALUE_COMPARATOR);
            return entries;
        }
zina
  • 144
  • 11
2
import java.util.*;

public class Main {

public static void main(String[] args) {
    TreeMap<String, Integer> initTree = new TreeMap();
    initTree.put("D", 0);
    initTree.put("C", -3);
    initTree.put("A", 43);
    initTree.put("B", 32);
    System.out.println("Sorted by keys:");
    System.out.println(initTree);
    List list = new ArrayList(initTree.entrySet());
    Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
        @Override
        public int compare(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
            return e1.getValue().compareTo(e2.getValue());
        }
    });
    System.out.println("Sorted by values:");
    System.out.println(list);
}
}
0
//convert HashMap into List   
List<Entry<String, Integer>> list = new LinkedList<Entry<String, Integer>>(map.entrySet()); 

Collections.sort(list, (o1, o2) -> o1.getValue().compareTo(o2.getValue()));
Kanagavelu Sugumar
  • 18,766
  • 20
  • 94
  • 101
0

If you want to use a Hash map you can add a condition in Comparator to check by values first & if values are equal perform a sort on keys

HashMap<String , Integer> polpularity = new HashMap<>();       
       List<String> collect = popularity.entrySet().stream().sorted((t2, t1) -> {
            if (t2.getValue() > t1.getValue()) {
                return -1;

            } else if (t2.getValue() < t1.getValue()) {
                return +1;

            } else {
                return t2.getKey().compareTo(t1.getKey());
            }
        }).map(entry -> entry.getKey()).collect(Collectors.toList());

If you don't want to take care of the latter condition then use a Treemap which will offer you sorting by itself, this can be done in an elegant single line of code:

 TreeMap<String, Integer> popularity = new TreeMap<>();

 List<String> collect = popularity.entrySet().stream().sorted(Collections.reverseOrder(Map.Entry.comparingByValue())).map(entry -> entry.getKey()).collect(Collectors.toList());
Gru
  • 67
  • 1
  • 7
0

TreeMap is always sorted by the keys.
If you want TreeMap to be sorted by the values, so you can simply construct it also.

Example:

// the original TreeMap which is sorted by key
Map<String, Integer> map = new TreeMap<>();
map.put("de",10);
map.put("ab", 20);
map.put("a",5);

// expected output:
// {a=5, ab=20, de=10}
System.out.println(map);



// now we will constrcut a new TreeSet which is sorted by values 
// [original TreeMap values will be the keys for this new TreeMap]
TreeMap<Integer, String> newTreeMapSortedByValue = new TreeMap();
treeMapmap.forEach((k,v) -> newTreeMapSortedByValue.put(v, k));

// expected output:
// {5=a, 10=de, 20=ab}
System.out.println(newTreeMapSortedByValue);
Ahmed Nabil
  • 17,392
  • 11
  • 61
  • 88
0

Only 1 Line Of Code Solution

Normal Order

map.entrySet().stream().sorted(Map.Entry.comparingByValue()).forEach(x->{});

Reverse Order

map.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).forEachOrdered(x -> {});