Hard Worker
Hard Worker

Reputation: 1111

How get a List of Entries sorted by Value and then by Key from a Map

My question is how to sort a Map contents based on its values and keys and get a List of entries as a result?

First, entries need to be sorted by value in descending order, and then if values are colliding, sort them by key in descending order as well.

Example of the given Map :

Map<String,Integer> data = new HashMap();
data.put("a",10);
data.put("b",3);
data.put("c",10);

Expected Order:

["c", 10], ["a",10], ["b",3]

Upvotes: 1

Views: 1899

Answers (4)

Alexander Ivanchenko
Alexander Ivanchenko

Reputation: 28968

For that you need to define a Comparator.

You can utilize for that purpose static methods that were added as an enhancement of Java 8 to the Comparator and Map.Entry interfaces.

Both methods comparingByValue() and comparingByKey() of the Map.Entry interface will generate a comparator for values and keys respectively. In order to obtain a descending order reversed() method needs to be applied on them.

Both comparators are chained together with the thenComparing() method.

Comparator<Map.Entry<String, Integer>> valDescThenKeyDesc =
       Map.Entry.<String, Integer>comparingByValue().reversed()
           .thenComparing(Map.Entry.<String, Integer>comparingByKey().reversed());

Note that compiler is unable to infer the correct types of the arguments of comparingByValue() and comparingByKey() based only on the resulting type of the comparator.

Therefore, both comparingByValue() and comparingByKey()need to be provided with the generic type information explicitly <String, Integer> (types of keys and values respectively).

If it seems too tough to comprehend at first, you might split it into separate lines (in this case, type inference works fine):

Comparator<Map.Entry<String, Integer>> byValDesc =
       Map.Entry.comparingByValue().reversed();

Comparator<Map.Entry<String, Integer>> byKeyDesc =
       Map.Entry.comparingByKey().reversed();

Comparator<Map.Entry<String, Integer>> valDescThenKeyDesc =
       byValDesc.thenComparing(byKeyDesc);

for more information on how to build comparators with Java 8 methods, take a look at this tutorial

The next step is to create a sorted list of entries.

For that, you can either create a List of entries manually by passing an entry set to the constructor and then apply the method sort() on it, or by making use of the Stream API

In order to implement it with streams, first, we need to obtain a stream of entries. Apply sorting() operation by passing the given comparator and collect the result into a list by applying the terminal operation collect().

public static List<Map.Entry<String, Integer>> getMapEntryList(Map<String,Integer> data,
                                                               Comparator<Map.Entry<String, Integer>> comparator) {
    return data.entrySet().stream()
            .sorted(comparator)
            .collect(Collectors.toList());
}

main()

public static void main(String[] args) {
    Map<String,Integer> data = Map.of("a",10, "b",3,"c",10);

    Comparator<Map.Entry<String, Integer>> valDescThenKeyDesc =
            Map.Entry.<String, Integer>comparingByValue().reversed()
                    .thenComparing(Map.Entry.<String, Integer>comparingByKey().reversed());

    List<Map.Entry<String, Integer>> result = getMapEntryList(data, valDescThenKeyDesc);
    
    System.out.println(result);
}

Output

[c=10, a=10, b=3]

Upvotes: 3

Lei Yang
Lei Yang

Reputation: 4325

Use stream api and custom Comparator.

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

public class Main {

  static Map<String, Integer> createTestMap() {
    Map<String, Integer> data = new HashMap();
    data.put("a", 10);
    data.put("b", 3);
    data.put("c", 10);
    return data;
  }

  public static void main(String[] args) {
    var sorted = createTestMap().entrySet().stream()
        .sorted(Comparator
            .comparing(Map.Entry<String, Integer>::getValue)
            .thenComparing(Map.Entry<String, Integer>::getKey).reversed())
        .collect(Collectors.toList());
    for (var kv : sorted) {
      System.out.println(
          String.format("%s\t%s", kv.getKey(), kv.getValue()));
    }
  }
  }
}

Upvotes: 1

Ralf Kleberhoff
Ralf Kleberhoff

Reputation: 7290

Write a Comparator<Map.Entry<String,Integer>> that compares map entries according to your rule. Get a list of entries with new ArrayList<>(map.entrySet()) out of the map. Sort that list with Collections.sort(list, comparator).

Upvotes: 0

Hard Worker
Hard Worker

Reputation: 1111

public class Person implements Comparable<Person>{

    private String name;
    private Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Person person) {
        if(this.age!= person.age){
            return person.age.compareTo(this.age);
        } else {
         return person.name.compareTo(this.name);
        }
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public static void main(String argsp[]){
        Map<String,Integer> data = new HashMap();
        data.put("a",10);
        data.put("b",3);
        data.put("c",10);

        List<Person> collect = data.entrySet().stream().map(entry -> new Person(entry.getKey(), entry.getValue())).collect(Collectors.toList());
        Collections.sort(collect);
        collect.stream().forEach(person-> System.out.println(person));

    Collection<Map.Entry<String,Integer>> result  = collect.stream().map(person -> 
            new AbstractMap.SimpleEntry<String, Integer>(person.name, person.age)).collect(Collectors.toList());


    }
}

results:

Person{name='c', age=10}
Person{name='a', age=10}
Person{name='b', age=3}

Upvotes: -1

Related Questions