-3

Usually this type of algorithm is done using SQL (gaps and islands) but I need to find a way to do it in Java.

I have a Set of objects : Set<UnavailableBlock>

The UnavailableBlock class is as follows (simplified):

@Getter
@Setter
@AllArgsConstructor
@ToString
public class UnavailableBlock{
    OffsetDateTime blockStartTime;
    OffsetDateTime blockEndTime;
}

Overlapping intervals is allowed, so I'm trying to return a Set<UnavailableBlock> but with merged intervals leaving the respective gaps in between. I'm using OffsetDateTime and need to take in account the time.

For example (using Outlook):

[1]: https://i.stack.imgur.com/zK8h0.png

private Set<AppointmentAvailabilityBlock> mergeUnavailabilitiesBlocks(
    Set<AppointmentAvailabilityBlock> appointmentComponentUnavailabilitiesBlock) {

// Transform Set to List
List<AppointmentAvailabilityBlock> intervals = new LinkedList<AppointmentAvailabilityBlock>();
intervals.addAll(appointmentComponentUnavailabilitiesBlock);

// Sort by blockStartTime
intervals.sort(Comparator.comparing(AppointmentAvailabilityBlock::getBlockStartTime));

// Merge
LinkedList<AppointmentAvailabilityBlock> merged = new LinkedList<>();
for (AppointmentAvailabilityBlock interval : intervals) {
    // No overlap with the previous interval, append it.
    if (merged.isEmpty() || merged.getLast().getBlockEndTime().isBefore(interval.getBlockStartTime())) {
        merged.add(interval);
    } else { // There is overlap
        OffsetDateTime maxOffsetDateTime = merged.getLast().getBlockEndTime().isAfter(
                interval.getBlockEndTime()) ? merged.getLast().getBlockEndTime() : interval.getBlockEndTime();
        
        merged.getLast().setBlockEndTime(maxOffsetDateTime);
    }
}
return new HashSet<AppointmentAvailabilityBlock>(merged);
}

The problem is that I keep getting blocks that overlap:

Orange = Before merge

Green = After merge

enter image description here

NB: I'm using Spring Boot with lombok annotations

wazowski
  • 115
  • 8
  • 1
    First move UnavailableBlock from Set to some ordered structure (List, array, ...). Then sort them by blockStartTime. Finally go trought sorted list of blockStartTime and if two neighbour records are overlapping each other replace them with merged one. – Martin Dendis Jul 12 '21 at 18:35
  • Thanks @MartinDendis. I've added my code and an explanation to why I'm still struggling with the merge. – wazowski Jul 12 '21 at 19:27
  • 1
    TBH I can't find any flaw in your code and from my brief tests it works. Can you provide some example test data? – Martin Dendis Jul 12 '21 at 20:27
  • I'm pulling hundreds of random lines straight from my database. I'll try to compile a set of examples.. The problem might be because my data is random – wazowski Jul 12 '21 at 20:30

1 Answers1

0
private Set<AppointmentAvailabilityBlock> mergeUnavailabilitiesBlocks(
    Set<AppointmentAvailabilityBlock> appointmentComponentUnavailabilitiesBlock) {

// Transform Set to List
List<AppointmentAvailabilityBlock> intervals = new LinkedList<AppointmentAvailabilityBlock>();
intervals.addAll(appointmentComponentUnavailabilitiesBlock);

// Sort by blockStartTime
intervals.sort(Comparator.comparing(AppointmentAvailabilityBlock::getBlockStartTime));

// Merge
LinkedList<AppointmentAvailabilityBlock> merged = new LinkedList<>();
for (AppointmentAvailabilityBlock interval : intervals) {
    // No overlap with the previous interval, append it.
    if (merged.isEmpty() || merged.getLast().getBlockEndTime().isBefore(interval.getBlockStartTime())) {
        merged.add(interval);
    } else { // There is overlap
        OffsetDateTime maxOffsetDateTime = merged.getLast().getBlockEndTime().isAfter(
                interval.getBlockEndTime()) ? merged.getLast().getBlockEndTime() : interval.getBlockEndTime();
        
        merged.getLast().setBlockEndTime(maxOffsetDateTime);
    }
}
return new HashSet<AppointmentAvailabilityBlock>(merged);
}
wazowski
  • 115
  • 8