Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I do a secondary sorting on a list of maps

Lets suppose I have the following list of maps

[{id:1,count:2,name:xyz},
 {id:2,count:3,name:def},
 {id:3,count:2,name:abc},
 {id:4,count:5,name:ghj}
]

I first want to sort this map by count and then by name:

Desired Output :

[{id:3,count:2,name:abc},
 {id:1,count:2,name:xyz},
 {id:2,count:3,name:def},
 {id:4,count:5,name:ghj}
]

I tried the following to perform the first sorting,but unable to sort using name after sorting by count

Collections.sort(list, new Comparator() {
      public int compare(Object o1, Object o2) {
           return ((Comparable) ((Map.Entry) (o1)).getValue())
          .compareTo(((Map.Entry) (o2)).getValue());
      }
like image 608
Meghashyam Sandeep V Avatar asked Sep 09 '15 06:09

Meghashyam Sandeep V


People also ask

What is secondary sorting in MapReduce?

Secondary sorting in MapReduce framework is a technique to sort the output of the reducer based on the values unlike the default one where output of the MapReduce framework is sorted based on the key of the mapper/reducer.

What is a secondary sort?

Secondary sorting means sorting to be done on two or more field values of the same or different data types. Additionally we might also have deal with grouping and partitioning. The best and most efficient way to do secondary sorting in Hadoop is by writing our own key class.

Which is called the secondary sort key?

A new sort key can be created from two or more sort keys by lexicographical order. The first is then called the primary sort key, the second the secondary sort key, etc. For example, addresses could be sorted using the city as primary sort key, and the street as secondary sort key.


3 Answers

With Java 1.8, I would use the new Comparator methods (although the lack of Type inference makes it necessary to declare all types, reducing the lisibility):

    final Comparator<Map<String, Comparable<Object>>> nameThenCountComparator = Comparator.<Map<String, Comparable<Object>>, Comparable<Object>> comparing(
            m -> m.get("name")).thenComparing(Comparator.<Map<String, Comparable<Object>>, Comparable<Object>> comparing(
            m -> m.get("count")));

With Java 1.7, I would probably use a chainedComparator (see Apache's ComparatorUtils or Guava's Ordering) and a custom MapValueComparator (there are probably one in common libraries, but haven't found it). Then the wanted ordering get quite readable :

    class MapValueComparator implements Comparator<Map<String, Object>> {
        private final String key;

        public MapValueComparator(final String key) {
            this.key = key;
        }

        @Override
        public int compare(final Map<String, Object> o1, final Map<String, Object> o2) {
            return ((Comparable<Object>)o1.get(key)).compareTo(o2.get(key));
        }
    }

    Comparator<Object> nameThenCountComparator = ComparatorUtils.chainedComparator(
            new MapValueComparator("name"), 
            new MapValueComparator("count")
    );

And then use it (Java 7 or 8):

final List<Map<String, Comparable<Object>>> list = null;
Collections.sort(list, nameThenCountComparator);

Rq: you should, as stated in other answers, check for nulls and absent keys in the MapValueComparator.

like image 107
Benoît Avatar answered Oct 10 '22 03:10

Benoît


Assuming list's type is List<Map<String,Object>> (it's not clear what's the type of the value of the Map, so I used Object), your Comparator should be able to compare two Map<String,Object> instances.

Collections.sort(list, new Comparator<Map<String,Object>>() {
      public int compare(Map<String,Object> o1, Map<String,Object> o2) {
           // first compare o1.get("count") to o2.get("count")
           // if they are equal, compare o1.get("name") to o2.get("name")
           // don't forget to handle nulls (for example if either o1 or o2 is null
           // or if any of the keys are not present in one or both of the maps)
      }
like image 23
Eran Avatar answered Oct 10 '22 04:10

Eran


If I understand correctly, you have a List<Map<String, Object>>. You'll need to write a custom Comparator in order to sort it. There, you can compare each entry separately (error handling removed for bravity):

public class ListMapComparator implements Comparator<List<Map<String, Object>>> {

    @Override
    public in compare (List<Map<String, Object>> l1, List<Map<String, Object>> l2) {
        Integer count1 = (Integer)l1.get("count");
        Integer count2 = (Integer)l2.get("count");
        int comp = count1.compare(count2);
        if (comp != 0) {
            return comp;
        }

        String name1 = (String)l1.get("name");
        String name2 = (String)l2.get("name");
        return name1.compare(name2);
    }    
}
like image 1
Mureinik Avatar answered Oct 10 '22 02:10

Mureinik