How to sort Strings based on string date and dynamic substring in java?

Viewed 94

I am looking to sort the strings based on

  1. Date(yyMMdd) present in string.
  2. Dynamic substring of the given string.(att, dscl, xml)

These are the input strings.

f1660.msg.220523.xml.gpg
f1660.msg.dscl.220523.xml.gpg
f1660.msg.att.220523.tar.gz.gpg
f1660.cit.msg.220520.xml1.gpg
f1660.cit.msg.220520.xml2.gpg
f1660.cit.msg.att.220520.tar.gz.gpg
f1660.cit.msg.dscl.220520.xml.gpg
8701D6.msg.220524.xml.gpg
8701D6.dscl.220524.xml.gpg
8701D6.msg.att.220524.tar.gz.gpg

Expected result: Sorted based on date (220520, 220523, 220524) and all the same date files should be sorted in the order where .att file first, .dscl file second and rest all .xml files 3rd position on wards.

f1660.cit.msg.att.220520.tar.gz.gpg
f1660.cit.msg.dscl.220520.xml.gpg
f1660.cit.msg.220520.xml1.gpg
f1660.cit.msg.220520.xml2.gpg
f1660.msg.att.220523.tar.gz.gpg
f1660.msg.dscl.220523.xml.gpg
f1660.msg.220523.xml.gpg
8701D6.msg.att.220524.tar.gz.gpg
8701D6.dscl.220524.xml.gpg
8701D6.msg.220524.xml.gpg

Tried extracting date and sorting based on date but i am not getting how to achieve it when date and dynamic substring is part of strings which i want to sort.

Could you please help?

Code tried.

public void sortBasedOnDate() {
    ArrayList<String> names = new ArrayList<>();
    names.add("f1660.msg.220523.xml.gpg");
    names.add("f1660.msg.dscl.220523.xml.gpg");
    names.add("f1660.msg.att.220523.tar.gz.gpg");
    names.add("f1660.cit.msg.220520.xml1.gpg");
    names.add("f1660.cit.msg.220520.xml2.gpg");
    names.add("f1660.cit.msg.att.220520.tar.gz.gpg");
    names.add("f1660.cit.msg.dscl.220520.xml.gpg");
    names.add("8701D6.msg.220524.xml.gpg");
    names.add("8701D6.dscl.220524.xml.gpg");
    names.add("8701D6.msg.att.220524.tar.gz.gpg");
    TreeMap<LocalDate, List<String>> map = new TreeMap<>(new SortByDate());
    for (String name : names) {
        LocalDate localDate = getDateFromFileName(name);
        List<String> mapList = map.get(localDate);
        if (mapList == null) {
            mapList = new ArrayList<>();
            mapList.add(name);
            map.put(localDate, mapList);
        } else {
            mapList.add(name);
        }
    }
    List<String> sortedList = new LinkedList<>();

    for (Map.Entry<LocalDate, List<String>> entry : map.entrySet()) {
        sortedList.addAll(entry.getValue());
    }
   
    System.out.println("sortedList: "+sortedList);
}

private LocalDate getDateFromFileName(String fileName) {
    String fileUtilDateStr = "";
    Optional<String> streamResult = Arrays.stream(fileName.split("\\."))
            .filter(element -> Pattern.compile("(\\d){6}").matcher(element).matches()).findFirst();
    if (streamResult.isPresent()) {
        fileUtilDateStr = streamResult.get();
    }
    DateTimeFormatter formatters = DateTimeFormatter.ofPattern("yyMMdd");
    LocalDate parsedDate = LocalDate.parse(fileUtilDateStr, formatters);
    return parsedDate;
}

public class SortByDate implements Comparator<LocalDate> {
@Override
public int compare(LocalDate date1, LocalDate date2) {
    return date1.compareTo(date2);
 }
}

