Priyabrat Nanda
Priyabrat Nanda

Reputation: 1095

Sort a map with "MMMyyyy" as key

I have a map whose keys are in MMMyyyy format and i need to sort based on month. input:

unsorted: {
 "Dec2010": 1,
 "Apr2010": 1,
 "Feb2010": 0,
 "Nov2010": 2,
 "Mar2010": 0,
 "Jun2010": 2,
 "Sep2010": 1,
 "May2010": 0,
 "Oct2010": 1,
 "Jul2010": 0,
 "Aug2010": 0,
 "Jan2010": 1
}

After sorting it should be like below:

sorted: {
 "Jan2010": 1
 "Feb2010": 0,
 "Mar2010": 0,
 "Apr2010": 1,
 "May2010": 0,
 "Jun2010": 2,
 "Jul2010": 0,
 "Aug2010": 0,
 "Sep2010": 1,
 "Oct2010": 1,
 "Nov2010": 2,
 "Dec2010": 1,     
}

Currently i am using treemap which sorts it in alphabetical order but how do i get it sorted based on month hierarchy.

Code:

    Map<String, Integer> unsorted = new HashMap<>();
    unsorted.put("Dec2010", 1);
    unsorted.put("Apr2010", 1);
    unsorted.put("Feb2010", 0);
    unsorted.put("Nov2010", 2);
    unsorted.put("Mar2010", 0);
    unsorted.put("Jun2010", 2);
    unsorted.put("Sep2010", 1);
    unsorted.put("May2010", 0);
    unsorted.put("Oct2010", 1);
    unsorted.put("Jul2010", 0);
    unsorted.put("Aug2010", 0);
    unsorted.put("Jan2010", 1);

    System.out.println("\nSorted......");
    Map<String, Integer> sorted = new TreeMap<>(unsorted);
    for (Map.Entry<String, Integer> entry : sorted.entrySet()) {
        System.out.println("Key : " + entry.getKey()
                + " Value : " + entry.getValue());
    }

Upvotes: 2

Views: 483

Answers (3)

Yassin Hajaj
Yassin Hajaj

Reputation: 21965

Actually, the answer is straightforward and quite simple using TreeMap and a custom Comparator

Map<String, Integer> map = new HashMap<>();
map.put("Dec2010", 1);
map.put("Apr2010", 1);
map.put("Feb2010", 0);
map.put("Nov2010", 2);
map.put("Mar2010", 0);
map.put("Jun2010", 2);
map.put("Sep2010", 1);
map.put("May2010", 0);
map.put("Oct2010", 1);
map.put("Jul2010", 0);
map.put("Aug2010", 0);
map.put("Jan2010", 1);

Map<String, Integer> sortedMap = new TreeMap<>(
  Comparator.comparing(
    text -> YearMonth.parse(
      text, 
      DateTimeFormatter.ofPattern("MMMyyyy", Locale.ENGLISH)
    )
  )
);

sortedMap.putAll(map);

sortedMap.entrySet().forEach(System.out::println);

As suggested by Arvind Kumar Avinash, here is an example using Eclipse Collections

The example is quite similar except it has an overloaded constructor accepting the source map as second argument

Map<String, Integer> sortedMap = new TreeSortedMap<>(
    Comparator.comparing(
      text -> YearMonth.parse(
        text, 
        DateTimeFormatter.ofPattern("MMMyyyy", Locale.ENGLISH)
      )
    ), 
    map
);

sortedMap.entrySet().forEach(System.out::println);

Both result in

Jan2010=1
Feb2010=0
Mar2010=0
Apr2010=1
May2010=0
Jun2010=2
Jul2010=0
Aug2010=0
Sep2010=1
Oct2010=1
Nov2010=2
Dec2010=1

Upvotes: 3

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 78945

The legacy date-time API (java.util date-time types and their formatting API, SimpleDateFormat) is outdated and error-prone. It is recommended to stop using it completely and switch to java.time, the modern date-time API*.

Java SE 8

