Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting list of all consecutive intervals from series of dates in Java

I have a list of LocalDates lets say 14-06-2020, 15-06-2020, 17-06-2020, 19-06-2020, 20-06-2020, 21-06-2020 and I want to have all consecutive intervals from above dates. So the output would be like

Interval 1 = [14-06-2020, 15-06-2020]
Interval 2 = [17-06-2020, 17-06-2020]
Interval 3 = [19-06-2020, 21-06-2020]

What would be the most efficient way to do in Java

So I have create an Interval class that would hold start and enddate

public class Interval{
  private LocalDate startDate;
  private LocalDate endDate;
}

I can iterate over each element in list of dates and then check for logic if two dates are consecutive something in below line,

public static void main(String args[]){
    List<LocalDate> dates = new ArrayList<>();
    dates.add(LocalDate.of(2020,6,14));
    dates.add(LocalDate.of(2020,6,15));
    dates.add(LocalDate.of(2020,6,17));
    dates.add(LocalDate.of(2020,6,19));
    dates.add(LocalDate.of(2020,6,21));
    dates.add(LocalDate.of(2020,6,20));

    Collections.sort(dates);

    //Handle if empty or null
    List<Interval> intervals = new ArrayList<>();
    if(dates==null || dates.size()==0){
        throw new IllegalArgumentException("list cannot be empty");
    }

    //If only one date then the interval starts and ends with same date
    if(dates.size()==1){
        Interval interval = new Interval();
        interval.setStartDate(dates.get(0));
        interval.setEndDate(dates.get(0));
    }
    LocalDate firstDate = dates.get(0);

    for(int i =1;i<dates.size(); i++){
        LocalDate endDate = dates.get(i);
        LocalDate nextDate = endDate.plusDays(1);
        //iterate over to get the desired list of interval
        while(my condition satisfies){
            //create new interval
        }
        //intervals.add(interval
    }
}

I wanted to check if there is something better using stream api or can i group dates by consecutive days and then collect them using interval

like image 487
Sandeep Nair Avatar asked Jun 12 '20 04:06

Sandeep Nair


People also ask

How do you find the number of consecutive dates in Java?

Java 8 Object Oriented Programming Programming To check to find whether a given array contains three consecutive dates: Convert the given array into a list of type LocalDate. Using the methods of the LocalDate class compare ith, i+1th and i+1th, i+2th elements of the list if equal the list contain 3 consecutive elements.

How to check if a given array contains three consecutive dates?

To check to find whether a given array contains three consecutive dates: Convert the given array into a list of type LocalDate. Using the methods of the LocalDate class compare ith, i+1th and i+1th, i+2th elements of the list if equal the list contain 3 consecutive elements.

How to get all dates between two dates in Java?

Java – Get All Dates Between Two Dates. 1 1. LocalDate.datesUntil () – Java 9. LocalDate ‘s datesUntil () method returns a sequential ordered stream of dates. The returned stream starts from ... 2 2. Stream.iterate () – Java 8. 3 3. Create dates in while loop – Java 7.

How to get all dates in a stream in Java?

LocalDate ‘s datesUntil () method returns a sequential ordered stream of dates. The returned stream starts from startDate and goes to endDate (exclusive) by an incremental step of 1 day. 2. Stream.iterate () – Java 8 To get all dates, create a stream of dates adding adding 1 to startdate and so on, until end date.


2 Answers

A "workaround" towards an approach based on Stream upon the sorted collection could be to use markers for range lookup -

List<Integer> rangeMarkers = new ArrayList<>();
rangeMarkers.add(0);
rangeMarkers.addAll(IntStream.range(0, dates.size() - 1)
        .filter(i -> !dates.get(i).plusDays(1).equals(dates.get(i + 1)))
        .mapToObj(i -> i + 1)
        .collect(Collectors.toList()));
rangeMarkers.add(dates.size());
System.out.println(rangeMarkers);

and then use those markers to map dates to Interval -

List<Interval> intervals = IntStream.range(0, rangeMarkers.size() - 1)
        .mapToObj(i -> new Interval(dates.get(rangeMarkers.get(i)),
                dates.get(rangeMarkers.get(i + 1) - 1)))
        .collect(Collectors.toList());
System.out.println(intervals);
like image 103
Naman Avatar answered Sep 22 '22 22:09

Naman


Stream API isn't your friend in this case. you can do it as I displayed the below but I think it isn't readable.

without non-stream API makes it more readable.

After sorting the list loop over the list by skipping the first index. current localDate is the first element in the list. by checking its equality with the other elements change the current value and merge intervalMap as you see. for current localDate in the loop if equality isn't matched put it on the map with a new key. (++index). because you just want to have start localDate and end localDate in the merge function I just set the endLocalDate of the first interVal with the second interval endLocalDate value.

LocalDate current = dates.get(0);
Map<Integer, Interval> intervalMap2 = new HashMap<>();
int index = 1;
intervalMap2.put(1, new Interval(current,current));
for (LocalDate localDate : dates.subList(1, dates.size())) {
    if (current.plusDays(1).equals(localDate)) {
       current = localDate;
       intervalMap2.merge(index, new Interval(localDate,localDate), 
                    (val, val2) -> {val.setEndDate(val2.getEndDate());return val; });
    } else {
        intervalMap2.merge(index, new Interval(current,current),
              (val, val2) -> {val.setEndDate(val2.getEndDate());return val; });

        intervalMap2.put(++index, new Interval(localDate,localDate));
        current = localDate;
    }
}

however, if you interested in do it with stream version you can do like:

 Map<Integer, Interval> intervalMap = dates.stream().sorted()
            .collect(HashMap::new, (hashMap, localDate) -> {
                if (hashMap.get(hashMap.size()) != null &&
                        hashMap.get(hashMap.size()).getEndDate()
                                .equals(localDate.minusDays(1))) {
                    hashMap.merge(hashMap.size(),
                         new Interval(localDate, localDate),
                         (val, val2) -> {val.setEndDate(val2.getEndDate());return val; });
                } else {
                    if (hashMap.size() > 1)
                       hashMap.merge(hashMap.size(), hashMap.get(hashMap.size()),
                        (val, val2) -> { val.setEndDate(val2.getEndDate());return val;});
                 hashMap.put(hashMap.size() + 1, new Interval(localDate, localDate));
                }
            }, HashMap::putAll);
like image 45
Hadi J Avatar answered Sep 22 '22 22:09

Hadi J