output:

  sortedList: [f1660.cit.msg.220520.xml1.gpg, 
    f1660.cit.msg.220520.xml2.gpg, f1660.cit.msg.att.220520.tar.gz.gpg, f1660.cit.msg.dscl.220520.xml.gpg, f1660.msg.220523.xml.gpg, f1660.msg.dscl.220523.xml.gpg, f1660.msg.att.220523.tar.gz.gpg, 8701D6.msg.220524.xml.gpg, 8701D6.dscl.220524.xml.gpg, 8701D6.msg.att.220524.tar.gz.gpg]

But the expected list:

  sortedList: [f1660.cit.msg.att.220520.tar.gz.gpg, 
   f1660.cit.msg.dscl.220520.xml.gpg, f1660.cit.msg.220520.xml1.gpg, f1660.cit.msg.220520.xml2.gpg, f1660.msg.att.220523.tar.gz.gpg, f1660.msg.dscl.220523.xml.gpg, f1660.msg.220523.xml.gpg, 8701D6.msg.att.220524.tar.gz.gpg, 8701D6.dscl.220524.xml.gpg, 8701D6.msg.220524.xml.gpg]

It is sorting based on date but not getting how to sort them with 2nd condition i.e in the order of att, dscl and xml for within same date.

6 Answers

You can

  • use a regex to get the comparison relevant part of the string, i.e. dscl.220523 att.220523 msg.220523 ...
  • extract date and extension from it using a function, method or UnaryOperator
  • create a priority map for your extensions
  • create a comparator which compares the date part by parsing it to LocalDate
  • create a comparator which compares the extensions by the value of the priority map
  • and finally chain the comparators to sort your list as desired

Example:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Map;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Example {

    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        names.add("f1660.msg.220523.xml.gpg");
        names.add("f1660.msg.dscl.220523.xml.gpg");
        names.add("f1660.msg.att.220523.tar.gz.gpg");
        names.add("f1660.cit.msg.220520.xml1.gpg");
        names.add("f1660.cit.msg.220520.xml2.gpg");
        names.add("f1660.cit.msg.att.220520.tar.gz.gpg");
        names.add("f1660.cit.msg.dscl.220520.xml.gpg");
        names.add("8701D6.msg.220524.xml.gpg");
        names.add("8701D6.dscl.220524.xml.gpg");
        names.add("8701D6.msg.att.220524.tar.gz.gpg");

        Pattern pattern = Pattern.compile("\\.(\\w+)\\.(\\d+)\\.");

        UnaryOperator<String> extnExtractor = s -> {
            Matcher m = pattern.matcher(s);
            m.find();
            return m.group(1);
        };

        UnaryOperator<String> dateExtractor = s -> {
            Matcher m = pattern.matcher(s);
            m.find();
            return m.group(2);
        };

        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyMMdd");

        Map<String,Integer> prio = new HashMap<>();
        prio.put("att", 1);
        prio.put("dscl", 2);

        Comparator<String> byDate =  Comparator.comparing(s -> LocalDate.parse(dateExtractor.apply(s), dtf));
        Comparator<String> byExt  =  Comparator.comparing(s -> prio.getOrDefault(extnExtractor.apply(s),3));

        names.sort(byDate.thenComparing(byExt));

        names.forEach(System.out::println);
    }
}

The problem was that you use a TreeMap<LocalDate, List<String>>. This means that every LocalDate is mapped to a List<String>, so you basically have multiple Lists.

getDateFromFileName(String) works.

You want your Comparator to be a Comparator<String> to actually sort a List<String> and not something else:

public static class SortByDate implements Comparator<String> {
    private final HashMap<String, LocalDate> map;

    public SortByDate(ArrayList<String> names) {
        this.map = new HashMap<>();
        for (String name : names) map.put(name, getDateFromFileName(name));
    }

    @Override
    public int compare(String o1, String o2) {
        return map.get(o1).compareTo(map.get(o2));
    }
}

Now create a sorted list like this:

