How to sort an ArrayList of Strings with seasons and years?

Viewed 278

I have an unsorted ArrayList of Strings of seasons and years like the following:

["Summer 2020", "Autumn 2018", "Spring 2019", "Autumn 2019", "Winter 2018", "Summer 2018", "Winter 2020", "Spring 2018"]

The correct sorting of this list is the following:

["Winter 2018", "Spring 2018", "Summer 2018", "Autumn 2018", "Spring 2019", "Autumn 2019", "Winter 2020", "Summer 2020"]

How can I achieve this order? The order of seasons in a year is like Winter 2021, Spring 2021, Summer 2021, Autumn 2021, and all the seasons for every year will not necessarily exist in the list.

5 Answers

You'll need to implement a custom Comparator that sorts by the year first and then by the seasons. An enum can be used to specify the order of the seasons easily.

private static class SeasonComparator implements Comparator<String> {
    
    private enum Season {
        WINTER, SPRING, SUMMER, AUTUMN
    }

    @Override
    public int compare(String s1, String s2) {
        String[] arr1 = s1.split(" ");
        String[] arr2 = s2.split(" ");
        
        int year1 = Integer.parseInt(arr1[1]);
        int year2 = Integer.parseInt(arr2[1]);
        
        if (year1 != year2) {
            return year1 - year2;
        } else {
            Season season1 = Season.valueOf(arr1[0].toUpperCase());
            Season season2 = Season.valueOf(arr2[0].toUpperCase());
            return season1.ordinal() - season2.ordinal();
        }
    }
}

public static void main(String[] args) {
    List<String> seasons = new ArrayList<>(Arrays.asList("Summer 2020", "Autumn 2018",
            "Spring 2019", "Autumn 2019", "Winter 2018", "Summer 2018", "Winter 2020", "Spring 2018"));

    System.out.println("Unordered list:");
    System.out.println(seasons);
    
    seasons.sort(new SeasonComparator());
    
    System.out.println("Sorted list:");
    System.out.println(seasons);
}

Output

Unordered list:
[Summer 2020, Autumn 2018, Spring 2019, Autumn 2019, Winter 2018, Summer 2018, Winter 2020, Spring 2018]

Sorted list:
[Winter 2018, Spring 2018, Summer 2018, Autumn 2018, Spring 2019, Autumn 2019, Winter 2020, Summer 2020]

You need to write own comparator for such strings. I suggest you to use arrow function for it. Also, to sort seasons you need to give them some priority, I would use Map<String, Integer> for it.

Try this code:

public static void main(String[] args) {
    Map<String, Integer> seasonsPriority = new HashMap<>();
    seasonsPriority.put("Winter", 1);
    seasonsPriority.put("Spring", 2);
    seasonsPriority.put("Summer", 3);
    seasonsPriority.put("Autumn", 4);

    ArrayList<String> seasons = new ArrayList<>();
    seasons.add("Summer 2020");
    seasons.add("Autumn 2018");
    seasons.add("Spring 2019");
    seasons.add("Autumn 2019");
    seasons.add("Winter 2018");
    seasons.add("Summer 2018");
    seasons.add("Winter 2020");
    seasons.add("Spring 2018");


    seasons.sort((x, y) -> {
        int firstYear = Integer.parseInt(x.split(" ")[1]);
        int secondYear = Integer.parseInt(y.split(" ")[1]);

        if(firstYear < secondYear) {
            return -1;
        }

        if(firstYear > secondYear) {
            return 1;
        }

        String firstSeason = x.split(" ")[0];
        String secondSeason = y.split(" ")[0];

        return Integer.compare(seasonsPriority.get(firstSeason), seasonsPriority.get(secondSeason));
    });

    System.out.println(seasons);
}

Output:

[Winter 2018, Spring 2018, Summer 2018, Autumn 2018, Spring 2019, Autumn 2019, Winter 2020, Summer 2020]

Consider writing a custom Comparator to perform sorting with custom logic.

public static void main(String[] args) {
    List<String> list = Arrays.asList("Summer 2020", "Autumn 2018", "Spring 2019", "Autumn 2019", "Winter 2018", "Summer 2018", "Winter 2020", "Spring 2018");

    List<String> listOfSeasonsInAscendingOrder = Arrays.asList("Winter", "Spring", "Summer", "Autumn");

    Collections.sort(list, new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            String firstStringSplit[] = o1.split(" ");
            String season1 = firstStringSplit[0];
            int year1 = Integer.valueOf(firstStringSplit[1]);

            String secondStringSplit[] = o2.split(" ");
            String season2 = secondStringSplit[0];
            int year2 = Integer.valueOf(secondStringSplit[1]);

            if (year1 != year2) {
                return year1 < year2 ? -1 : 1;
            }

            int indexOfSeason1 = listOfSeasonsInAscendingOrder.indexOf(season1);
            int indexOfSeason2 = listOfSeasonsInAscendingOrder.indexOf(season2);

            return indexOfSeason1 == indexOfSeason2 ? 0 : indexOfSeason1 < indexOfSeason2 ? -1 : 1;
        }
    });

    System.out.println("Sorted List = " + list);
}

Output of the above code is :-

Sorted List = [Winter 2018, Spring 2018, Summer 2018, Autumn 2018, Spring 2019, Autumn 2019, Winter 2020, Summer 2020]

Try this.

static final Map<String, Integer> SEASON_ORDER = Map.of(
    "Winter", 0, "Spring", 1, "Summer", 2, "Autumn", 3);

public static void main(String[] args) {
    List<String> list = new ArrayList<>(List.of(
        "Summer 2020", "Autumn 2018", "Spring 2019", "Autumn 2019",
        "Winter 2018", "Summer 2018", "Winter 2020", "Spring 2018"));
    list.sort(Comparator.comparing(s -> {
        String[] a = s.split("\\s+");
        return a[1] + SEASON_ORDER.get(a[0]);
    }));
    System.out.println(list);
}

output:

[Winter 2018, Spring 2018, Summer 2018, Autumn 2018, Spring 2019, Autumn 2019, Winter 2020, Summer 2020]

The previous answers all perform the string splitting and parsing within the comparator function. This works perfectly fine, but might cause performance issues at scale - splitting and parsing can be relatively expensive operations, and when sorting a comparator will need to check each element on average more than once. It might improve performance to have the comparator calculate a code for each element that naturally sorts correctly, and remember the codes using a map, to avoid having to perform these operations more than once per element:

public class Scratch {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("Summer 2020", "Autumn 2018", "Spring 2019", "Autumn 2019", "Winter 2018", "Summer 2018", "Winter 2020", "Spring 2018");
        list.sort(new SeasonYearComparator());
        System.out.println(list);
    }

    private enum Season {
        Winter, Spring, Summer, Autumn;
    }

    private static class SeasonYearComparator implements Comparator<String> {
        private final Map<String, String> codes = new HashMap<>();

        @Override
        public int compare(String s1, String s2) {
            String code1 = codes.computeIfAbsent(s1, this::computeCode);
            String code2 = codes.computeIfAbsent(s2, this::computeCode);
            return code1.compareTo(code2);
        }

        private String computeCode(String seasonYear) {
            String[] parts = seasonYear.split(" ");
            return parts[1] + Season.valueOf(parts[0]).ordinal();
        }
    }
}
Related