Czy można poprawić czytelność tego kodu?

0

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:

  1. Pobiera jedne i drugie przedziały z bazy.
  2. 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.
  3. Iterujemy po wszystkich przedziałach i każdy przedział próbujemy pociąć na odpowiednie sloty o długości przypisanej do tego przedziału.
  4. 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;
2

Twój kod jest najlepszy przykładem na to że nie należy używać "var" wtedy kiedy jasno nie widać jakiego typu jest zmienna, czyta się to ciężko :).

3

Jeśli chodzi o poprawę czytelności Twojego kodu, to można zrobić:

  • używać typów zamiast var, w miejscach w których nie widać od razu jaki typ ma zmienna
  • pozbyć się niepotrzebnego słownika i posortowanej tablicy, wystarczy posortować na końcu
  • stworzyć konstruktory dla Slot, by te Selecty nie były tak rozwlekłe

Natomiast bardziej czytelnie samego algorytmu nie da się zapisać, natomiast można zejść trochę ze złożonością by za każdym razem nie przeglądać całej listy zajętych przedziałów czy nie zazębia się z nowo tworzonym, ale wtedy raczej oczywistość algorytmu ucierpi:

  var periods = new List<Period>()
 {
     new Period(new DateTime(2018, 11, 20, 8, 0, 0), new DateTime(2018, 11, 20, 12, 0, 0), TimeSpan.FromMinutes(35))
 };

 var busyPeriods = new List<DateRange>()
 {
     new DateRange(new DateTime(2018, 11, 20, 6, 30, 0), new DateTime(2018, 11, 20, 7, 10, 0)),
     new DateRange(new DateTime(2018, 11, 20, 7, 45, 0), new DateTime(2018, 11, 20, 8, 20, 0)),
     new DateRange(new DateTime(2018, 11, 20, 10, 15, 0), new DateTime(2018, 11, 20, 11, 30, 0))
 };   
 
 var slots = busyPeriods.Select(x => new Slot(x)).ToList(); 
 
 int i = 0;
 foreach (Period period in periods)
 {               
     DateTime newSlotEnd = period.Start + period.SlotDuration; 

     while (newSlotEnd <= period.End)
     {
         DateTime newSlotStart = newSlotEnd - period.SlotDuration;

         if ((i < busyPeriods.Count) && (busyPeriods[i].Start < newSlotEnd))
         {
             if (newSlotStart < busyPeriods[i].End)
             {
                 newSlotEnd = busyPeriods[i].End + period.SlotDuration;
             }
             ++i;
             continue;
         }

         slots.Add(new Slot(newSlotStart, newSlotEnd));
       
         newSlotEnd += period.SlotDuration;
     }
 } 
 
 slots = slots.OrderBy(x => x.Start).ToList();

1 użytkowników online, w tym zalogowanych: 0, gości: 1