6

How sort HashMap entries by multiple properties.

Suppose I have a map with key String and value as Object.

Map<String, UserMetrics> map = new HashMap<>
map.put("user10",new UserMetrics(1,100,111));
map.put("user3",new UserMetrics(10,330,444));
map.put("user11",new UserMetrics(333,100,555));
map.put("user1",new UserMetrics(1,111,433));

 public static class UsageMetrics implements Serializable {
        private long param1;
        private long param2;
        private long param3;....
 }

I want to sort users first by "param1" and then after by "param2"

result expected:<>

user10, UserMetrics(1,100,111)
user1,  UserMetrics(1,111,433))
user3,  UserMetrics(10,330,444));
user11, UserMetrics(333,100,555))
VitalyT
  • 1,671
  • 3
  • 21
  • 49

3 Answers3

8

You can use the following comparator for sorting:

Comparator
        .comparingLong(UserMetrics::getParam1)
        .thenComparingLong(UserMetrics::getParam2);

The difficulty is, however that you want to sort values, not keys. It seems also you need both keys and values. For this you could make and sort a copy of the entry set of your map. Something along the lines:

List<Map.Entry<String, UserMetrics>> sortedEntries = new ArrayList<>(map.entrySet());
Collections.sort(sortedEntries,
    Map.Entry.comparingByValue(
        Comparator
            .comparingLong(UserMetrics::getParam1)
            .thenComparingLong(UserMetrics::getParam2)));

Alternatively you can also use a sorted collection (like TreeSet) or a sorted stream - normally you can provide your own comparator to the "sorting things".

Also note that I'm using comparingLong/thenComparingLong, unlike other answers where people just used comparing/thenComparing. The problem with comparing/thenComparing is that if you have primitive types like long, comparing/thenComparing will essentially box them into wrapper types like Long, which is totally unnecessary.

lexicore
  • 42,748
  • 17
  • 132
  • 221
2

You can use below code. I have written by using Employee the s value object, so you can use your own Object :

public class Main {
    public static void main(String[] args) throws InterruptedException {
        HashMap<String, Employee> map = new HashMap<>();
        Map<String, Employee> sortedMap = map.entrySet()
                                             .stream()
                                             .sorted(Entry.comparingByValue(Main::compare))
                                             .collect(Collectors.toMap(Entry::getKey, Entry::getValue,
                                                     (e1, e2) -> e1, LinkedHashMap::new));

    }

    public static int compare(Employee e1, Employee e2) {
        return e1.getAge() - e2.getAge();
    }
}

Edited: Here is another way you can use Comparator#comparing and thenComparing to sort.

Map<String, Employee> sortedMap = map.entrySet()
                                     .stream()
                                     .sorted(Entry.comparingByValue(
                                             Comparator.comparing(Employee::getAge)
                                                       .thenComparing(Employee::getSalary)))
                                     .collect(Collectors.toMap(Entry::getKey, Entry::getValue,
                                             (e1, e2) -> e1, LinkedHashMap::new));
Amit Bera
  • 7,075
  • 1
  • 19
  • 42
  • This does not address the core of the question which is: how to compare on multiple properties. – lexicore Apr 12 '18 at 09:22
  • Main:: compare is a comparator you can put your logic as your wish. You can use multiple properties to develop your logic in public static int compare(Employee e1, Employee e2) method. – Amit Bera Apr 12 '18 at 09:24
  • Also sorting and then collecting to `HashMap` is useless since `HashMap` will neither retain sorting not insertion order. Use `LinkedHashMap` to retain insertion order. (Update: this was corrected in the answer later on.) – lexicore Apr 12 '18 at 09:24
  • You are right I have updated my answer. – Amit Bera Apr 12 '18 at 09:26
  • Yes, of course you can put logic for comparing multiple properties in `Main.compare`. But you did not - despite it being the core of the question. – lexicore Apr 12 '18 at 09:26
  • @lexicore I have modified my answer as per OP's need. Thanks for the suggestion . – Amit Bera Apr 12 '18 at 09:31
  • `Map.Entry.comparingByValue` was new to me. Thank you. – lexicore Apr 12 '18 at 09:40
0

HashMap is not an efficient data structure to sort by value. But if you really need to use it, you can try something like this:

map.entrySet().stream().sorted(Comparator.comparing(entry -> entry.getValue().param1)
                                                         .thenComparing(entry -> entry.getValue().param2)
                                                         .thenComparing(entry -> entry.getValue().param3))
        .collect(Collectors.toList());
Hari Menon
  • 33,649
  • 14
  • 85
  • 108