In Java, we can use the TreeMap
class to sort a map by its keys. This class is very handy to use. However, sometimes we need to sort a map by its values. How to sort a map by its values is a most frequently asked question by Java programmers. In this post, I will develop the best way to write such a method.
1. Naive Method
The following is a solution to sort a map of <String, Integer> pairs. This is often used in counting frequency of words.
import java.util.Comparator; import java.util.HashMap; import java.util.TreeMap; public class SortMapByValue { public static void main(String[] args) { HashMap<String, Integer> map = new HashMap<String, Integer>(); map.put("a", 10); map.put("b", 30); map.put("c", 50); map.put("d", 40); map.put("e", 20); System.out.println(map); TreeMap<String, Integer> sortedMap = sortMapByValue(map); System.out.println(sortedMap); } public static TreeMap<String, Integer> sortMapByValue(HashMap<String, Integer> map){ Comparator<String> comparator = new ValueComparator(map); //TreeMap is a map sorted by its keys. //The comparator is used to sort the TreeMap by keys. TreeMap<String, Integer> result = new TreeMap<String, Integer>(comparator); result.putAll(map); return result; } } |
Here is the comparator class implementation.
// a comparator that compares Strings class ValueComparator implements Comparator<String>{ HashMap<String, Integer> map = new HashMap<String, Integer>(); public ValueComparator(HashMap<String, Integer> map){ this.map.putAll(map); } @Override public int compare(String s1, String s2) { if(map.get(s1) >= map.get(s2)){ return -1; }else{ return 1; } } } |
In this solution, we use a TreeMap to sort the map. When creating the TreeMap, we give it a comparator. The comparator accepts strings and compare the assocated values of the given string key in the map.
The method works well, but it only works for sorting String and Integer pairs. If we want to sort a map with other types of keys and values, it needs to be rewritten. Therefore, a more general solution is preferred.
2. More General Solution
We can ignore the generic type and make the methods work for any types like the following.
import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; public class Solution { public static void main(String[] args) { HashMap<String, Integer> map = new HashMap<String, Integer>(); map.put("a", 10); map.put("b", 30); map.put("c", 50); map.put("d", 40); map.put("e", 20); System.out.println(map); Map sortedMap = sortByValue(map); System.out.println(sortedMap); } public static Map sortByValue(Map unsortedMap) { Map sortedMap = new TreeMap(new ValueComparator(unsortedMap)); sortedMap.putAll(unsortedMap); return sortedMap; } } class ValueComparator implements Comparator { Map map; public ValueComparator(Map map) { this.map = map; } public int compare(Object keyA, Object keyB) { Comparable valueA = (Comparable) map.get(keyA); Comparable valueB = (Comparable) map.get(keyB); return valueB.compareTo(valueA); } } |
The solution is not type-safe, we need a type-safe and general solution.
3. Using Generic Types
public class SortMapByValue { public static void main(String[] args) { // <String, Integer> Map HashMap<String, Integer> map = new HashMap<String, Integer>(); map.put("a", 10); map.put("b", 30); map.put("c", 50); map.put("d", 40); map.put("e", 20); System.out.println(map); Comparator<String> comparator = new ValueComparator<String, Integer>(map); TreeMap<String, Integer> result = new TreeMap<String, Integer>(comparator); result.putAll(map); System.out.println(result); // <Integer, Integer> Map HashMap<Integer, Integer> map2 = new HashMap<Integer, Integer>(); map2.put(1, 10); map2.put(2, 30); map2.put(3, 50); map2.put(4, 40); map2.put(5, 20); System.out.println(map2); Comparator<Integer> comparator2 = new ValueComparator<Integer, Integer>(map2); TreeMap<Integer, Integer> result2 = new TreeMap<Integer, Integer>(comparator2); result2.putAll(map2); System.out.println(result2); } } |
// a comparator using generic type class ValueComparator<K, V extends Comparable<V>> implements Comparator<K>{ HashMap<K, V> map = new HashMap<K, V>(); public ValueComparator(HashMap<K, V> map){ this.map.putAll(map); } @Override public int compare(K s1, K s2) { return -map.get(s1).compareTo(map.get(s2));//descending order } } |
4. Another Way of Using Generic Types
public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) { List<Map.Entry<K, V>> list = new LinkedList<>(map.entrySet()); Collections.sort(list, new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) { return (e1.getValue()).compareTo(e2.getValue()); } }); Map<K, V> result = new LinkedHashMap<>(); for (Map.Entry<K, V> entry : list) { result.put(entry.getKey(), entry.getValue()); } return result; } |
5. Simple
When need a simple solution, sorting can be done in one line.
LinkedList<Map.Entry<String, Integer>> list = new LinkedList<>(counter.entrySet()); Comparator<Map.Entry<String, Integer>> comparator = Comparator.comparing(Map.Entry::getValue); Collections.sort(list, comparator.reversed()); |
You’re right
To make other examples work I have to add some code to avoid that Comparator.compare() returns a zero.
like this:
return valueB.compareTo(valueA) > 0 ? 1 : -1;
Only first solutions works well. If you want to sort a Map with many the same values then they will be deleted. for examplehere D will be delete:
HashMap hm = new HashMap();
hm.put(“A”, 8);
hm.put(“B”, 4);
hm.put(“C”, 9);
hm.put(“D”, 4);
There is a bug in example 3. If two or more k/v pair have the same value the only one k/v pair is added to result.
In “2. More General Solution”, why are you passing unsorted map essentially TWO times?
here (1):
Map sortedMap = new TreeMap(new ValueComparator(unsortedMap));
and here(2):
sortedMap.putAll(unsortedMap);
Why do you do (2) ? To rely (while comparing) on whatever values there are in the initial unsortedMap? But..isn’t this solution error-prone? Who guarantees that in (2) the map that you pass will contain the same keys? Also, isn’t this solution making the client of the code remember that they also need to pass in .putAll() an unsortedMap once again?
The provided solution will return a NullPointerException if we tried to get a object using invalid (not found) key. Since the get method internally calls the compare method which tries to get the value from the map using the invalid key.
Hi, you can also use a generic version for sorting HashMap by values. I have discussed it here http://goo.gl/WvOBfZ
Thanks, I think this is the most simple way to sort Hashmap by value, you only need to worry about classes which do not implement comparator.
Note: below code will work to sort HashMap values.. no need to worry about generics.
import java.util.*;
public class SortHashMapValues{
public static void main(String []args){
Map map=new HashMap();
map.put(“one”,1);
map.put(“ten”,10);
map.put(“three”,3);
map.put(“two”,2);
List list=new ArrayList(map.entrySet());
Collections.sort(list,new Comparator(){
public int compare(Object obj1, Object obj2){
return ((Comparable)((Map.Entry)(obj1)).getValue
()).compareTo(((Map.Entry)(obj2)).getValue());
}
});
System.out.println(list);
}
}
This is interesting but there is many warning because there is no generic type. This is a version of More General Solution with all the generic type :
package misc;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
public class ArrayTools
{
private static class ValueComparator<K , V extends Comparable> implements Comparator
{
Map map;
public ValueComparator(Map map) {
this.map = map;
}
@Override
public int compare(K keyA, K keyB) {
Comparable valueA = map.get(keyA);
V valueB = map.get(keyB);
return valueA.compareTo(valueB);
}
}
public static<K, V extends Comparable> Map sortByValue(Map unsortedMap)
{
Map sortedMap = new
TreeMap(new ValueComparator(unsortedMap));
sortedMap.putAll(unsortedMap);
return sortedMap;
}
}