Courbes de Lance - Solution

On se retrouve aujourd'hui pour la solution du précédent #KataOfTheWeek proposé par Damien en début de semaine !

Voici la solution proposée en Java 11:

import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static io.lalahtalks.lancescurve.StringUtils.indent;

public class LancesCurveComputing {

    public static void main(String[] args) {
        var requirements = getRequirements();
        var lancesCurve = new LancesCurveComputer<>(requirements, 0, Integer::sum).compute();
        System.out.println(lancesCurve);
    }

    private static Collection<Requirement<Integer>> getRequirements() {
        var requirementA = getRequirementA();
        var requirementB = getRequirementB();
        var requirementC = getRequirementC();
        var requirementD = getRequirementD();
        return List.of(requirementA, requirementB, requirementC, requirementD);
    }

    private static Requirement<Integer> getRequirementA() {
        var requirementAStart = Instant.parse("2021-07-05T00:00:00Z");
        var requirementAEnd = Instant.parse("2021-07-12T00:00:00Z");
        return new Requirement<>(1, requirementAStart, requirementAEnd);
    }

    private static Requirement<Integer> getRequirementB() {
        var requirementBStart = Instant.parse("2021-07-06T20:00:00Z");
        var requirementBEnd = Instant.parse("2021-07-07T00:00:00Z");
        return new Requirement<>(2, requirementBStart, requirementBEnd);
    }

    private static Requirement<Integer> getRequirementC() {
        var requirementCStart = Instant.parse("2021-07-06T22:00:00Z");
        var requirementCEnd = Instant.parse("2021-07-07T02:00:00Z");
        return new Requirement<>(1, requirementCStart, requirementCEnd);
    }

    private static Requirement<Integer> getRequirementD() {
        var requirementDStart = Instant.parse("2021-07-07T02:00:00Z");
        var requirementDEnd = Instant.parse("2021-07-07T04:00:00Z");
        return new Requirement<>(1, requirementDStart, requirementDEnd);
    }

    private LancesCurveComputing() {

    }

}

final class LancesCurveComputer<T> {

    private final Collection<Requirement<T>> requirements;
    private final T identity;
    private final BinaryOperator<T> reducer;

    LancesCurveComputer(Collection<Requirement<T>> requirements, T identity, BinaryOperator<T> reducer) {
        this.requirements = requirements;
        this.identity = identity;
        this.reducer = reducer;
    }

    LancesCurve<T> compute() {
        var abscissas = getOrderedAbscissas();
        var intervals = getOrderedIntervals(abscissas);
        var resultRequirements = getOrderedRequirements(intervals);
        var smoothed = smooth(resultRequirements);
        return new LancesCurve<>(smoothed);
    }

    private List<Requirement<T>> smooth(List<Requirement<T>> requirements) {
        if (requirements.isEmpty()) {
            return List.of();
        }

        var previous = requirements.get(0);
        var smoothed = new ArrayList<Requirement<T>>();
        for (var i = 1; i < requirements.size(); i++) {
            var current = requirements.get(i);
            if (current.getValue().equals(previous.getValue())) {
                previous = new Requirement<>(
                        previous.getValue(),
                        previous.getInterval().getStart(),
                        current.getInterval().getEnd());
            } else {
                smoothed.add(previous);
                previous = current;
            }
        }

        smoothed.add(previous);

        return List.copyOf(smoothed);
    }

    private List<Requirement<T>> getOrderedRequirements(List<InstantInterval> intervals) {
        var resultRequirements = new ArrayList<Requirement<T>>();
        for (var interval : intervals) {
            var value = requirements.stream()
                    .filter(r -> r.getInterval().intersects(interval))
                    .map(Requirement::getValue)
                    .reduce(identity, reducer);
            var resultRequirement = new Requirement<>(value, interval);
            resultRequirements.add(resultRequirement);
        }
        return resultRequirements;
    }

    private List<InstantInterval> getOrderedIntervals(List<Instant> orderedAbscissas) {
        var intervals = new ArrayList<InstantInterval>();
        for (var i = 0; i < orderedAbscissas.size() - 1; i++) {
            var start = orderedAbscissas.get(i);
            var end = orderedAbscissas.get(i + 1);
            var interval = new InstantInterval(start, end);
            intervals.add(interval);
        }
        return intervals;
    }

    private List<Instant> getOrderedAbscissas() {
        return requirements.stream()
                .flatMap(it -> Stream.of(it.getInterval().getStart(), it.getInterval().getEnd()))
                .distinct()
                .sorted()
                .collect(Collectors.toList());
    }

}

final class InstantInterval {

    private final Instant start;
    private final Instant end; // exclusive

    InstantInterval(Instant start, Instant end) {
        if (!end.isAfter(start)) {
            throw new IllegalArgumentException("end must be strictly after start");
        }
        this.start = start;
        this.end = end;
    }

    Instant getStart() {
        return start;
    }

    Instant getEnd() {
        return end;
    }

    boolean contains(Instant instant) {
        return !instant.isBefore(start)
                && instant.isBefore(end);
    }

    boolean containsExcludingBounds(Instant instant) {
        return instant.isAfter(start)
                && instant.isBefore(end);
    }

    boolean intersects(InstantInterval other) {
        return contains(other.start) || containsExcludingBounds(other.end);
    }

}

final class Requirement<T> {

    private final T value;
    private final InstantInterval interval;

    Requirement(T value, InstantInterval interval) {
        this.value = value;
        this.interval = interval;
    }

    Requirement(T value, Instant start, Instant end) {
        this(value, new InstantInterval(start, end));
    }

    T getValue() {
        return value;
    }

    InstantInterval getInterval() {
        return interval;
    }

    @Override
    public String toString() {
        return "Requirement {" +
                "\n  value = " + value +
                "\n  start = " + interval.getStart() +
                "\n  end   = " + interval.getEnd() +
                "\n}";
    }

}

final class LancesCurve<T> {

    private final List<Requirement<T>> requirements;

    LancesCurve(List<Requirement<T>> requirements) {
        this.requirements = List.copyOf(requirements);
    }

    @Override
    public String toString() {
        var builder = new StringBuilder();
        requirements.forEach(it -> builder.append('\n').append(it));
        return "LancesCurve {" +
                indent(builder.toString()) +
                "\n}";
    }

}

class StringUtils {

    static String indent(String s) {
        return s.replace("\n", "\n  ");
    }

    private StringUtils() {

    }

}

A bientôt pour un nouveau #KataOfTheWeek !

TakiVeille

TakiVeille