DevSay
DevSay

Reputation: 1176

How to find least dates of each month from given set of dates

I need to extract least dates from given dates array for every mentioned month.

example- date format is dd/MM/yyyy Following is sample input

04/11/2019,  11/11/2019, 18/11/2019, 25/11/2019, 02/12/2019, 09/12/2019, 
06/01/2020, 03/02/2020, 10/02/2020, 17/02/2020, 24/02/2020, 02/03/2020, 
09/03/2020, 16/03/2020, 23/03/2020, 30/03/2020, 06/04/2020, 13/04/2020, 
20/04/2020, 27/04/2020

I need to get least date from each month:

output be like-

04/11/2019, 02/12/2019, 06/01/2020, 03/02/2020, 02/03/2020, 06/04/2020

Can anyone help?

Upvotes: 5

Views: 760

Answers (5)

Anonymous
Anonymous

Reputation: 86276

There’s a wealth of good answers already. On top of those I would like to add:

  1. Don’t put strings in your original list. Put LocalDate objects. If you get string input, parse it before adding to the list.
  2. You don’t need to sort the inner list to get its minimum (or maximum).

In Java 10 or later:

    List<LocalDate> input = List.of(LocalDate.of(2019, 11, 4), 
            LocalDate.of(2019, 11, 11), LocalDate.of(2019, 11, 18),
            LocalDate.of(2019, 11, 25), LocalDate.of(2019, 12, 2),
            LocalDate.of(2019, 12, 9), LocalDate.of(2020, 1, 6),
            LocalDate.of(2020, 2, 3), LocalDate.of(2020, 2, 10),
            LocalDate.of(2020, 2, 17), LocalDate.of(2020, 2, 24),
            LocalDate.of(2020, 3, 2), LocalDate.of(2020, 3, 9),
            LocalDate.of(2020, 3, 16), LocalDate.of(2020, 3, 23),
            LocalDate.of(2020, 3, 30), LocalDate.of(2020, 4, 6),
            LocalDate.of(2020, 4, 13), LocalDate.of(2020, 4, 20),
            LocalDate.of(2020, 4, 27));

    List<LocalDate> minDatePerMonth = input.stream()
            .collect(Collectors.groupingBy(YearMonth::from, Collectors.minBy(Comparator.naturalOrder())))
            .values()
            .stream()
            .map(Optional::orElseThrow)
            .sorted()
            .collect(Collectors.toList());

I am using the overloaded version of Collectors.groupingBy that accepts a downstream collector. groupingBy produces a map, but instead of the map values being lists they are produced by the downstream collector. In this case Collectors.minBy, which produces Optional<LocalDate>. And since the groupingBy would not create a map entry without at least one date, we know that the Optional cannot be empty.

Of course we want nicely formatted output for our user. We produce that in this way:

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu");
    String output = minDatePerMonth.stream()
            .map(formatter::format)
            .collect(Collectors.joining(", "));
    System.out.println(output);

04/11/2019, 02/12/2019, 06/01/2020, 03/02/2020, 02/03/2020, 06/04/2020

In Java 9

The no-arg Optional.orElseThrow was introduced in Java 10. In Java 8 and 9 you may use for example:

            .map(old -> old.orElseThrow(() -> new IllegalStateException("This can’t happen")))

In Java 8

List.of was introduced in Java 9. However you said that you had already got the list, so I consider how to initialize it outside the question. I trust readers to find their way of initializing it in Java 8 if required.

Upvotes: 3

ruslanys
ruslanys

Reputation: 1211

public List<LocalDate> filterDates(List<LocalDate> dates) {
    return dates.stream()
            .collect(Collectors.groupingBy(YearMonth::from))
            .values()
            .stream()
            .map(Collections::min) // .map(Collections::max)
            .collect(Collectors.toList());
}

Instead of .map(Collections::min) you can use the following:

.map(list -> {
    list.sort(Comparator.naturalOrder());
    return list.get(0);
})

Or:

.map(list -> list.stream().max(Comparator.naturalOrder()))

Upvotes: 4

Shahzeb Khan
Shahzeb Khan

Reputation: 3642

//localDates is list of LocalDate. You can convert it.

localDates.stream().forEach(localDate -> {
            Integer month = localDate.getMonthValue();
            if (localDateMap.containsKey(month)){
                if(localDate.isBefore(localDateMap.get(month))){
                    localDateMap.put(month,localDate);
                }
            }else{
                localDateMap.put(month,localDate);
            }
        });

Then get all values from map.

Upvotes: 1

Youcef LAIDANI
Youcef LAIDANI

Reputation: 59978

If you are using Java-8 you can use :

List<LocalDate> collect = Stream.of(strings)
        .map(s -> LocalDate.parse(s, format)) // convert your strings to LocalDate
        .collect(Collectors.groupingBy(YearMonth::from)) // group by year and month
        .values().stream()
        .map(a -> a.stream().sorted().limit(1).findFirst().get())
        .sorted()
        .collect(Collectors.toList()); // collect the results

outputs

[2019-11-04, 2019-12-02, 2020-01-06, 2020-02-03, 2020-03-02, 2020-04-06]

Ideone demo

Upvotes: 6

Michael Gantman
Michael Gantman

Reputation: 7790

Here is a sequence that you will need to do

  1. Parse your Strings to LocalDate
  2. Build a map where your key would be a month and your key would be a sorted List.
    1. Go through all the Lists and take the first value from each one

This is your algorithm. I will leave the implementation for you.

Upvotes: 1

Related Questions