import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> unsorted = new HashMap<>();
        unsorted.put("Dec2010", 1);
        unsorted.put("Apr2010", 1);
        unsorted.put("Feb2010", 0);
        unsorted.put("Nov2010", 2);
        unsorted.put("Mar2010", 0);
        unsorted.put("Jun2010", 2);
        unsorted.put("Sep2010", 1);
        unsorted.put("May2010", 0);
        unsorted.put("Oct2010", 1);
        unsorted.put("Jul2010", 0);
        unsorted.put("Aug2010", 0);
        unsorted.put("Jan2010", 1);

        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("MMMuuuu", Locale.ENGLISH);
        Comparator<String> comparator = (s1, s2) -> YearMonth.parse(s1, dtf).compareTo(YearMonth.parse(s2, dtf));
        Map<String, Integer> sorted = new TreeMap<>(comparator);
        sorted.putAll(unsorted);
        System.out.println(sorted);
    }
}

Output:

{Jan2010=1, Feb2010=0, Mar2010=0, Apr2010=1, May2010=0, Jun2010=2, Jul2010=0, Aug2010=0, Sep2010=1, Oct2010=1, Nov2010=2, Dec2010=1}

Learn more about the the modern date-time API* from Trail: Date Time.

Java SE 9

Java SE 9 introduced Map.ofEntries which you can use to initialize your Map<String, Integer> unsorted.

import static java.util.Map.entry;

import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> unsorted = Map.ofEntries(
                                            entry("Dec2010", 1),
                                            entry("Apr2010", 1),
                                            entry("Feb2010", 0),
                                            entry("Nov2010", 2),
                                            entry("Mar2010", 0),
                                            entry("Jun2010", 2),
                                            entry("Sep2010", 1),
                                            entry("May2010", 0),
                                            entry("Oct2010", 1),
                                            entry("Jul2010", 0),
                                            entry("Aug2010", 0),
                                            entry("Jan2010", 1)
                                        );

        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("MMMuuuu", Locale.ENGLISH);
        Comparator<String> comparator = (s1, s2) -> YearMonth.parse(s1, dtf).compareTo(YearMonth.parse(s2, dtf));         
        Map<String, Integer> sorted = new TreeMap<>(comparator); 
        sorted.putAll(unsorted);
        System.out.println(sorted);
    }
}

Sorting & mapping using a single statement:

You can make use of the powerful Stream API (tutorials: 1, 2) to get your job done.

import static java.util.Map.entry;

import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> unsorted = Map.ofEntries(
                                            entry("Dec2010", 1),
                                            entry("Apr2010", 1),
                                            entry("Feb2010", 0),
                                            entry("Nov2010", 2),
                                            entry("Mar2010", 0),
                                            entry("Jun2010", 2),
                                            entry("Sep2010", 1),
                                            entry("May2010", 0),
                                            entry("Oct2010", 1),
                                            entry("Jul2010", 0),
                                            entry("Aug2010", 0),
                                            entry("Jan2010", 1)
                                        );

        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("MMMuuuu", Locale.ENGLISH);
        Map<String, Integer> sorted = unsorted
                                        .entrySet()
                                        .stream()
                                        .collect(Collectors.toMap(
                                            e -> YearMonth.parse(e.getKey(), dtf),
                                            e -> e.getValue(),
                                            (v1, v2) -> v1,
                                            TreeMap::new
                                         )) // Returns TreeMap<YearMonth, Integer>
                                        .entrySet()
                                        .stream()
                                        .collect(Collectors.toMap(
                                            e -> dtf.format(e.getKey()),
                                            e -> e.getValue(),
                                            (v1, v2) -> v1,
                                            LinkedHashMap::new
                                         ));
        
        System.out.println(sorted);
    }
}

Learn more about Collectors#toMap from the documentation.


* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

Upvotes: 4

mzc
mzc

Reputation: 3355

Let's try with a custom comparator:

public class Main {

    public static void main(String[] args) {

        final SimpleDateFormat df = new SimpleDateFormat("MMMyyyy");

        Map<String, Integer> map = new TreeMap<>(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                String s1 = (String) o1;
                String s2 = (String) o2;
                try {
                    return df.parse(s1).compareTo(df.parse(s2));
                } catch (ParseException e) {
                    throw new RuntimeException("Bad date format");
                }
            };
        });

        map.put("Dec2011",1);
        map.put("Jan2011",0);
        map.put("Feb2011",1);
        map.put("Mar2011",0);
        map.put("Oct2011",1);
        map.put("Sep2011",0);

        for(Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }

    }
}

Output:

Jan2011 -> 0
Feb2011 -> 1
Mar2011 -> 0
Sep2011 -> 0
Oct2011 -> 1
Dec2011 -> 1

Upvotes: 5

Related Questions