Mam dla Was łamigłówkę. Może ktoś wymyśli coś lepszego niż moje wypociny. No to jedziemy, enjoy :D
Mamy x przedziałów czasowych (periods). Każdy przedział czasowy ma określoną długość slota, na jakie można te przedziały podzielić. Na przykład: przedział od 08:00 do 09:00, długość slota 15 min -> 08:00 - 08:15, 08:15 - 08:30, 08:30 - 08:45 i 08:45 do 09:00.
Teraz dochodzą zajęte przedziały (busy periods). Ich również może być y i wcale nie muszą nakładać się z przedziałami. Sloty zajęte mają tylko start i end. Długość dowolna, może być nawet od 08:00 do 20:00.
Próbowałem użyć Linq, ale musiałem zmienić algorytm, ponieważ powstał jeszcze mniej czytelny kod, więc wróciłem do starego dobrego while'a. Przykładowy input i output:
IN:
Period 08:00 - 12:00, długość slot'a 35 min
Busy period 6:30 - 7:10
Busy period 7:45 - 8:20
Busy period 10:15 - 11:30
Busy period 11:55 - 12:30
Busy period 13:00 - 14:05
OUT:
Slot 6:30 - 7:10 Zajęty: true
Slot 7:45 - 8:20 Zajęty: true
Slot 8:20 - 8:55 Zajęty: false
Slot 8:55 - 9:30 Zajęty: false
Slot 9:30 - 10:05 Zajęty: false
Slot 10:15 - 11:30 Zajęty: true
Slot 11:55 - 12:30 Zajęty: true
Slot 13:00 - 14:05 Zajęty: true
Poniższy algorytm działa tak:
- Pobiera jedne i drugie przedziały z bazy.
- Ponieważ wszystkie zajęte przedziały muszą być w wyniku jako sloty, to na dzień dobry tworzy z nich słownik i ładuje do posortowanej listy.
- Iterujemy po wszystkich przedziałach i każdy przedział próbujemy pociąć na odpowiednie sloty o długości przypisanej do tego przedziału.
- Każdorazowo sprawdzamy, czy wygenerowany slot nie nakłada się z jakimś zajętym przedziałem.
- Jeżeli nie, to dorzucamy go do puli i w następnej iteracji kolejny slot próbujemy utworzyć od końca poprzedniego.
- Jeżeli tak, to olewamy go i kolejny slot tworzymy od końca zajętego przedziału. Zajętego przedziału nie musimy już tu dodawać, ponieważ wszystkie zostały dodane na początku.
var periods = _periodsRepository.GetInRange(addressId, from, to);
var busyPeriods = _busyPeriodsRepository.GetInRange(addressId, from, to)
.Select(busyPeriod => new DateRange(busyPeriod.Start, busyPeriod.End))
.ToList();
var busySlots = busyPeriods
.Select(busyPeriod => new Slot
{
Start = busyPeriod.Start,
End = busyPeriod.End,
AddressId = addressId,
IsBusy = true
})
.ToDictionary(slot => slot.Start);
var slots = new SortedList<DateTime, Slot>(busySlots);
foreach (var period in periods)
{
var rangeStart = period.Start;
while (rangeStart.AddMinutes(period.SlotDuration) <= period.End)
{
var rangeEnd = rangeStart.AddMinutes(period.SlotDuration);
var slotRange = new DateRange(rangeStart, rangeEnd);
var busyPeriod = busyPeriods.FirstOrDefault(range => range.Overlaps(slotRange));
DateTime nextRangeStart;
if (busyPeriod is null)
{
nextRangeStart = slotRange.End;
slots.Add(slotRange.Start, new Slot
{
Start = slotRange.Start,
End = slotRange.End,
AddressId = addressId,
IsBusy = false
});
}
else
{
nextRangeStart = busyPeriod.End;
}
rangeStart = nextRangeStart;
}
}
return slots.Values;