Hero Image
- Mihai Surdeanu

Algoritm de compactare a unor intervale

Salutare tuturor. Astăzi vom prezenta și vom rezolva împreună o problemă cât se poate de simplă și anumea aceea de compactare a unor intervale de timp, în funcție de anumite informații.

Să presupunem că avem o aplicație de shopping, ce lucrează cu o tabelă de produse și una de prețuri. Fiecare produs va avea un anumit preț în fiecare moment de timp. Informația legată de preț poate fi modificată de mai multe ori pe zi și să spunem că în spatele fiecărei modificări stă o altă aplicație, care va monitoriza cu atenție ceea ce se întâmplă pe piață.

Haideți să luăm și un exemplu pentru a înțelege problema și mai bine. Să presupunem că tabela de produse arată astfel:

ID produs Nume produs
1 Roborock S5 Max
2 Dyson V10 Absolute

Fiecare produs are asociat un anumit preț la fiecare moment de timp. Acest lucru se poate evidenția cu ușurință dacă analizăm structura tabelei de prețuri:

ID produs Dată de start Dată de sfârșit Preț
1 2021-01-23 10:00 2021-01-23 13:00 10
1 2021-01-23 13:00 2021-01-23 15:00 10
1 2021-01-23 15:00 2021-01-24 09:00 11
1 2021-01-24 09:00 null 11
2 2021-01-25 10:00 2021-01-25 19:00 5
2 2021-01-25 19:00 null 6

Pentru simplitate, să presupunem că moneda este întotdeauna RON. Pentru determinarea prețului aferent unui produs, va trebui să selectăm linia din tabela de prețuri care corespunde ID-ului respectiv de produs și care corespunde intervalului aferent de timp - [Dată de start, Dată de sfârșit). Intervalul de timp este închis la stânga și deschis la dreapta. Se garantează faptul că prețul de start al unui interval coincide cu prețul de sfârșit al celui precedent. De exemplu, dacă astăzi este 2021-01-22 ora 10:00, produsul cu id-ul 1 nu are niciun preț atașat. Dacă la acest interval mai adăugat fix o zi, prețul produsului 1 va deveni 10. Dar ce se întâmplă dacă acum este 2021-01-24 ora 10:00? Acel null pe coloana de Dată de sfârșit se traduce ca fiind present. Cu alte cuvinte, prețul produsului 1 va fi 11. Clar?

Acum, dacă ne uităm mai cu atenție, observăm că produsul 1 are două prețuri posibile: 10 sau 11, chiar dacă în tabelă sunt 4 intervale reprezentate pentru acesta. Primul și al doilea interval se pot compacta. La fel și al 3-lea și al 4-lea.

ID produs Dată de start Dată de sfârșit Preț
1 2021-01-23 10:00 2021-01-23 15:00 10
1 2021-01-23 15:00 null 11
2 2021-01-25 10:00 2021-01-25 19:00 5
2 2021-01-25 19:00 null 6

Problema pe care dorim să o rezolvăm este cea de a scrie algoritmul aferent compactării prețurilor unui anumit produs.
Să presupunem că avem un Map în care cheia este id-ul produsului și valoarea este o listă de versiuni de prețuri: Map<Integer, List<PriceVersion>>unde definiția unui PriceVersion este:

public class PriceVersion {
    public LocalDateTime startDate;
    public LocalDateTime endDate;
    public int price;
}

Să se scrie o funcție care să realizeze compactarea versiunilor unui anumit produs, respectând toate constrângerile impuse mai jos:

Ne dorim ca metoda de compactare să fie cât mai generică. Din acest motiv, semnătura ei va fi:

public static <T> List<T> compact(List<T> versions,
 BiPredicate<T, T> mergePredicate,
 BiFunction<T, T, T> mergeFunction)

Complexitatea legată de timp trebuie să fie O(n), iar cea de spațiu maxim tot O(n).

  • List<T> este lista de versiuni de prețuri primită ca și input.
  • BiPredicate<T, T> reprezintă un predicat prin care se poate decide dacă operația de compacting va avea loc sau nu. Predicatul va returna true dacă compactarea va avea loc, altfel false.
  • BiFunction<T, T, T> reprezintă o funcție prin care se va realiza compactarea a două versiuni consecutive.

O posibilă implementare în Java este descrisă mai jos:

public static <T> List<T> compact(final List<T> versions,
                                  final BiPredicate<T, T> mergePredicate,
                                  final BiFunction<T, T, T> mergeFunction) {
    final int size = versions.size();
    if (size <= 1) {
        return versions;
    }

    final List<T> results = new ArrayList<>();
    T lastCompactedVersion = versions.get(0);
    for (int i = 1; i < size; i++) {
        final T currentVersion = versions.get(i);
        if (mergePredicate.test(lastCompactedVersion, currentVersion)) {
            lastCompactedVersion = mergeFunction.apply(lastCompactedVersion, currentVersion);
        } else {
            results.add(lastCompactedVersion);
            lastCompactedVersion = currentVersion;
        }
    }
    results.add(lastCompactedVersion);

    return results;
}

Temă de casă: Câte teste unitare sunteți dispuși să scrieți pentru a testa algoritmul?

Other Related Posts:

Algoritm pentru planificarea optimă a unor pași dintr-un workflow

Algoritm pentru planificarea optimă a unor pași dintr-un workflow

Să spunem că vrei să implementezi un algoritm de planificare automată a unui set de pași, parte a unui workflow, în care fiecare pas poate depinde de unul sau mai mulți pași din acesta. Bineînțeles că algoritmul pe care dorim să îl implementăm, se dorește a fi cât mai optim, în așa fel încât să genereze cea mai bună variantă de planificare a tuturor pașilor (unii pași pot fi în paralel executați), în așa fel încât să se minimizeze timpul de rulare al întreg workflow-ului.

14th Nov 2020 - Mihai Surdeanu