List<String> sortedList = new ArrayList<>(names);
sortedList.sort(new SortByDate(names));
List<String> names = new ArrayList<>();
names.add("f1660.msg.220523.xml.gpg");
names.add("f1660.msg.dscl.220523.xml.gpg");
names.add("f1660.msg.att.220523.tar.gz.gpg");
names.add("f1660.cit.msg.220520.xml1.gpg");
names.add("f1660.cit.msg.220520.xml2.gpg");
names.add("f1660.cit.msg.att.220520.tar.gz.gpg");
names.add("f1660.cit.msg.dscl.220520.xml.gpg");
names.add("8701D6.msg.220524.xml.gpg");
names.add("8701D6.dscl.220524.xml.gpg");
names.add("8701D6.msg.att.220524.tar.gz.gpg");
 
Comparator<String> comparator = new Comparator<String>() {
    Pattern pattern = Pattern.compile("\\.\\d{6}\\.");
    DateTimeFormatter  formatter = DateTimeFormatter.ofPattern("yyMMdd");
        @Override
    public int compare(String o1, String o2) {
        LocalDate dateA = getDate(o1);
        LocalDate dateB = getDate(o2);
        return dateA.compareTo(dateB);
    }
        
    private LocalDate getDate(String val) {
        Matcher matcher = pattern.matcher(val);
        if (matcher.find()) {
            String strVal = matcher.group();
            strVal = strVal.substring(1, strVal.length()-1);
            return LocalDate.parse(strVal, formatter);
        }
            
        return LocalDate.now();
    }
};
    
names.sort(comparator);
names.forEach(System.out::println);

This will work

    import java.util.*;
    import java.util.regex.Pattern;
    import java.util.stream.Collectors;
    
    public class Demo {
        public static void main(String[] args) {
            ArrayList<String> strings = new ArrayList<>();
            strings.add("f1660.msg.220523.xml.gpg");
            strings.add("f1660.msg.dscl.220523.xml.gpg");
            strings.add("f1660.msg.att.220523.tar.gz.gpg");
            strings.add("f1660.cit.msg.220520.xml1.gpg");
            strings.add("f1660.cit.msg.att.220520.tar.gz.gpg");
            strings.add("f1660.cit.msg.dscl.220520.xml.gpg");
            strings.add("8701D6.msg.220524.xml.gpg");
            strings.add("8701D6.dscl.220524.xml.gpg");
            strings.add("8701D6.msg.att.220524.tar.gz.gpg");
    
            HashMap<Integer, Integer> indexDateMap = new HashMap<>();
            for (int i = 0; i < strings.size(); i++) {
    
                Pattern pattern = Pattern.compile("\\d{6}");
                int finalI = i;
                pattern.matcher(strings.get(i)).results()                       // Stream<MatchResult>
                        .map(mr -> mr.group(0)).forEach(str -> {
                            indexDateMap.put(finalI, Integer.parseInt(str));
                        });// Stream<String> - the 1st group of each result
            }
    
            indexDateMap.entrySet().stream().sorted(Map.Entry.comparingByValue()).forEach(index -> {
                System.out.println(strings.get(index.getKey()));
            });
        }
    }

OUTPUT:

f1660.cit.msg.220520.xml1.gpg
f1660.cit.msg.att.220520.tar.gz.gpg
f1660.cit.msg.dscl.220520.xml.gpg
f1660.msg.220523.xml.gpg
f1660.msg.dscl.220523.xml.gpg
f1660.msg.att.220523.tar.gz.gpg
8701D6.msg.220524.xml.gpg
8701D6.dscl.220524.xml.gpg
8701D6.msg.att.220524.tar.gz.gpg

Using Ryan's code created the date comparator and I have added an extra comparator for extension sorting.

1st Comparator

public class DateComparator implements Comparator<String> {

    @Override
    public int compare(String o1, String o2) {

        LocalDate date1 = getDate(o1);
        LocalDate date2 = getDate(o2);
        return date1.compareTo(date2);

    }


