Saturday, 12 July 2014

How to sort a Map on the values in Java?

Goal

In this post we will see how can we sort a Map based on its values. We will use Comparators.

Map overview in Java -

Code :

``` /*
* author : athakur
*/

public class HelloWorld {

public static void main(String args[]) {

Map<String,Integer> nameAgeMap = new HashMap<String,Integer>();
AgeComparator ageComparator =  new AgeComparator(nameAgeMap);
Map<String,Integer> sortedNameAgeMap = new TreeMap<String,Integer>(ageComparator);

nameAgeMap.put("Name1",21);
nameAgeMap.put("Name2",16);
nameAgeMap.put("Name3",10);
nameAgeMap.put("Name4",23);

System.out.println("Original Map : "+ nameAgeMap);

sortedNameAgeMap.putAll(nameAgeMap);

System.out.println("Sorted Name Age Map : "+ sortedNameAgeMap);

}

}

class AgeComparator implements Comparator<String> {

Map<String,Integer> mapToBeCompared;

public AgeComparator(Map<String,Integer> mapToBeCompared){
this.mapToBeCompared = mapToBeCompared;
}

@Override
public int compare(String o1, String o2) {
Integer x = mapToBeCompared.get(o1);
Integer y = mapToBeCompared.get(o2);
if(x >= y) {
return 1;
}
else {
return -1;
}
}

}
```

Output :

The output is as expected

Original Map : {Name4=23, Name3=10, Name2=16, Name1=21}
Sorted Name Age Map : {Name3=10, Name2=16, Name1=21, Name4=23}

Note

1. Above comparator imposes orderings that are inconsistent with equals.
2. Do not return 0 from the comparator as it will merge the keys. Given that all keys in a Map are unique. Duplicate key entry over writes the old entry.
3. If you have your own class instead of Integer in the map then it makes sense for your class to implement comparable interface and use compareTo() directly in compare() method.
4. Collection APIs provide sort method that takes comparator as an argument. You can use that for the sorting criteria that suits your requirement.
To summarize if you want to sort objects based on natural order then use Comparable in Java and if you want to sort on some other attribute of object then use Comparator in Java.

Using Comparator on Entry

You can create a comparator on Entry which is datastructure that HashMap internally uses -

```public class SortMapByValue
{
public static boolean ASC = true;
public static boolean DESC = false;

public static void main(String[] args)
{

// Creating dummy unsorted map
Map<String, Integer> unsortMap = new HashMap<String, Integer>();
unsortMap.put("B", 55);
unsortMap.put("A", 80);
unsortMap.put("D", 20);
unsortMap.put("C", 70);

System.out.println("Before sorting......");
printMap(unsortMap);

System.out.println("After sorting ascending order......");
Map<String, Integer> sortedMapAsc = sortByComparator(unsortMap, ASC);
printMap(sortedMapAsc);

System.out.println("After sorting descindeng order......");
Map<String, Integer> sortedMapDesc = sortByComparator(unsortMap, DESC);
printMap(sortedMapDesc);

}

private static Map<String, Integer> sortByComparator(Map<String, Integer> unsortMap, final boolean order)
{

List<Entry<String, Integer>> list = new LinkedList<Entry<String, Integer>>(unsortMap.entrySet());

// Sorting the list based on values
Collections.sort(list, new Comparator<Entry<String, Integer>>()
{
public int compare(Entry<String, Integer> o1,
Entry<String, Integer> o2)
{
if (order)
{
return o1.getValue().compareTo(o2.getValue());
}
else
{
return o2.getValue().compareTo(o1.getValue());

}
}
});

// Maintaining insertion order with the help of LinkedList
Map<String, Integer> sortedMap = new LinkedHashMap<String, Integer>();
for (Entry<String, Integer> entry : list)
{
sortedMap.put(entry.getKey(), entry.getValue());
}

return sortedMap;
}

public static void printMap(Map<String, Integer> map)
{
for (Entry<String, Integer> entry : map.entrySet())
{
System.out.println("Key : " + entry.getKey() + " Value : "+ entry.getValue());
}
}
}
```