    private LocalDate getDate(String val) {
        Pattern pattern = Pattern.compile("\\.\\d{6}\\.");
        DateTimeFormatter  formatter = DateTimeFormatter.ofPattern("yyMMdd");
        Matcher matcher = pattern.matcher(val);
        if (matcher.find()) {
            String sDate = matcher.group();
            sDate = sDate.substring(1, sDate.length()-1);
            return LocalDate.parse(sDate, formatter);
        }
        return LocalDate.now();
    }
}

2nd Comparator

public class ExtComparator implements Comparator<String > {

    @Override
    public int compare(String o1, String o2) {

        Pattern pattern = Pattern.compile("\\.(\\w+)\\.(\\d{6})\\.");
        Matcher matcher = pattern.matcher(o1);

        String s1 = matcher.find()?matcher.group():"";
        matcher = pattern.matcher(o2);
        String s2 = matcher.find()?matcher.group():"";

        return s1.compareTo(s2);
    }
}

Calling these 2 comparators in order will do the job.

 list.sort(new DateComparator().thenComparing(new ExtComparator()));

list contains all the input strings

Some thoughts around the performance of possible solutions:

  1. Don't bother converting the date string into a Date object. The yymmdd format is already sortable as a string, so converting it to another object is wasteful.

  2. Be aware that a Comparator will be called many times with the same object (because it compares it with many other objects). If it takes time to extract the date from one object, do it just once. You can put the date into a map.

  3. You can simply create a priority code ("att" being first, "dscl" second, others third) and add it to the end of the date string and then sort by that combination.

Here's a full runnable example, together with unit test:

package org.example;

import org.junit.jupiter.api.Test;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.assertj.core.api.Assertions.assertThat;

class Example {

    private static final Pattern FILENAME_PATTERN = Pattern.compile("\\.(att\\.|dscl\\.|)(\\d{6})\\.");
    private static final List<String> priority = List.of("att.", "dscl.", "");

    List<String> sortedFilenames(List<String> filenames) {
        Map<String, String> sortCodes = new HashMap<>();
        for (String filename : filenames) {
            Matcher matcher = FILENAME_PATTERN.matcher(filename);
            if (matcher.find()) {
                String type = matcher.group(1);
                String date = matcher.group(2);
                sortCodes.put(filename, date + priority.indexOf(type));
            } else {
                sortCodes.put(filename, "999999z");
            }
        }
        return filenames.stream().sorted(Comparator.comparing(sortCodes::get)).toList();
    }

    @Test
    void test() {
        List<String> filenames = """
                f1660.msg.220523.xml.gpg
                f1660.msg.dscl.220523.xml.gpg
                f1660.msg.att.220523.tar.gz.gpg
                f1660.cit.msg.220520.xml1.gpg
                f1660.cit.msg.220520.xml2.gpg
                f1660.cit.msg.att.220520.tar.gz.gpg
                f1660.cit.msg.dscl.220520.xml.gpg
                8701D6.msg.220524.xml.gpg
                8701D6.dscl.220524.xml.gpg
                8701D6.msg.att.220524.tar.gz.gpg
                """.lines().toList();

        List<String> sortedFilenames = sortedFilenames(filenames);

        List<String> expected = """
                f1660.cit.msg.att.220520.tar.gz.gpg
                f1660.cit.msg.dscl.220520.xml.gpg
                f1660.cit.msg.220520.xml1.gpg
                f1660.cit.msg.220520.xml2.gpg
                f1660.msg.att.220523.tar.gz.gpg
                f1660.msg.dscl.220523.xml.gpg
                f1660.msg.220523.xml.gpg
                8701D6.msg.att.220524.tar.gz.gpg
                8701D6.dscl.220524.xml.gpg
                8701D6.msg.220524.xml.gpg
                """.lines().toList();
        assertThat(sortedFilenames).isEqualTo(expected);
    }
}
Related