From 3d08ca3f58c0639ec42188eb46704b064f35adaf Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Thu, 18 Apr 2024 16:36:07 -0400 Subject: [PATCH 1/9] feat: Move the consecutive interval collector to core - Renamed the collector to "concurrent usage", since it is mostly useful for tracking the maximum usage of a limited resource - Add two new methods for getting the minimum and maximum concurrent usage - Fix bugs in the consecutive interval collector; in particular handling duplicate intervals (previously duplicate intervals were not added). --- .../score/stream/ConstraintCollectors.java | 254 ++++++++++++ .../score/stream/common/ConcurrentUsage.java | 8 +- .../stream/common/ConcurrentUsageInfo.java | 16 + .../score/stream/common}/IntervalBreak.java | 16 +- .../collector/ConcurrentUsageCalculator.java | 38 ++ .../stream/collector/ObjectCalculator.java | 4 +- .../stream/collector/SequenceCalculator.java | 1 + .../ConcurrentUsageBiConstraintCollector.java | 49 +++ .../bi/InnerBiConstraintCollectors.java | 11 + .../bi/ObjectCalculatorBiCollector.java | 4 +- .../concurrentUsage/ConcurrentUsageImpl.java | 128 ++++-- .../ConcurrentUsageInfoImpl.java | 85 ++-- .../collector/concurrentUsage}/Interval.java | 5 +- .../concurrentUsage/IntervalBreakImpl.java | 72 ++++ .../concurrentUsage}/IntervalSplitPoint.java | 15 +- .../concurrentUsage}/IntervalTree.java | 31 +- .../IntervalTreeIterator.java | 2 +- .../concurrentUsage/TreeMultiSet.java | 87 +++++ .../{ => consecutive}/BreakImpl.java | 2 +- .../{ => consecutive}/ComparableValue.java | 2 +- .../{ => consecutive}/ConsecutiveSetTree.java | 2 +- .../{ => consecutive}/SequenceImpl.java | 2 +- ...oncurrentUsageQuadConstraintCollector.java | 51 +++ .../quad/InnerQuadConstraintCollectors.java | 11 + .../quad/ObjectCalculatorQuadCollector.java | 5 +- ...ConcurrentUsageTriConstraintCollector.java | 50 +++ .../tri/InnerTriConstraintCollectors.java | 11 + .../tri/ObjectCalculatorTriCollector.java | 4 +- ...ConcurrentUsageUniConstraintCollector.java | 49 +++ .../uni/InnerUniConstraintCollectors.java | 11 + .../uni/ObjectCalculatorUniCollector.java | 4 +- .../AbstractConstraintCollectorsTest.java | 19 + .../bi/InnerBiConstraintCollectorsTest.java | 29 ++ .../concurrentUsage}/IntervalTreeTest.java | 116 ++++-- .../concurrentUsage}/IterableList.java | 2 +- .../ConsecutiveSetTreeTest.java | 2 +- .../{ => consecutive}/IterableList.java | 2 +- .../InnerQuadConstraintCollectorsTest.java | 29 ++ .../tri/InnerTriConstraintCollectorsTest.java | 29 ++ .../uni/InnerUniConstraintCollectorsTest.java | 29 ++ .../ExperimentalConstraintCollectors.java | 368 ------------------ .../api/ConsecutiveIntervalInfo.java | 16 - .../experimental/impl/IntervalBreakImpl.java | 54 --- .../ExperimentalConstraintCollectorsTest.java | 76 ---- 44 files changed, 1137 insertions(+), 664 deletions(-) rename examples/src/main/java/ai/timefold/solver/examples/common/experimental/api/IntervalCluster.java => core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsage.java (52%) create mode 100644 core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsageInfo.java rename {examples/src/main/java/ai/timefold/solver/examples/common/experimental/api => core/src/main/java/ai/timefold/solver/core/api/score/stream/common}/IntervalBreak.java (70%) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConcurrentUsageCalculator.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConcurrentUsageBiConstraintCollector.java rename examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalClusterImpl.java => core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageImpl.java (55%) rename examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/ConsecutiveIntervalInfoImpl.java => core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageInfoImpl.java (80%) rename {examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl => core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage}/Interval.java (86%) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalBreakImpl.java rename {examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl => core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage}/IntervalSplitPoint.java (87%) rename {examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl => core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage}/IntervalTree.java (71%) rename {examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl => core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage}/IntervalTreeIterator.java (93%) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/TreeMultiSet.java rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{ => consecutive}/BreakImpl.java (96%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{ => consecutive}/ComparableValue.java (96%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{ => consecutive}/ConsecutiveSetTree.java (99%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{ => consecutive}/SequenceImpl.java (98%) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConcurrentUsageQuadConstraintCollector.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConcurrentUsageTriConstraintCollector.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConcurrentUsageUniConstraintCollector.java rename {examples/src/test/java/ai/timefold/solver/examples/common/experimental/impl => core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage}/IntervalTreeTest.java (76%) rename {examples/src/test/java/ai/timefold/solver/examples/common/experimental/impl => core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage}/IterableList.java (94%) rename core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/{ => consecutive}/ConsecutiveSetTreeTest.java (99%) rename core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/{ => consecutive}/IterableList.java (93%) delete mode 100644 examples/src/main/java/ai/timefold/solver/examples/common/experimental/ExperimentalConstraintCollectors.java delete mode 100644 examples/src/main/java/ai/timefold/solver/examples/common/experimental/api/ConsecutiveIntervalInfo.java delete mode 100644 examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalBreakImpl.java delete mode 100644 examples/src/test/java/ai/timefold/solver/examples/common/experimental/ExperimentalConstraintCollectorsTest.java diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java index 4d9a63b80d..88778874f7 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java @@ -5,6 +5,7 @@ import java.math.RoundingMode; import java.time.Duration; import java.time.Period; +import java.time.temporal.Temporal; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; @@ -36,6 +37,7 @@ import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.function.TriPredicate; import ai.timefold.solver.core.api.score.stream.bi.BiConstraintCollector; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; import ai.timefold.solver.core.api.score.stream.quad.QuadConstraintCollector; import ai.timefold.solver.core.api.score.stream.tri.TriConstraintCollector; @@ -1955,6 +1957,258 @@ public static UniConstraintCollector + * {@code + * IntervalClusters: [[Shift from=2, to=4] [Shift from=3, to=5]], [[Shift from=6, to=7] [Shift from=7, to=8]] + * Breaks: [[Break from=5, to=6, length=1]] + * } + * + * + * @param startMap Maps the fact to its start + * @param endMap Maps the fact to its end + * @param differenceFunction Computes the difference between two points. The second argument is always + * larger than the first (ex: {@link Duration#between} + * or {@code (a,b) -> b - a}). + * @param type of the first mapped fact + * @param type of the fact endpoints + * @param type of difference between points + * @return never null + */ + public static , DifferenceType_ extends Comparable> + UniConstraintCollector> + concurrentUsage(Function startMap, Function endMap, + BiFunction differenceFunction) { + return InnerUniConstraintCollectors.consecutiveUsages(ConstantLambdaUtils.identity(), startMap, endMap, + differenceFunction); + } + + /** + * Specialized version of {@link #concurrentUsage(Function,Function,BiFunction)} for + * {@link Temporal} types. + * + * @param type of the first mapped fact + * @param temporal type of the endpoints + * @param startMap Maps the fact to its start + * @param endMap Maps the fact to its end + * @return never null + */ + public static > + UniConstraintCollector> + concurrentUsageByTime(Function startMap, Function endMap) { + return concurrentUsage(startMap, endMap, Duration::between); + } + + /** + * Specialized version of {@link #concurrentUsage(Function,Function,BiFunction)} for Long. + * + * @param startMap Maps the fact to its start + * @param endMap Maps the fact to its end + * @param type of the first mapped fact + * @return never null + */ + public static UniConstraintCollector> + concurrentUsage(ToLongFunction startMap, ToLongFunction endMap) { + return concurrentUsage(startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); + } + + /** + * As defined by {@link #concurrentUsage(Function,Function,BiFunction)}. + * + * @param intervalMap Maps both facts to an item in the cluster + * @param startMap Maps the item to its start + * @param endMap Maps the item to its end + * @param differenceFunction Computes the difference between two points. The second argument is always + * larger than the first (ex: {@link Duration#between} + * or {@code (a,b) -> b - a}). + * @param type of the first mapped fact + * @param type of the second mapped fact + * @param type of the item in the cluster + * @param type of the item endpoints + * @param type of difference between points + * @return never null + */ + public static , DifferenceType_ extends Comparable> + BiConstraintCollector> + concurrentUsage(BiFunction intervalMap, Function startMap, + Function endMap, + BiFunction differenceFunction) { + return InnerBiConstraintCollectors.consecutiveUsages(intervalMap, startMap, endMap, differenceFunction); + } + + /** + * As defined by {@link #concurrentUsageByTime(Function,Function)}. + * + * @param intervalMap Maps the three facts to an item in the cluster + * @param startMap Maps the fact to its start + * @param endMap Maps the fact to its end + * @param type of the first mapped fact + * @param type of the second mapped fact + * @param type of the item in the cluster + * @param temporal type of the endpoints + * @return never null + */ + public static > + BiConstraintCollector> + concurrentUsageByTime(BiFunction intervalMap, + Function startMap, Function endMap) { + return concurrentUsage(intervalMap, startMap, endMap, Duration::between); + } + + /** + * As defined by {@link #concurrentUsage(ToLongFunction, ToLongFunction)}. + * + * @param startMap Maps the fact to its start + * @param endMap Maps the fact to its end + * @param type of the first mapped fact + * @param type of the second mapped fact + * @param type of the item in the cluster + * @return never null + */ + public static + BiConstraintCollector> + concurrentUsage(BiFunction intervalMap, ToLongFunction startMap, + ToLongFunction endMap) { + return concurrentUsage(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); + } + + /** + * As defined by {@link #concurrentUsage(Function,Function,BiFunction)}. + * + * @param intervalMap Maps the three facts to an item in the cluster + * @param startMap Maps the item to its start + * @param endMap Maps the item to its end + * @param differenceFunction Computes the difference between two points. The second argument is always + * larger than the first (ex: {@link Duration#between} + * or {@code (a,b) -> b - a}). + * @param type of the first mapped fact + * @param type of the second mapped fact + * @param type of the third mapped fact + * @param type of the item in the cluster + * @param type of the item endpoints + * @param type of difference between points + * @return never null + */ + public static , DifferenceType_ extends Comparable> + TriConstraintCollector> + concurrentUsage(TriFunction intervalMap, Function startMap, + Function endMap, + BiFunction differenceFunction) { + return InnerTriConstraintCollectors.consecutiveUsages(intervalMap, startMap, endMap, differenceFunction); + } + + /** + * As defined by {@link #concurrentUsageByTime(Function,Function)}. + * + * @param intervalMap Maps the three facts to an item in the cluster + * @param startMap Maps the fact to its start + * @param endMap Maps the fact to its end + * @param type of the first mapped fact + * @param type of the second mapped fact + * @param type of the third mapped fact + * @param type of the item in the cluster + * @param temporal type of the endpoints + * @return never null + */ + public static > + TriConstraintCollector> + concurrentUsageByTime(TriFunction intervalMap, + Function startMap, Function endMap) { + return concurrentUsage(intervalMap, startMap, endMap, Duration::between); + } + + /** + * As defined by {@link #concurrentUsage(ToLongFunction, ToLongFunction)}. + * + * @param startMap Maps the fact to its start + * @param endMap Maps the fact to its end + * @param type of the first mapped fact + * @param type of the second mapped fact + * @param type of the third mapped fact + * @param type of the item in the cluster + * @return never null + */ + public static + TriConstraintCollector> + concurrentUsage(TriFunction intervalMap, ToLongFunction startMap, + ToLongFunction endMap) { + return concurrentUsage(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); + } + + /** + * As defined by {@link #concurrentUsage(Function,Function,BiFunction)}. + * + * @param intervalMap Maps the four facts to an item in the cluster + * @param startMap Maps the item to its start + * @param endMap Maps the item to its end + * @param differenceFunction Computes the difference between two points. The second argument is always + * larger than the first (ex: {@link Duration#between} + * or {@code (a,b) -> b - a}). + * @param type of the first mapped fact + * @param type of the second mapped fact + * @param type of the third mapped fact + * @param type of the fourth mapped fact + * @param type of the item in the cluster + * @param type of the item endpoints + * @param type of difference between points + * @return never null + */ + public static , DifferenceType_ extends Comparable> + QuadConstraintCollector> + concurrentUsage(QuadFunction intervalMap, + Function startMap, Function endMap, + BiFunction differenceFunction) { + return InnerQuadConstraintCollectors.consecutiveUsages(intervalMap, startMap, endMap, differenceFunction); + } + + /** + * As defined by {@link #concurrentUsageByTime(Function,Function)}. + * + * @param intervalMap Maps the three facts to an item in the cluster + * @param startMap Maps the fact to its start + * @param endMap Maps the fact to its end + * @param type of the first mapped fact + * @param type of the second mapped fact + * @param type of the third mapped fact + * @param type of the fourth mapped fact + * @param type of the item in the cluster + * @param temporal type of the endpoints + * @return never null + */ + public static > + QuadConstraintCollector> + concurrentUsageByTime(QuadFunction intervalMap, + Function startMap, Function endMap) { + return concurrentUsage(intervalMap, startMap, endMap, Duration::between); + } + + /** + * As defined by {@link #concurrentUsage(ToLongFunction, ToLongFunction)}. + * + * @param startMap Maps the fact to its start + * @param endMap Maps the fact to its end + * @param type of the first mapped fact + * @param type of the second mapped fact + * @param type of the third mapped fact + * @param type of the fourth mapped fact + * @param type of the item in the cluster + * @return never null + */ + public static + QuadConstraintCollector> + concurrentUsage(QuadFunction intervalMap, ToLongFunction startMap, + ToLongFunction endMap) { + return concurrentUsage(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); + } + private ConstraintCollectors() { } } diff --git a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/api/IntervalCluster.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsage.java similarity index 52% rename from examples/src/main/java/ai/timefold/solver/examples/common/experimental/api/IntervalCluster.java rename to core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsage.java index 0876c1c5dd..6f4c5c36fd 100644 --- a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/api/IntervalCluster.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsage.java @@ -1,11 +1,15 @@ -package ai.timefold.solver.examples.common.experimental.api; +package ai.timefold.solver.core.api.score.stream.common; -public interface IntervalCluster, Difference_ extends Comparable> +public interface ConcurrentUsage, Difference_ extends Comparable> extends Iterable { int size(); boolean hasOverlap(); + int getMinimumConcurrentUsage(); + + int getMaximumConcurrentUsage(); + Difference_ getLength(); Point_ getStart(); diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsageInfo.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsageInfo.java new file mode 100644 index 0000000000..e7a154ec6a --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsageInfo.java @@ -0,0 +1,16 @@ +package ai.timefold.solver.core.api.score.stream.common; + +public interface ConcurrentUsageInfo, Difference_ extends Comparable> { + + /** + * @return never null, an iterable that iterates through the interval clusters + * contained in the collection in ascending order + */ + Iterable> getConcurrentUsages(); + + /** + * @return never null, an iterable that iterates through the breaks contained in + * the collection in ascending order + */ + Iterable> getBreaks(); +} diff --git a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/api/IntervalBreak.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/IntervalBreak.java similarity index 70% rename from examples/src/main/java/ai/timefold/solver/examples/common/experimental/api/IntervalBreak.java rename to core/src/main/java/ai/timefold/solver/core/api/score/stream/common/IntervalBreak.java index 048ec7d277..827665fa13 100644 --- a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/api/IntervalBreak.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/IntervalBreak.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.examples.common.experimental.api; +package ai.timefold.solver.core.api.score.stream.common; /** * An IntervalBreak is a gap between two consecutive interval clusters. For instance, @@ -11,12 +11,12 @@ public interface IntervalBreak, Dif /** * @return never null, the interval cluster leading directly into this */ - IntervalCluster getPreviousIntervalCluster(); + ConcurrentUsage getPreviousConcurrentUsage(); /** * @return never null, the interval cluster immediately following this */ - IntervalCluster getNextIntervalCluster(); + ConcurrentUsage getNextConcurrentUsage(); /** * Return the end of the sequence before this break. For the @@ -24,8 +24,8 @@ public interface IntervalBreak, Dif * * @return never null, the item this break is directly after */ - default Point_ getPreviousIntervalClusterEnd() { - return getPreviousIntervalCluster().getEnd(); + default Point_ getPreviousConcurrentUsageEnd() { + return getPreviousConcurrentUsage().getEnd(); }; /** @@ -34,13 +34,13 @@ default Point_ getPreviousIntervalClusterEnd() { * * @return never null, the item this break is directly before */ - default Point_ getNextIntervalClusterStart() { - return getNextIntervalCluster().getStart(); + default Point_ getNextConcurrentUsageStart() { + return getNextConcurrentUsage().getStart(); } /** * Return the length of the break, which is the difference - * between {@link #getNextIntervalClusterStart()} and {@link #getPreviousIntervalClusterEnd()}. For the + * between {@link #getNextConcurrentUsageStart()} and {@link #getPreviousConcurrentUsageEnd()}. For the * break between 6 and 10, this will return 4. * * @return never null, the length of this break diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConcurrentUsageCalculator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConcurrentUsageCalculator.java new file mode 100644 index 0000000000..0985475cf6 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConcurrentUsageCalculator.java @@ -0,0 +1,38 @@ +package ai.timefold.solver.core.impl.score.stream.collector; + +import java.util.function.BiFunction; +import java.util.function.Function; + +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage.IntervalTree; + +public final class ConcurrentUsageCalculator, Difference_ extends Comparable> + implements ObjectCalculator> { + + private final IntervalTree context; + + public ConcurrentUsageCalculator(Function startMap, + Function endMap, + BiFunction differenceFunction) { + this.context = new IntervalTree<>( + startMap, + endMap, + differenceFunction); + } + + @Override + public void insert(Interval_ result) { + context.add(context.getInterval(result)); + } + + @Override + public void retract(Interval_ result) { + context.remove(context.getInterval(result)); + } + + @Override + public ConcurrentUsageInfo result() { + return context.getConsecutiveIntervalData(); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ObjectCalculator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ObjectCalculator.java index bbc26cf254..8cf6197491 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ObjectCalculator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ObjectCalculator.java @@ -1,8 +1,8 @@ package ai.timefold.solver.core.impl.score.stream.collector; public sealed interface ObjectCalculator - permits IntDistinctCountCalculator, LongDistinctCountCalculator, ReferenceAverageCalculator, ReferenceSumCalculator, - SequenceCalculator { + permits ConcurrentUsageCalculator, IntDistinctCountCalculator, LongDistinctCountCalculator, ReferenceAverageCalculator, + ReferenceSumCalculator, SequenceCalculator { void insert(Input_ input); void retract(Input_ input); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/SequenceCalculator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/SequenceCalculator.java index edc7744b0b..54db9dc5fb 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/SequenceCalculator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/SequenceCalculator.java @@ -4,6 +4,7 @@ import java.util.function.ToIntFunction; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; +import ai.timefold.solver.core.impl.score.stream.collector.consecutive.ConsecutiveSetTree; public final class SequenceCalculator implements ObjectCalculator> { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConcurrentUsageBiConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConcurrentUsageBiConstraintCollector.java new file mode 100644 index 0000000000..52f2356f54 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConcurrentUsageBiConstraintCollector.java @@ -0,0 +1,49 @@ +package ai.timefold.solver.core.impl.score.stream.collector.bi; + +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.impl.score.stream.collector.ConcurrentUsageCalculator; + +final class ConcurrentUsageBiConstraintCollector, Difference_ extends Comparable> + extends + ObjectCalculatorBiCollector, ConcurrentUsageCalculator> { + + private final Function startMap; + private final Function endMap; + private final BiFunction differenceFunction; + + public ConcurrentUsageBiConstraintCollector(BiFunction mapper, + Function startMap, Function endMap, + BiFunction differenceFunction) { + super(mapper); + this.startMap = startMap; + this.endMap = endMap; + this.differenceFunction = differenceFunction; + } + + @Override + public Supplier> supplier() { + return () -> new ConcurrentUsageCalculator<>(startMap, endMap, differenceFunction); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ConcurrentUsageBiConstraintCollector that)) + return false; + if (!super.equals(o)) + return false; + return Objects.equals(startMap, that.startMap) && Objects.equals(endMap, + that.endMap) && Objects.equals(differenceFunction, that.differenceFunction); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), startMap, endMap, differenceFunction); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectors.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectors.java index 784be57c56..0606df9124 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectors.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectors.java @@ -22,6 +22,7 @@ import ai.timefold.solver.core.api.function.QuadFunction; import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.score.stream.bi.BiConstraintCollector; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; import ai.timefold.solver.core.impl.score.stream.collector.ReferenceAverageCalculator; @@ -208,6 +209,16 @@ public static BiConstraintCollector(resultMap, indexMap); } + public static , Difference_ extends Comparable> + BiConstraintCollector> + consecutiveUsages(BiFunction mapper, + Function startMap, + Function endMap, + BiFunction differenceFunction) { + return new ConcurrentUsageBiConstraintCollector<>(mapper, startMap, endMap, + differenceFunction); + } + public static BiConstraintCollector collectAndThen(BiConstraintCollector delegate, Function mappingFunction) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ObjectCalculatorBiCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ObjectCalculatorBiCollector.java index 52bc89f32b..87f85dbcec 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ObjectCalculatorBiCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ObjectCalculatorBiCollector.java @@ -10,8 +10,8 @@ abstract sealed class ObjectCalculatorBiCollector> implements BiConstraintCollector - permits AverageReferenceBiCollector, ConsecutiveSequencesBiConstraintCollector, CountDistinctIntBiCollector, - CountDistinctLongBiCollector, SumReferenceBiCollector { + permits AverageReferenceBiCollector, ConcurrentUsageBiConstraintCollector, ConsecutiveSequencesBiConstraintCollector, + CountDistinctIntBiCollector, CountDistinctLongBiCollector, SumReferenceBiCollector { protected final BiFunction mapper; public ObjectCalculatorBiCollector(BiFunction mapper) { diff --git a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalClusterImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageImpl.java similarity index 55% rename from examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalClusterImpl.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageImpl.java index 5d3c6be1df..a2a5acdc4a 100644 --- a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalClusterImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageImpl.java @@ -1,24 +1,28 @@ -package ai.timefold.solver.examples.common.experimental.impl; +package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; import java.util.Iterator; import java.util.NavigableSet; +import java.util.Objects; import java.util.function.BiFunction; -import ai.timefold.solver.examples.common.experimental.api.IntervalCluster; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsage; -final class IntervalClusterImpl, Difference_ extends Comparable> - implements IntervalCluster { +final class ConcurrentUsageImpl, Difference_ extends Comparable> + implements ConcurrentUsage { private final NavigableSet> splitPointSet; - private final BiFunction differenceFunction; + private final BiFunction differenceFunction; private IntervalSplitPoint startSplitPoint; private IntervalSplitPoint endSplitPoint; private int count; + private int minimumOverlap; + private int maximumOverlap; private boolean hasOverlap; - IntervalClusterImpl(NavigableSet> splitPointSet, - BiFunction differenceFunction, IntervalSplitPoint start) { + ConcurrentUsageImpl(NavigableSet> splitPointSet, + BiFunction differenceFunction, + IntervalSplitPoint start) { if (start == null) { throw new IllegalArgumentException("start (" + start + ") is null"); } @@ -30,14 +34,20 @@ final class IntervalClusterImpl, Di this.endSplitPoint = start; this.differenceFunction = differenceFunction; this.count = 0; + this.minimumOverlap = Integer.MAX_VALUE; + this.maximumOverlap = Integer.MIN_VALUE; var activeIntervals = 0; var anyOverlap = false; var current = start; do { this.count += current.intervalsStartingAtSplitPointSet.size(); activeIntervals += current.intervalsStartingAtSplitPointSet.size() - current.intervalsEndingAtSplitPointSet.size(); - if (activeIntervals > 1) { - anyOverlap = true; + if (activeIntervals > 0) { + minimumOverlap = Math.min(minimumOverlap, activeIntervals); + maximumOverlap = Math.max(maximumOverlap, activeIntervals); + if (activeIntervals > 1) { + anyOverlap = true; + } } current = splitPointSet.higher(current); } while (activeIntervals > 0 && current != null); @@ -45,14 +55,19 @@ final class IntervalClusterImpl, Di this.endSplitPoint = (current != null) ? splitPointSet.lower(current) : splitPointSet.last(); } - IntervalClusterImpl(NavigableSet> splitPointSet, - BiFunction differenceFunction, IntervalSplitPoint start, - IntervalSplitPoint end, int count, boolean hasOverlap) { + ConcurrentUsageImpl(NavigableSet> splitPointSet, + BiFunction differenceFunction, + IntervalSplitPoint start, + IntervalSplitPoint end, int count, + int minimumOverlap, int maximumOverlap, + boolean hasOverlap) { this.splitPointSet = splitPointSet; this.startSplitPoint = start; this.endSplitPoint = end; this.differenceFunction = differenceFunction; this.count = count; + this.minimumOverlap = minimumOverlap; + this.maximumOverlap = maximumOverlap; this.hasOverlap = hasOverlap; } @@ -75,14 +90,16 @@ void addInterval(Interval interval) { if (interval.getEndSplitPoint().compareTo(endSplitPoint) > 0) { endSplitPoint = splitPointSet.ceiling(interval.getEndSplitPoint()); } + minimumOverlap = -1; + maximumOverlap = -1; count++; } - Iterable> removeInterval(Interval interval) { + Iterable> removeInterval(Interval interval) { return IntervalClusterIterator::new; } - void mergeIntervalCluster(IntervalClusterImpl laterIntervalCluster) { + void mergeIntervalCluster(ConcurrentUsageImpl laterIntervalCluster) { if (endSplitPoint.compareTo(laterIntervalCluster.startSplitPoint) > 0) { hasOverlap = true; } @@ -90,6 +107,8 @@ void mergeIntervalCluster(IntervalClusterImpl la endSplitPoint = laterIntervalCluster.endSplitPoint; } count += laterIntervalCluster.count; + minimumOverlap = -1; + maximumOverlap = -1; hasOverlap |= laterIntervalCluster.hasOverlap; } @@ -108,6 +127,37 @@ public boolean hasOverlap() { return hasOverlap; } + private void recalculateMinimumAndMaximumOverlap() { + var current = startSplitPoint; + var activeIntervals = 0; + minimumOverlap = Integer.MAX_VALUE; + maximumOverlap = Integer.MIN_VALUE; + do { + activeIntervals += current.intervalsStartingAtSplitPointSet.size() - current.intervalsEndingAtSplitPointSet.size(); + if (activeIntervals > 0) { + minimumOverlap = Math.min(minimumOverlap, activeIntervals); + maximumOverlap = Math.max(maximumOverlap, activeIntervals); + } + current = splitPointSet.higher(current); + } while (activeIntervals > 0 && current != null); + } + + @Override + public int getMinimumConcurrentUsage() { + if (minimumOverlap == -1) { + recalculateMinimumAndMaximumOverlap(); + } + return minimumOverlap; + } + + @Override + public int getMaximumConcurrentUsage() { + if (maximumOverlap == -1) { + recalculateMinimumAndMaximumOverlap(); + } + return maximumOverlap; + } + @Override public Point_ getStart() { return startSplitPoint.splitPoint; @@ -123,19 +173,44 @@ public Difference_ getLength() { return differenceFunction.apply(startSplitPoint.splitPoint, endSplitPoint.splitPoint); } + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ConcurrentUsageImpl that)) + return false; + return count == that.count && + getMinimumConcurrentUsage() == that.getMinimumConcurrentUsage() + && getMaximumConcurrentUsage() == that.getMaximumConcurrentUsage() + && hasOverlap == that.hasOverlap && Objects.equals( + splitPointSet, that.splitPointSet) + && Objects.equals(startSplitPoint, + that.startSplitPoint) + && Objects.equals(endSplitPoint, that.endSplitPoint); + } + + @Override + public int hashCode() { + return Objects.hash(splitPointSet, startSplitPoint, endSplitPoint, count, + getMinimumConcurrentUsage(), getMaximumConcurrentUsage(), hasOverlap); + } + @Override public String toString() { - return "IntervalCluster{" + - "startSplitPoint=" + startSplitPoint + - ", endSplitPoint=" + endSplitPoint + + return "ConcurrentUsage {" + + "start=" + startSplitPoint + + ", end=" + endSplitPoint + ", count=" + count + + ", minimumOverlap=" + getMinimumConcurrentUsage() + + ", maximumOverlap=" + getMaximumConcurrentUsage() + ", hasOverlap=" + hasOverlap + + ", set=" + splitPointSet + '}'; } // TODO: Make this incremental by only checking between the interval's start and end points private final class IntervalClusterIterator - implements Iterator> { + implements Iterator> { private IntervalSplitPoint current = getStart(startSplitPoint); @@ -153,22 +228,27 @@ public boolean hasNext() { } @Override - public IntervalClusterImpl next() { + public ConcurrentUsageImpl next() { IntervalSplitPoint start = current; IntervalSplitPoint end; int activeIntervals = 0; + minimumOverlap = Integer.MAX_VALUE; + maximumOverlap = Integer.MIN_VALUE; count = 0; boolean anyOverlap = false; do { count += current.intervalsStartingAtSplitPointSet.size(); activeIntervals += current.intervalsStartingAtSplitPointSet.size() - current.intervalsEndingAtSplitPointSet.size(); - if (activeIntervals > 1) { - anyOverlap = true; + if (activeIntervals > 0) { + minimumOverlap = Math.min(minimumOverlap, activeIntervals); + maximumOverlap = Math.max(maximumOverlap, activeIntervals); + if (activeIntervals > 1) { + anyOverlap = true; + } } current = splitPointSet.higher(current); } while (activeIntervals > 0 && current != null); - hasOverlap = anyOverlap; if (current != null) { end = splitPointSet.lower(current); @@ -176,8 +256,10 @@ public IntervalClusterImpl next() { } else { end = splitPointSet.last(); } + hasOverlap = anyOverlap; - return new IntervalClusterImpl<>(splitPointSet, differenceFunction, start, end, count, hasOverlap); + return new ConcurrentUsageImpl<>(splitPointSet, differenceFunction, start, end, count, + minimumOverlap, maximumOverlap, hasOverlap); } } } diff --git a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/ConsecutiveIntervalInfoImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageInfoImpl.java similarity index 80% rename from examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/ConsecutiveIntervalInfoImpl.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageInfoImpl.java index 5818ddeb73..23955bccbc 100644 --- a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/ConsecutiveIntervalInfoImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageInfoImpl.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.examples.common.experimental.impl; +package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; import java.util.NavigableMap; import java.util.NavigableSet; @@ -7,20 +7,20 @@ import java.util.TreeSet; import java.util.function.BiFunction; -import ai.timefold.solver.examples.common.experimental.api.ConsecutiveIntervalInfo; -import ai.timefold.solver.examples.common.experimental.api.IntervalBreak; -import ai.timefold.solver.examples.common.experimental.api.IntervalCluster; +import ai.timefold.solver.core.api.score.stream.common.Break; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsage; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; -public final class ConsecutiveIntervalInfoImpl, Difference_ extends Comparable> - implements ConsecutiveIntervalInfo { +public final class ConcurrentUsageInfoImpl, Difference_ extends Comparable> + implements ConcurrentUsageInfo { - private final NavigableMap, IntervalClusterImpl> clusterStartSplitPointToCluster; + private final NavigableMap, ConcurrentUsageImpl> clusterStartSplitPointToCluster; private final NavigableSet> splitPointSet; private final NavigableMap, IntervalBreakImpl> clusterStartSplitPointToNextBreak; - private final BiFunction differenceFunction; + private final BiFunction differenceFunction; - public ConsecutiveIntervalInfoImpl(TreeSet> splitPointSet, - BiFunction differenceFunction) { + public ConcurrentUsageInfoImpl(TreeSet> splitPointSet, + BiFunction differenceFunction) { this.clusterStartSplitPointToCluster = new TreeMap<>(); this.clusterStartSplitPointToNextBreak = new TreeMap<>(); this.splitPointSet = splitPointSet; @@ -78,8 +78,8 @@ void addInterval(Interval interval) { var nextBreak = clusterStartSplitPointToNextBreak.get(firstIntersectedIntervalCluster.getStartSplitPoint()); if (nextBreak != null) { nextBreak.setPreviousCluster(firstIntersectedIntervalCluster); - nextBreak.setLength(differenceFunction.apply(nextBreak.getPreviousIntervalClusterEnd(), - nextBreak.getNextIntervalClusterStart())); + nextBreak.setLength(differenceFunction.apply(nextBreak.getPreviousSequenceEnd(), + nextBreak.getNextSequenceStart())); } } } @@ -90,7 +90,7 @@ private void createNewIntervalCluster(Interval interval) { // ----- //---- ----- var startSplitPoint = splitPointSet.floor(interval.getStartSplitPoint()); - var newCluster = new IntervalClusterImpl<>(splitPointSet, differenceFunction, startSplitPoint); + var newCluster = new ConcurrentUsageImpl<>(splitPointSet, differenceFunction, startSplitPoint); clusterStartSplitPointToCluster.put(startSplitPoint, newCluster); // If there a cluster after this interval, add a new break @@ -116,7 +116,7 @@ private void createNewIntervalCluster(Interval interval) { } private void removeSpannedBreaksAndUpdateIntersectedBreaks(Interval interval, - IntervalClusterImpl intervalCluster) { + ConcurrentUsageImpl intervalCluster) { var firstBreakSplitPointBeforeInterval = Objects.requireNonNullElseGet( clusterStartSplitPointToNextBreak.floorKey(interval.getStartSplitPoint()), interval::getStartSplitPoint); var intersectedIntervalBreakMap = clusterStartSplitPointToNextBreak.subMap(firstBreakSplitPointBeforeInterval, true, @@ -127,11 +127,11 @@ private void removeSpannedBreaksAndUpdateIntersectedBreaks(Interval) (intersectedIntervalBreakMap.firstEntry().getValue() - .getPreviousIntervalCluster()); + (ConcurrentUsageImpl) (intersectedIntervalBreakMap.firstEntry().getValue() + .getPreviousConcurrentUsage()); var clusterAfterFinalIntersectedBreak = - (IntervalClusterImpl) (intersectedIntervalBreakMap.lastEntry().getValue() - .getNextIntervalCluster()); + (ConcurrentUsageImpl) (intersectedIntervalBreakMap.lastEntry().getValue() + .getNextConcurrentUsage()); // All breaks that are not the first or last intersected breaks will // be removed (as interval span them) @@ -151,8 +151,8 @@ private void removeSpannedBreaksAndUpdateIntersectedBreaks(Interval) (previousBreak - .getPreviousIntervalCluster())).getStartSplitPoint(), previousBreak); + .put(((ConcurrentUsageImpl) (previousBreak + .getPreviousConcurrentUsage())).getStartSplitPoint(), previousBreak); } else { // Case: interval does not span either the first or final break // Ex: // --------- //---- ------ ----- var finalBreak = intersectedIntervalBreakMap.lastEntry().getValue(); - finalBreak.setLength(differenceFunction.apply(finalBreak.getPreviousIntervalClusterEnd(), - finalBreak.getNextIntervalClusterStart())); + finalBreak.setLength(differenceFunction.apply(finalBreak.getPreviousSequenceEnd(), + finalBreak.getNextSequenceStart())); var previousBreakEntry = intersectedIntervalBreakMap.firstEntry(); var previousBreak = previousBreakEntry.getValue(); previousBreak.setNextCluster(intervalCluster); previousBreak.setLength( - differenceFunction.apply(previousBreak.getPreviousIntervalClusterEnd(), intervalCluster.getStart())); + differenceFunction.apply(previousBreak.getPreviousSequenceEnd(), intervalCluster.getStart())); intersectedIntervalBreakMap.clear(); clusterStartSplitPointToNextBreak.put(previousBreakEntry.getKey(), previousBreak); @@ -201,17 +201,17 @@ void removeInterval(Interval interval) { var previousBreak = (previousBreakEntry != null) ? previousBreakEntry.getValue() : null; var previousIntervalCluster = (previousBreak != null) - ? (IntervalClusterImpl) previousBreak.getPreviousIntervalCluster() + ? (ConcurrentUsageImpl) previousBreak.getPreviousConcurrentUsage() : null; for (var newIntervalCluster : intervalCluster.removeInterval(interval)) { if (previousBreak != null) { previousBreak.setNextCluster(newIntervalCluster); - previousBreak.setLength(differenceFunction.apply(previousBreak.getPreviousIntervalCluster().getEnd(), + previousBreak.setLength(differenceFunction.apply(previousBreak.getPreviousConcurrentUsage().getEnd(), newIntervalCluster.getStart())); clusterStartSplitPointToNextBreak - .put(((IntervalClusterImpl) previousBreak - .getPreviousIntervalCluster()).getStartSplitPoint(), previousBreak); + .put(((ConcurrentUsageImpl) previousBreak + .getPreviousConcurrentUsage()).getStartSplitPoint(), previousBreak); } previousBreak = new IntervalBreakImpl<>(newIntervalCluster, null, null); previousIntervalCluster = newIntervalCluster; @@ -235,19 +235,38 @@ void removeInterval(Interval interval) { } @Override - public Iterable> getIntervalClusters() { + public Iterable> getConcurrentUsages() { return (Iterable) clusterStartSplitPointToCluster.values(); } @Override - public Iterable> getBreaks() { + public Iterable> getBreaks() { return (Iterable) clusterStartSplitPointToNextBreak.values(); } + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ConcurrentUsageInfoImpl that)) + return false; + return Objects.equals(clusterStartSplitPointToCluster, + that.clusterStartSplitPointToCluster) + && Objects.equals(splitPointSet, + that.splitPointSet) + && Objects.equals(clusterStartSplitPointToNextBreak, + that.clusterStartSplitPointToNextBreak); + } + + @Override + public int hashCode() { + return Objects.hash(clusterStartSplitPointToCluster, splitPointSet, clusterStartSplitPointToNextBreak); + } + @Override public String toString() { return "Clusters {" + - "intervalClusters=" + getIntervalClusters() + + "intervalClusters=" + getConcurrentUsages() + ", breaks=" + getBreaks() + '}'; } diff --git a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/Interval.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/Interval.java similarity index 86% rename from examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/Interval.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/Interval.java index 1647c4cb69..cfdab122fa 100644 --- a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/Interval.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/Interval.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.examples.common.experimental.impl; +package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; import java.util.function.Function; @@ -7,7 +7,8 @@ public final class Interval> { private final IntervalSplitPoint startSplitPoint; private final IntervalSplitPoint endSplitPoint; - public Interval(Interval_ value, Function startMapping, Function endMapping) { + public Interval(Interval_ value, Function startMapping, + Function endMapping) { this.value = value; var start = startMapping.apply(value); var end = endMapping.apply(value); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalBreakImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalBreakImpl.java new file mode 100644 index 0000000000..12475c663f --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalBreakImpl.java @@ -0,0 +1,72 @@ +package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; + +import ai.timefold.solver.core.api.score.stream.common.Break; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsage; + +final class IntervalBreakImpl, Difference_ extends Comparable> + implements Break { + private ConcurrentUsage previousCluster; + private ConcurrentUsage nextCluster; + private Difference_ length; + + IntervalBreakImpl(ConcurrentUsage previousCluster, + ConcurrentUsage nextCluster, Difference_ length) { + this.previousCluster = previousCluster; + this.nextCluster = nextCluster; + this.length = length; + } + + public ConcurrentUsage getPreviousConcurrentUsage() { + return previousCluster; + } + + public ConcurrentUsage getNextConcurrentUsage() { + return nextCluster; + } + + @Override + public boolean isFirst() { + return previousCluster == null; + } + + @Override + public boolean isLast() { + return nextCluster == null; + } + + @Override + public Point_ getPreviousSequenceEnd() { + return previousCluster.getEnd(); + } + + @Override + public Point_ getNextSequenceStart() { + return nextCluster.getStart(); + } + + @Override + public Difference_ getLength() { + return length; + } + + void setPreviousCluster(ConcurrentUsage previousCluster) { + this.previousCluster = previousCluster; + } + + void setNextCluster(ConcurrentUsage nextCluster) { + this.nextCluster = nextCluster; + } + + void setLength(Difference_ length) { + this.length = length; + } + + @Override + public String toString() { + return "IntervalBreak{" + + "previousCluster=" + previousCluster + + ", nextCluster=" + nextCluster + + ", length=" + length + + '}'; + } +} diff --git a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalSplitPoint.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalSplitPoint.java similarity index 87% rename from examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalSplitPoint.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalSplitPoint.java index 2f93e50da1..4777ae444d 100644 --- a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalSplitPoint.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalSplitPoint.java @@ -1,20 +1,18 @@ -package ai.timefold.solver.examples.common.experimental.impl; +package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; import java.util.Comparator; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.Map; import java.util.Objects; -import java.util.TreeSet; -import java.util.stream.IntStream; public class IntervalSplitPoint> implements Comparable> { final Point_ splitPoint; Map startIntervalToCountMap; Map endIntervalToCountMap; - TreeSet> intervalsStartingAtSplitPointSet; - TreeSet> intervalsEndingAtSplitPointSet; + TreeMultiSet> intervalsStartingAtSplitPointSet; + TreeMultiSet> intervalsEndingAtSplitPointSet; public IntervalSplitPoint(Point_ splitPoint) { this.splitPoint = splitPoint; @@ -23,10 +21,10 @@ public IntervalSplitPoint(Point_ splitPoint) { protected void createCollections() { startIntervalToCountMap = new IdentityHashMap<>(); endIntervalToCountMap = new IdentityHashMap<>(); - intervalsStartingAtSplitPointSet = new TreeSet<>( + intervalsStartingAtSplitPointSet = new TreeMultiSet<>( Comparator., Point_> comparing(Interval::getEnd) .thenComparingInt(interval -> System.identityHashCode(interval.getValue()))); - intervalsEndingAtSplitPointSet = new TreeSet<>( + intervalsEndingAtSplitPointSet = new TreeMultiSet<>( Comparator., Point_> comparing(Interval::getStart) .thenComparingInt(interval -> System.identityHashCode(interval.getValue()))); } @@ -75,8 +73,7 @@ public boolean containsIntervalEnding(Interval interval) { public Iterator getValuesStartingFromSplitPointIterator() { return intervalsStartingAtSplitPointSet.stream() - .flatMap(interval -> IntStream.range(0, startIntervalToCountMap.get(interval.getValue())) - .mapToObj(index -> interval.getValue())) + .map(Interval::getValue) .iterator(); } diff --git a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalTree.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTree.java similarity index 71% rename from examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalTree.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTree.java index 1645d4f7d4..77874e762f 100644 --- a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalTree.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTree.java @@ -1,23 +1,26 @@ -package ai.timefold.solver.examples.common.experimental.impl; +package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; import java.util.Iterator; import java.util.TreeSet; import java.util.function.BiFunction; import java.util.function.Function; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; + public final class IntervalTree, Difference_ extends Comparable> { - private final Function startMapping; - private final Function endMapping; + private final Function startMapping; + private final Function endMapping; private final TreeSet> splitPointSet; - private final ConsecutiveIntervalInfoImpl consecutiveIntervalData; + private final ConcurrentUsageInfoImpl consecutiveIntervalData; - public IntervalTree(Function startMapping, Function endMapping, - BiFunction differenceFunction) { + public IntervalTree(Function startMapping, + Function endMapping, + BiFunction differenceFunction) { this.startMapping = startMapping; this.endMapping = endMapping; this.splitPointSet = new TreeSet<>(); - this.consecutiveIntervalData = new ConsecutiveIntervalInfoImpl<>(splitPointSet, differenceFunction); + this.consecutiveIntervalData = new ConcurrentUsageInfoImpl<>(splitPointSet, differenceFunction); } public Interval getInterval(Interval_ intervalValue) { @@ -47,27 +50,27 @@ public Iterator iterator() { public boolean add(Interval interval) { var startSplitPoint = interval.getStartSplitPoint(); var endSplitPoint = interval.getEndSplitPoint(); - boolean isChanged; + var anyChanged = false; var flooredStartSplitPoint = splitPointSet.floor(startSplitPoint); if (flooredStartSplitPoint == null || !flooredStartSplitPoint.equals(startSplitPoint)) { splitPointSet.add(startSplitPoint); startSplitPoint.createCollections(); - isChanged = startSplitPoint.addIntervalStartingAtSplitPoint(interval); + anyChanged |= startSplitPoint.addIntervalStartingAtSplitPoint(interval); } else { - isChanged = flooredStartSplitPoint.addIntervalStartingAtSplitPoint(interval); + anyChanged |= flooredStartSplitPoint.addIntervalStartingAtSplitPoint(interval); } var ceilingEndSplitPoint = splitPointSet.ceiling(endSplitPoint); if (ceilingEndSplitPoint == null || !ceilingEndSplitPoint.equals(endSplitPoint)) { splitPointSet.add(endSplitPoint); endSplitPoint.createCollections(); - isChanged |= endSplitPoint.addIntervalEndingAtSplitPoint(interval); + anyChanged |= endSplitPoint.addIntervalEndingAtSplitPoint(interval); } else { - isChanged |= ceilingEndSplitPoint.addIntervalEndingAtSplitPoint(interval); + anyChanged |= ceilingEndSplitPoint.addIntervalEndingAtSplitPoint(interval); } - if (isChanged) { + if (true || anyChanged) { consecutiveIntervalData.addInterval(interval); } return true; @@ -97,7 +100,7 @@ public boolean remove(Interval interval) { return true; } - public ConsecutiveIntervalInfoImpl getConsecutiveIntervalData() { + public ConcurrentUsageInfo getConsecutiveIntervalData() { return consecutiveIntervalData; } } diff --git a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalTreeIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTreeIterator.java similarity index 93% rename from examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalTreeIterator.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTreeIterator.java index 6bdf972897..28c3903469 100644 --- a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalTreeIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTreeIterator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.examples.common.experimental.impl; +package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; import java.util.Iterator; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/TreeMultiSet.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/TreeMultiSet.java new file mode 100644 index 0000000000..3a9508e223 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/TreeMultiSet.java @@ -0,0 +1,87 @@ +package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; + +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.TreeMap; + +public final class TreeMultiSet extends AbstractSet { + private final TreeMap backingMap; + private int size; + + public TreeMultiSet(Comparator comparator) { + backingMap = new TreeMap<>(comparator); + size = 0; + } + + @Override + public Iterator iterator() { + return new MultiSetIterator(); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean add(T key) { + backingMap.merge(key, 1, Integer::sum); + size++; + return true; + } + + @Override + public boolean remove(Object o) { + var removed = backingMap.remove(o); + if (removed != null) { + if (removed != 1) { + backingMap.put((T) o, removed - 1); + } + size--; + return true; + } + return false; + } + + @Override + public boolean contains(Object o) { + return backingMap.containsKey(o); + } + + @Override + public boolean containsAll(Collection c) { + return backingMap.keySet().containsAll(c); + } + + private final class MultiSetIterator implements Iterator { + T currentKey = backingMap.isEmpty() ? null : backingMap.firstKey(); + int remainingForKey = currentKey != null ? backingMap.get(currentKey) : 0; + + @Override + public boolean hasNext() { + return remainingForKey > 0 || backingMap.higherKey(currentKey) != null; + } + + @Override + public T next() { + if (remainingForKey > 0) { + remainingForKey--; + return currentKey; + } + currentKey = backingMap.higherKey(currentKey); + if (currentKey == null) { + throw new NoSuchElementException(); + } + remainingForKey = backingMap.get(currentKey) - 1; + return currentKey; + } + + @Override + public void remove() { + TreeMultiSet.this.remove(currentKey); + } + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/BreakImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/BreakImpl.java similarity index 96% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/BreakImpl.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/BreakImpl.java index 0cfe69ebbf..84ea6de7c9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/BreakImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/BreakImpl.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector; +package ai.timefold.solver.core.impl.score.stream.collector.consecutive; import ai.timefold.solver.core.api.score.stream.common.Break; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ComparableValue.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ComparableValue.java similarity index 96% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ComparableValue.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ComparableValue.java index ce3ef817d0..0b023b2e73 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ComparableValue.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ComparableValue.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector; +package ai.timefold.solver.core.impl.score.stream.collector.consecutive; import java.util.TreeMap; import java.util.TreeSet; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConsecutiveSetTree.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTree.java similarity index 99% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConsecutiveSetTree.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTree.java index 997cc3f138..7368b15a0f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConsecutiveSetTree.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTree.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector; +package ai.timefold.solver.core.impl.score.stream.collector.consecutive; import java.util.Collection; import java.util.Collections; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/SequenceImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/SequenceImpl.java similarity index 98% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/SequenceImpl.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/SequenceImpl.java index 053262452a..57d5c2ae1c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/SequenceImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/SequenceImpl.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector; +package ai.timefold.solver.core.impl.score.stream.collector.consecutive; import java.util.Collection; import java.util.Collections; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConcurrentUsageQuadConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConcurrentUsageQuadConstraintCollector.java new file mode 100644 index 0000000000..a71be9f550 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConcurrentUsageQuadConstraintCollector.java @@ -0,0 +1,51 @@ +package ai.timefold.solver.core.impl.score.stream.collector.quad; + +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +import ai.timefold.solver.core.api.function.QuadFunction; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.impl.score.stream.collector.ConcurrentUsageCalculator; + +final class ConcurrentUsageQuadConstraintCollector, Difference_ extends Comparable> + extends + ObjectCalculatorQuadCollector, ConcurrentUsageCalculator> { + + private final Function startMap; + private final Function endMap; + private final BiFunction differenceFunction; + + public ConcurrentUsageQuadConstraintCollector( + QuadFunction mapper, + Function startMap, Function endMap, + BiFunction differenceFunction) { + super(mapper); + this.startMap = startMap; + this.endMap = endMap; + this.differenceFunction = differenceFunction; + } + + @Override + public Supplier> supplier() { + return () -> new ConcurrentUsageCalculator<>(startMap, endMap, differenceFunction); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ConcurrentUsageQuadConstraintCollector that)) + return false; + if (!super.equals(o)) + return false; + return Objects.equals(startMap, that.startMap) && Objects.equals(endMap, + that.endMap) && Objects.equals(differenceFunction, that.differenceFunction); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), startMap, endMap, differenceFunction); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectors.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectors.java index 61e40dd72c..511a1fcac8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectors.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectors.java @@ -21,6 +21,7 @@ import ai.timefold.solver.core.api.function.ToIntQuadFunction; import ai.timefold.solver.core.api.function.ToLongQuadFunction; import ai.timefold.solver.core.api.function.TriFunction; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; import ai.timefold.solver.core.api.score.stream.quad.QuadConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.ReferenceAverageCalculator; @@ -216,6 +217,16 @@ public class InnerQuadConstraintCollectors { return new ConsecutiveSequencesQuadConstraintCollector<>(resultMap, indexMap); } + public static , Difference_ extends Comparable> + QuadConstraintCollector> + consecutiveUsages(QuadFunction mapper, + Function startMap, + Function endMap, + BiFunction differenceFunction) { + return new ConcurrentUsageQuadConstraintCollector<>(mapper, startMap, endMap, + differenceFunction); + } + public static QuadConstraintCollector collectAndThen(QuadConstraintCollector delegate, Function mappingFunction) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ObjectCalculatorQuadCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ObjectCalculatorQuadCollector.java index 8ad442c8e1..99da978303 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ObjectCalculatorQuadCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ObjectCalculatorQuadCollector.java @@ -10,8 +10,9 @@ abstract sealed class ObjectCalculatorQuadCollector> implements QuadConstraintCollector - permits AverageReferenceQuadCollector, ConsecutiveSequencesQuadConstraintCollector, CountDistinctIntQuadCollector, - CountDistinctLongQuadCollector, SumReferenceQuadCollector { + permits AverageReferenceQuadCollector, ConcurrentUsageQuadConstraintCollector, + ConsecutiveSequencesQuadConstraintCollector, CountDistinctIntQuadCollector, CountDistinctLongQuadCollector, + SumReferenceQuadCollector { protected final QuadFunction mapper; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConcurrentUsageTriConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConcurrentUsageTriConstraintCollector.java new file mode 100644 index 0000000000..342c2ef52e --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConcurrentUsageTriConstraintCollector.java @@ -0,0 +1,50 @@ +package ai.timefold.solver.core.impl.score.stream.collector.tri; + +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +import ai.timefold.solver.core.api.function.TriFunction; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.impl.score.stream.collector.ConcurrentUsageCalculator; + +final class ConcurrentUsageTriConstraintCollector, Difference_ extends Comparable> + extends + ObjectCalculatorTriCollector, ConcurrentUsageCalculator> { + + private final Function startMap; + private final Function endMap; + private final BiFunction differenceFunction; + + public ConcurrentUsageTriConstraintCollector(TriFunction mapper, + Function startMap, Function endMap, + BiFunction differenceFunction) { + super(mapper); + this.startMap = startMap; + this.endMap = endMap; + this.differenceFunction = differenceFunction; + } + + @Override + public Supplier> supplier() { + return () -> new ConcurrentUsageCalculator<>(startMap, endMap, differenceFunction); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ConcurrentUsageTriConstraintCollector that)) + return false; + if (!super.equals(o)) + return false; + return Objects.equals(startMap, that.startMap) && Objects.equals(endMap, + that.endMap) && Objects.equals(differenceFunction, that.differenceFunction); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), startMap, endMap, differenceFunction); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectors.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectors.java index fbde9258ce..5796bed016 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectors.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectors.java @@ -21,6 +21,7 @@ import ai.timefold.solver.core.api.function.ToLongTriFunction; import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.function.TriPredicate; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; import ai.timefold.solver.core.api.score.stream.tri.TriConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.ReferenceAverageCalculator; @@ -215,6 +216,16 @@ public class InnerTriConstraintCollectors { return new ConsecutiveSequencesTriConstraintCollector<>(resultMap, indexMap); } + public static , Difference_ extends Comparable> + TriConstraintCollector> + consecutiveUsages(TriFunction mapper, + Function startMap, + Function endMap, + BiFunction differenceFunction) { + return new ConcurrentUsageTriConstraintCollector<>(mapper, startMap, endMap, + differenceFunction); + } + public static TriConstraintCollector collectAndThen(TriConstraintCollector delegate, Function mappingFunction) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ObjectCalculatorTriCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ObjectCalculatorTriCollector.java index 395b8542ee..c11ec17dcd 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ObjectCalculatorTriCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ObjectCalculatorTriCollector.java @@ -10,8 +10,8 @@ abstract sealed class ObjectCalculatorTriCollector> implements TriConstraintCollector - permits AverageReferenceTriCollector, ConsecutiveSequencesTriConstraintCollector, CountDistinctIntTriCollector, - CountDistinctLongTriCollector, SumReferenceTriCollector { + permits AverageReferenceTriCollector, ConcurrentUsageTriConstraintCollector, ConsecutiveSequencesTriConstraintCollector, + CountDistinctIntTriCollector, CountDistinctLongTriCollector, SumReferenceTriCollector { protected final TriFunction mapper; public ObjectCalculatorTriCollector(TriFunction mapper) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConcurrentUsageUniConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConcurrentUsageUniConstraintCollector.java new file mode 100644 index 0000000000..201554a9e5 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConcurrentUsageUniConstraintCollector.java @@ -0,0 +1,49 @@ +package ai.timefold.solver.core.impl.score.stream.collector.uni; + +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.impl.score.stream.collector.ConcurrentUsageCalculator; + +final class ConcurrentUsageUniConstraintCollector, Difference_ extends Comparable> + extends + ObjectCalculatorUniCollector, ConcurrentUsageCalculator> { + + private final Function startMap; + private final Function endMap; + private final BiFunction differenceFunction; + + public ConcurrentUsageUniConstraintCollector(Function mapper, + Function startMap, Function endMap, + BiFunction differenceFunction) { + super(mapper); + this.startMap = startMap; + this.endMap = endMap; + this.differenceFunction = differenceFunction; + } + + @Override + public Supplier> supplier() { + return () -> new ConcurrentUsageCalculator<>(startMap, endMap, differenceFunction); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ConcurrentUsageUniConstraintCollector that)) + return false; + if (!super.equals(o)) + return false; + return Objects.equals(startMap, that.startMap) && Objects.equals(endMap, + that.endMap) && Objects.equals(differenceFunction, that.differenceFunction); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), startMap, endMap, differenceFunction); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectors.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectors.java index 84b9fac8f5..8c00c811c0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectors.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectors.java @@ -20,6 +20,7 @@ import ai.timefold.solver.core.api.function.QuadFunction; import ai.timefold.solver.core.api.function.TriFunction; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; import ai.timefold.solver.core.api.score.stream.uni.UniConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.ReferenceAverageCalculator; @@ -195,6 +196,16 @@ public static UniConstraintCollector(indexMap); } + public static , Difference_ extends Comparable> + UniConstraintCollector> + consecutiveUsages(Function mapper, + Function startMap, + Function endMap, + BiFunction differenceFunction) { + return new ConcurrentUsageUniConstraintCollector<>(mapper, startMap, endMap, + differenceFunction); + } + public static UniConstraintCollector collectAndThen(UniConstraintCollector delegate, Function mappingFunction) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ObjectCalculatorUniCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ObjectCalculatorUniCollector.java index 7fc33b5d24..3f444257be 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ObjectCalculatorUniCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ObjectCalculatorUniCollector.java @@ -9,8 +9,8 @@ abstract sealed class ObjectCalculatorUniCollector> implements UniConstraintCollector - permits AverageReferenceUniCollector, ConsecutiveSequencesUniConstraintCollector, CountDistinctIntUniCollector, - CountDistinctLongUniCollector, SumReferenceUniCollector { + permits AverageReferenceUniCollector, ConcurrentUsageUniConstraintCollector, ConsecutiveSequencesUniConstraintCollector, + CountDistinctIntUniCollector, CountDistinctLongUniCollector, SumReferenceUniCollector { protected final Function mapper; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java index 5491445097..8f25290083 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java @@ -2,7 +2,10 @@ import java.util.Arrays; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; +import ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage.IntervalTree; +import ai.timefold.solver.core.impl.score.stream.collector.consecutive.ConsecutiveSetTree; import org.junit.jupiter.api.Test; @@ -104,6 +107,9 @@ public abstract class AbstractConstraintCollectorsTest { @Test public abstract void toConsecutiveSequences(); + @Test + public abstract void consecutiveUsage(); + @Test public abstract void collectAndThen(); @@ -116,4 +122,17 @@ protected static SequenceChain buildSequenceChain(Integer... d }); } + protected ConcurrentUsageInfo buildConsecutiveUsage(Interval... data) { + return Arrays.stream(data).collect( + () -> new IntervalTree<>(Interval::start, Interval::end, (a, b) -> b - a), + (tree, datum) -> tree.add(tree.getInterval(datum)), + (a, b) -> { + throw new UnsupportedOperationException(); + }).getConsecutiveIntervalData(); + } + + public record Interval(int start, int end) { + + } + } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectorsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectorsTest.java index 1497b838ec..b9571fb236 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectorsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectorsTest.java @@ -32,6 +32,7 @@ import ai.timefold.solver.core.api.score.stream.ConstraintCollectors; import ai.timefold.solver.core.api.score.stream.bi.BiConstraintCollector; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; import ai.timefold.solver.core.impl.score.stream.collector.AbstractConstraintCollectorsTest; import ai.timefold.solver.core.impl.util.Pair; import ai.timefold.solver.core.impl.util.Quadruple; @@ -1066,6 +1067,34 @@ public void toConsecutiveSequences() { assertResultRecursive(collector, container, buildSequenceChain()); } + @Override + @Test + public void consecutiveUsage() { + BiConstraintCollector> collector = + ConstraintCollectors.concurrentUsage(Interval::new, + Interval::start, + Interval::end, (a, b) -> b - a); + var container = collector.supplier().get(); + // Add first value, sequence is [(1,3)] + Runnable firstRetractor = accumulate(collector, container, 1, 3); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3))); + // Add second value, sequence is [(1,3),(2,4)] + Runnable secondRetractor = accumulate(collector, container, 2, 4); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3), new Interval(2, 4))); + // Add third value, same as the second. Sequence is [{1,1},2}] + Runnable thirdRetractor = accumulate(collector, container, 2, 4); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3), new Interval(2, 4), new Interval(2, 4))); + // Retract one instance of the second value; we only have two values now. + secondRetractor.run(); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3), new Interval(2, 4))); + // Retract final instance of the second value; we only have one value now. + thirdRetractor.run(); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3))); + // Retract last value; there are no values now. + firstRetractor.run(); + assertResult(collector, container, buildConsecutiveUsage()); + } + @Override @Test public void collectAndThen() { diff --git a/examples/src/test/java/ai/timefold/solver/examples/common/experimental/impl/IntervalTreeTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTreeTest.java similarity index 76% rename from examples/src/test/java/ai/timefold/solver/examples/common/experimental/impl/IntervalTreeTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTreeTest.java index 49378dc7d4..d3afddae60 100644 --- a/examples/src/test/java/ai/timefold/solver/examples/common/experimental/impl/IntervalTreeTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTreeTest.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.examples.common.experimental.impl; +package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; import static org.assertj.core.api.Assertions.assertThat; @@ -11,8 +11,8 @@ import java.util.TreeSet; import java.util.stream.Collectors; -import ai.timefold.solver.examples.common.experimental.api.IntervalBreak; -import ai.timefold.solver.examples.common.experimental.api.IntervalCluster; +import ai.timefold.solver.core.api.score.stream.common.Break; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsage; import org.junit.jupiter.api.Test; @@ -77,18 +77,24 @@ void testNonConsecutiveIntervals() { tree.add(b); tree.add(c); - IterableList> clusterList = - new IterableList<>(tree.getConsecutiveIntervalData().getIntervalClusters()); + var clusterList = + new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); assertThat(clusterList).hasSize(3); assertThat(clusterList.get(0)).containsExactly(new TestInterval(0, 2)); assertThat(clusterList.get(0).hasOverlap()).isFalse(); + assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(1); assertThat(clusterList.get(1)).containsExactly(new TestInterval(3, 4)); assertThat(clusterList.get(1).hasOverlap()).isFalse(); + assertThat(clusterList.get(1).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(1).getMaximumConcurrentUsage()).isEqualTo(1); assertThat(clusterList.get(2)).containsExactly(new TestInterval(5, 7)); assertThat(clusterList.get(2).hasOverlap()).isFalse(); + assertThat(clusterList.get(2).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(2).getMaximumConcurrentUsage()).isEqualTo(1); verifyBreaks(tree); } @@ -103,11 +109,13 @@ void testConsecutiveIntervals() { tree.add(b); tree.add(c); - IterableList> clusterList = - new IterableList<>(tree.getConsecutiveIntervalData().getIntervalClusters()); + var clusterList = + new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); assertThat(clusterList).hasSize(1); assertThat(clusterList.get(0)).containsExactly(new TestInterval(0, 2), new TestInterval(2, 4), new TestInterval(4, 7)); + assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(1); verifyBreaks(tree); } @@ -120,12 +128,16 @@ void testDuplicateIntervals() { tree.add(a); tree.add(b); - IterableList> clusterList = - new IterableList<>(tree.getConsecutiveIntervalData().getIntervalClusters()); + var clusterList = + new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); assertThat(clusterList).hasSize(2); assertThat(clusterList.get(0)).containsExactly(a.getValue(), a.getValue()); + assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(2); + assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(2); assertThat(clusterList.get(1)).containsExactly(b.getValue()); + assertThat(clusterList.get(1).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(1).getMaximumConcurrentUsage()).isEqualTo(1); verifyBreaks(tree); } @@ -146,8 +158,8 @@ void testIntervalRemoval() { tree.remove(b); - IterableList> clusterList = - new IterableList<>(tree.getConsecutiveIntervalData().getIntervalClusters()); + var clusterList = + new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); assertThat(clusterList).hasSize(2); assertThat(clusterList.get(0)).containsExactly(new TestInterval(0, 2)); @@ -201,18 +213,24 @@ void testOverlappingInterval() { tree.add(e); tree.add(removedInterval2); - IterableList> clusterList = - new IterableList<>(tree.getConsecutiveIntervalData().getIntervalClusters()); + var clusterList = + new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); assertThat(clusterList).hasSize(3); assertThat(clusterList.get(0)).containsExactly(a.getValue(), removedTestInterval1, c.getValue()); assertThat(clusterList.get(0).hasOverlap()).isTrue(); + assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(2); assertThat(clusterList.get(1)).containsExactly(d.getValue()); assertThat(clusterList.get(1).hasOverlap()).isFalse(); + assertThat(clusterList.get(1).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(1).getMaximumConcurrentUsage()).isEqualTo(1); assertThat(clusterList.get(2)).containsExactly(e.getValue(), removedTestInterval2); assertThat(clusterList.get(2).hasOverlap()).isTrue(); + assertThat(clusterList.get(2).getMinimumConcurrentUsage()).isEqualTo(2); + assertThat(clusterList.get(2).getMaximumConcurrentUsage()).isEqualTo(2); verifyBreaks(tree); @@ -222,17 +240,23 @@ void testOverlappingInterval() { tree.remove(removedInterval1); - clusterList = new IterableList<>(tree.getConsecutiveIntervalData().getIntervalClusters()); + clusterList = new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); assertThat(clusterList).hasSize(3); assertThat(clusterList.get(0)).containsExactly(a.getValue(), c.getValue()); assertThat(clusterList.get(0).hasOverlap()).isFalse(); + assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(1); assertThat(clusterList.get(1)).containsExactly(d.getValue()); assertThat(clusterList.get(1).hasOverlap()).isFalse(); + assertThat(clusterList.get(1).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(1).getMaximumConcurrentUsage()).isEqualTo(1); assertThat(clusterList.get(2)).containsExactly(e.getValue(), removedTestInterval2); assertThat(clusterList.get(2).hasOverlap()).isTrue(); + assertThat(clusterList.get(2).getMinimumConcurrentUsage()).isEqualTo(2); + assertThat(clusterList.get(2).getMaximumConcurrentUsage()).isEqualTo(2); verifyBreaks(tree); @@ -241,35 +265,45 @@ void testOverlappingInterval() { removedTestInterval2.setEnd(4); tree.remove(removedInterval2); - clusterList = new IterableList<>(tree.getConsecutiveIntervalData().getIntervalClusters()); + clusterList = new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); assertThat(clusterList).hasSize(3); assertThat(clusterList.get(0)).containsExactly(a.getValue(), c.getValue()); assertThat(clusterList.get(0).hasOverlap()).isFalse(); + assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(1); assertThat(clusterList.get(1)).containsExactly(d.getValue()); assertThat(clusterList.get(1).hasOverlap()).isFalse(); + assertThat(clusterList.get(1).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(1).getMaximumConcurrentUsage()).isEqualTo(1); assertThat(clusterList.get(2)).containsExactly(e.getValue()); assertThat(clusterList.get(2).hasOverlap()).isFalse(); + assertThat(clusterList.get(2).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(2).getMaximumConcurrentUsage()).isEqualTo(1); verifyBreaks(tree); Interval g = tree.getInterval(new TestInterval(6, 7)); tree.add(g); - clusterList = new IterableList<>(tree.getConsecutiveIntervalData().getIntervalClusters()); + clusterList = new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); assertThat(clusterList).hasSize(2); assertThat(clusterList.get(0)).containsExactly(a.getValue(), c.getValue()); assertThat(clusterList.get(0).hasOverlap()).isFalse(); + assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(1); assertThat(clusterList.get(1)).containsExactly(d.getValue(), g.getValue(), e.getValue()); assertThat(clusterList.get(1).hasOverlap()).isFalse(); + assertThat(clusterList.get(1).getMinimumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(1).getMaximumConcurrentUsage()).isEqualTo(1); } - public void verifyBreaks(IntervalTree tree) { - IterableList> clusterList = - new IterableList<>(tree.getConsecutiveIntervalData().getIntervalClusters()); - IterableList> breakList = + void verifyBreaks(IntervalTree tree) { + var clusterList = + new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); + var breakList = new IterableList<>(tree.getConsecutiveIntervalData().getBreaks()); if (clusterList.size() == 0) { @@ -277,24 +311,22 @@ public void verifyBreaks(IntervalTree tree) { } assertThat(breakList).hasSize(clusterList.size() - 1); for (int i = 0; i < clusterList.size() - 1; i++) { - assertThat(breakList.get(i).getPreviousIntervalCluster()).isSameAs(clusterList.get(i)); - assertThat(breakList.get(i).getNextIntervalCluster()).isSameAs(clusterList.get(i + 1)); - assertThat(breakList.get(i).getPreviousIntervalClusterEnd()).isEqualTo(clusterList.get(i).getEnd()); - assertThat(breakList.get(i).getNextIntervalClusterStart()).isEqualTo(clusterList.get(i + 1).getStart()); + assertThat(breakList.get(i).getPreviousSequenceEnd()).isEqualTo(clusterList.get(i).getEnd()); + assertThat(breakList.get(i).getNextSequenceStart()).isEqualTo(clusterList.get(i + 1).getStart()); assertThat(breakList.get(i).getLength()).isEqualTo(clusterList.get(i + 1).getStart() - clusterList.get(i).getEnd()); } } - private static int intervalBreakCompare(IntervalBreak a, - IntervalBreak b) { + private static int intervalBreakCompare(Break a, + Break b) { if (a == b) { return 0; } if (a == null || b == null) { return (a == null) ? -1 : 1; } - boolean out = intervalClusterCompare(a.getPreviousIntervalCluster(), b.getPreviousIntervalCluster()) == 0 && - intervalClusterCompare(a.getNextIntervalCluster(), b.getNextIntervalCluster()) == 0 && + boolean out = Objects.equals(a.getPreviousSequenceEnd(), b.getPreviousSequenceEnd()) && + Objects.equals(a.getNextSequenceStart(), b.getNextSequenceStart()) && Objects.equals(a.getLength(), b.getLength()); if (out) { @@ -303,8 +335,8 @@ private static int intervalBreakCompare(IntervalBreak a, - IntervalCluster b) { + private static int intervalClusterCompare(ConcurrentUsage a, + ConcurrentUsage b) { if (a == b) { return 0; } @@ -312,15 +344,17 @@ private static int intervalClusterCompare(IntervalCluster first = (IntervalClusterImpl) a; - IntervalClusterImpl second = (IntervalClusterImpl) b; + var first = (ConcurrentUsageImpl) a; + var second = (ConcurrentUsageImpl) b; boolean out = first.getStartSplitPoint().compareTo(second.getStartSplitPoint()) == 0 && - first.getEndSplitPoint().compareTo(second.getEndSplitPoint()) == 0; + first.getEndSplitPoint().compareTo(second.getEndSplitPoint()) == 0 && + first.getMinimumConcurrentUsage() == second.getMinimumConcurrentUsage() && + first.getMaximumConcurrentUsage() == second.getMaximumConcurrentUsage(); if (out) { return 0; } @@ -385,14 +419,14 @@ void testRandomIntervals() { // Recompute all interval clusters IntervalSplitPoint previous = null; IntervalSplitPoint current = splitPoints.isEmpty() ? null : splitPoints.first(); - List> intervalClusterList = new ArrayList<>(); + List> intervalClusterList = new ArrayList<>(); List> breakList = new ArrayList<>(); while (current != null) { - intervalClusterList.add(new IntervalClusterImpl<>(splitPoints, (a, b) -> a - b, current)); + intervalClusterList.add(new ConcurrentUsageImpl<>(splitPoints, (a, b) -> a - b, current)); if (previous != null) { - IntervalClusterImpl before = + ConcurrentUsageImpl before = intervalClusterList.get(intervalClusterList.size() - 2); - IntervalClusterImpl after = + ConcurrentUsageImpl after = intervalClusterList.get(intervalClusterList.size() - 1); breakList.add(new IntervalBreakImpl<>(before, after, after.getStart() - before.getEnd())); } @@ -402,7 +436,7 @@ void testRandomIntervals() { // Verify the mutable version matches the recompute version verifyBreaks(tree); - assertThat(tree.getConsecutiveIntervalData().getIntervalClusters()) + assertThat(tree.getConsecutiveIntervalData().getConcurrentUsages()) .as(op + " interval " + interval + " to " + old) .usingElementComparator(IntervalTreeTest::intervalClusterCompare) .containsExactlyElementsOf(intervalClusterList); @@ -416,8 +450,8 @@ void testRandomIntervals() { private String formatIntervalTree(IntervalTree intervalTree) { List> listOfIntervalClusters = new ArrayList<>(); - for (IntervalCluster cluster : intervalTree.getConsecutiveIntervalData() - .getIntervalClusters()) { + for (ConcurrentUsage cluster : intervalTree.getConsecutiveIntervalData() + .getConcurrentUsages()) { List intervalsInCluster = new ArrayList<>(); for (TestInterval interval : cluster) { intervalsInCluster.add(interval); diff --git a/examples/src/test/java/ai/timefold/solver/examples/common/experimental/impl/IterableList.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IterableList.java similarity index 94% rename from examples/src/test/java/ai/timefold/solver/examples/common/experimental/impl/IterableList.java rename to core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IterableList.java index 2518dfe1f7..4e7ff1ddba 100644 --- a/examples/src/test/java/ai/timefold/solver/examples/common/experimental/impl/IterableList.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IterableList.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.examples.common.experimental.impl; +package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; import java.util.Iterator; import java.util.Objects; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/ConsecutiveSetTreeTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTreeTest.java similarity index 99% rename from core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/ConsecutiveSetTreeTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTreeTest.java index 24b001c46b..50a5731542 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/ConsecutiveSetTreeTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTreeTest.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector; +package ai.timefold.solver.core.impl.score.stream.collector.consecutive; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/IterableList.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/IterableList.java similarity index 93% rename from core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/IterableList.java rename to core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/IterableList.java index 1ea811dfbb..0bb3d08647 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/IterableList.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/IterableList.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector; +package ai.timefold.solver.core.impl.score.stream.collector.consecutive; import java.util.Iterator; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectorsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectorsTest.java index fb6f7e7ad4..aa1d21b3a3 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectorsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectorsTest.java @@ -30,6 +30,7 @@ import java.util.SortedSet; import ai.timefold.solver.core.api.score.stream.ConstraintCollectors; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; import ai.timefold.solver.core.api.score.stream.quad.QuadConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.AbstractConstraintCollectorsTest; import ai.timefold.solver.core.impl.util.Pair; @@ -1118,6 +1119,34 @@ public void toConsecutiveSequences() { assertResultRecursive(collector, container, buildSequenceChain()); } + @Override + @Test + public void consecutiveUsage() { + QuadConstraintCollector> collector = + ConstraintCollectors.concurrentUsage((a, b, c, d) -> new Interval(a, b), + Interval::start, + Interval::end, (a, b) -> b - a); + var container = collector.supplier().get(); + // Add first value, sequence is [(1,3)] + Runnable firstRetractor = accumulate(collector, container, 1, 3, null, null); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3))); + // Add second value, sequence is [(1,3),(2,4)] + Runnable secondRetractor = accumulate(collector, container, 2, 4, null, null); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3), new Interval(2, 4))); + // Add third value, same as the second. Sequence is [{1,1},2}] + Runnable thirdRetractor = accumulate(collector, container, 2, 4, null, null); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3), new Interval(2, 4), new Interval(2, 4))); + // Retract one instance of the second value; we only have two values now. + secondRetractor.run(); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3), new Interval(2, 4))); + // Retract final instance of the second value; we only have one value now. + thirdRetractor.run(); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3))); + // Retract last value; there are no values now. + firstRetractor.run(); + assertResult(collector, container, buildConsecutiveUsage()); + } + @Override @Test public void collectAndThen() { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectorsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectorsTest.java index da5be789d8..d736c63e5f 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectorsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectorsTest.java @@ -30,6 +30,7 @@ import java.util.SortedSet; import ai.timefold.solver.core.api.score.stream.ConstraintCollectors; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; import ai.timefold.solver.core.api.score.stream.tri.TriConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.AbstractConstraintCollectorsTest; import ai.timefold.solver.core.impl.util.Pair; @@ -1071,6 +1072,34 @@ public void toConsecutiveSequences() { assertResultRecursive(collector, container, buildSequenceChain()); } + @Override + @Test + public void consecutiveUsage() { + TriConstraintCollector> collector = + ConstraintCollectors.concurrentUsage((a, b, c) -> new Interval(a, b), + Interval::start, + Interval::end, (a, b) -> b - a); + var container = collector.supplier().get(); + // Add first value, sequence is [(1,3)] + Runnable firstRetractor = accumulate(collector, container, 1, 3, null); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3))); + // Add second value, sequence is [(1,3),(2,4)] + Runnable secondRetractor = accumulate(collector, container, 2, 4, null); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3), new Interval(2, 4))); + // Add third value, same as the second. Sequence is [{1,1},2}] + Runnable thirdRetractor = accumulate(collector, container, 2, 4, null); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3), new Interval(2, 4), new Interval(2, 4))); + // Retract one instance of the second value; we only have two values now. + secondRetractor.run(); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3), new Interval(2, 4))); + // Retract final instance of the second value; we only have one value now. + thirdRetractor.run(); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3))); + // Retract last value; there are no values now. + firstRetractor.run(); + assertResult(collector, container, buildConsecutiveUsage()); + } + @Override @Test public void collectAndThen() { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectorsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectorsTest.java index c7bdec4362..8773a5478d 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectorsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectorsTest.java @@ -32,6 +32,7 @@ import ai.timefold.solver.core.api.function.QuadFunction; import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.score.stream.ConstraintCollectors; +import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; import ai.timefold.solver.core.api.score.stream.uni.UniConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.AbstractConstraintCollectorsTest; import ai.timefold.solver.core.impl.util.Pair; @@ -986,6 +987,34 @@ public void toConsecutiveSequences() { assertResultRecursive(collector, container, buildSequenceChain()); } + @Override + @Test + public void consecutiveUsage() { + UniConstraintCollector> collector = + ConstraintCollectors.concurrentUsage( + Interval::start, + Interval::end, (a, b) -> b - a); + var container = collector.supplier().get(); + // Add first value, sequence is [(1,3)] + Runnable firstRetractor = accumulate(collector, container, new Interval(1, 3)); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3))); + // Add second value, sequence is [(1,3),(2,4)] + Runnable secondRetractor = accumulate(collector, container, new Interval(2, 4)); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3), new Interval(2, 4))); + // Add third value, same as the second. Sequence is [{1,1},2}] + Runnable thirdRetractor = accumulate(collector, container, new Interval(2, 4)); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3), new Interval(2, 4), new Interval(2, 4))); + // Retract one instance of the second value; we only have two values now. + secondRetractor.run(); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3), new Interval(2, 4))); + // Retract final instance of the second value; we only have one value now. + thirdRetractor.run(); + assertResult(collector, container, buildConsecutiveUsage(new Interval(1, 3))); + // Retract last value; there are no values now. + firstRetractor.run(); + assertResult(collector, container, buildConsecutiveUsage()); + } + @Override @Test public void collectAndThen() { diff --git a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/ExperimentalConstraintCollectors.java b/examples/src/main/java/ai/timefold/solver/examples/common/experimental/ExperimentalConstraintCollectors.java deleted file mode 100644 index 1188d6e02a..0000000000 --- a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/ExperimentalConstraintCollectors.java +++ /dev/null @@ -1,368 +0,0 @@ -package ai.timefold.solver.examples.common.experimental; - -import java.time.Duration; -import java.time.temporal.Temporal; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.function.ToLongFunction; - -import ai.timefold.solver.core.api.function.PentaFunction; -import ai.timefold.solver.core.api.function.QuadFunction; -import ai.timefold.solver.core.api.function.TriFunction; -import ai.timefold.solver.core.api.score.stream.bi.BiConstraintCollector; -import ai.timefold.solver.core.api.score.stream.quad.QuadConstraintCollector; -import ai.timefold.solver.core.api.score.stream.tri.TriConstraintCollector; -import ai.timefold.solver.core.api.score.stream.uni.UniConstraintCollector; -import ai.timefold.solver.examples.common.experimental.api.ConsecutiveIntervalInfo; -import ai.timefold.solver.examples.common.experimental.impl.Interval; -import ai.timefold.solver.examples.common.experimental.impl.IntervalTree; - -/** - * A collection of experimental constraint collectors subject to change in future versions. - */ -public class ExperimentalConstraintCollectors { - - /** - * Creates a constraint collector that returns {@link ConsecutiveIntervalInfo} about the first fact. - * - * For instance, {@code [Shift from=2, to=4] [Shift from=3, to=5] [Shift from=6, to=7] [Shift from=7, to=8]} - * returns the following information: - * - *
-     * {@code
-     * IntervalClusters: [[Shift from=2, to=4] [Shift from=3, to=5]], [[Shift from=6, to=7] [Shift from=7, to=8]]
-     * Breaks: [[Break from=5, to=6, length=1]]
-     * }
-     * 
- * - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end - * @param differenceFunction Computes the difference between two points. The second argument is always - * larger than the first (ex: {@link Duration#between} - * or {@code (a,b) -> b - a}). - * @param
type of the first mapped fact - * @param type of the fact endpoints - * @param type of difference between points - * @return never null - */ - public static , DifferenceType_ extends Comparable> - UniConstraintCollector, ConsecutiveIntervalInfo> - consecutiveIntervals(Function startMap, Function endMap, - BiFunction differenceFunction) { - return new UniConstraintCollector<>() { - @Override - public Supplier> supplier() { - return () -> new IntervalTree<>( - startMap, - endMap, differenceFunction); - } - - @Override - public BiFunction, A, Runnable> accumulator() { - return (acc, a) -> { - Interval interval = acc.getInterval(a); - acc.add(interval); - return () -> acc.remove(interval); - }; - } - - @Override - public Function, ConsecutiveIntervalInfo> - finisher() { - return IntervalTree::getConsecutiveIntervalData; - } - }; - } - - /** - * Specialized version of {@link #consecutiveIntervals(Function,Function,BiFunction)} for - * {@link Temporal} types. - * - * @param type of the first mapped fact - * @param temporal type of the endpoints - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end - * @return never null - */ - public static > - UniConstraintCollector, ConsecutiveIntervalInfo> - consecutiveTemporalIntervals(Function startMap, Function endMap) { - return consecutiveIntervals(startMap, endMap, Duration::between); - } - - /** - * Specialized version of {@link #consecutiveIntervals(Function,Function,BiFunction)} for Long. - * - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end - * @param type of the first mapped fact - * @return never null - */ - public static UniConstraintCollector, ConsecutiveIntervalInfo> - consecutiveIntervals(ToLongFunction startMap, ToLongFunction endMap) { - return consecutiveIntervals(startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); - } - - /** - * As defined by {@link #consecutiveIntervals(Function,Function,BiFunction)}. - * - * @param intervalMap Maps both facts to an item in the cluster - * @param startMap Maps the item to its start - * @param endMap Maps the item to its end - * @param differenceFunction Computes the difference between two points. The second argument is always - * larger than the first (ex: {@link Duration#between} - * or {@code (a,b) -> b - a}). - * @param type of the first mapped fact - * @param type of the second mapped fact - * @param type of the item in the cluster - * @param type of the item endpoints - * @param type of difference between points - * @return never null - */ - public static , DifferenceType_ extends Comparable> - BiConstraintCollector, ConsecutiveIntervalInfo> - consecutiveIntervals(BiFunction intervalMap, Function startMap, - Function endMap, - BiFunction differenceFunction) { - return new BiConstraintCollector<>() { - @Override - public Supplier> supplier() { - return () -> new IntervalTree<>( - startMap, - endMap, differenceFunction); - } - - @Override - public TriFunction, A, B, Runnable> accumulator() { - return (acc, a, b) -> { - IntervalType_ intervalObj = intervalMap.apply(a, b); - Interval interval = acc.getInterval(intervalObj); - acc.add(interval); - return () -> acc.remove(interval); - }; - } - - @Override - public Function, ConsecutiveIntervalInfo> - finisher() { - return IntervalTree::getConsecutiveIntervalData; - } - }; - } - - /** - * As defined by {@link #consecutiveTemporalIntervals(Function,Function)}. - * - * @param intervalMap Maps the three facts to an item in the cluster - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end - * @param type of the first mapped fact - * @param type of the second mapped fact - * @param type of the item in the cluster - * @param temporal type of the endpoints - * @return never null - */ - public static > - BiConstraintCollector, ConsecutiveIntervalInfo> - consecutiveTemporalIntervals(BiFunction intervalMap, - Function startMap, Function endMap) { - return consecutiveIntervals(intervalMap, startMap, endMap, Duration::between); - } - - /** - * As defined by {@link #consecutiveIntervals(ToLongFunction, ToLongFunction)}. - * - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end - * @param type of the first mapped fact - * @param type of the second mapped fact - * @param type of the item in the cluster - * @return never null - */ - public static - BiConstraintCollector, ConsecutiveIntervalInfo> - consecutiveIntervals(BiFunction intervalMap, ToLongFunction startMap, - ToLongFunction endMap) { - return consecutiveIntervals(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); - } - - /** - * As defined by {@link #consecutiveIntervals(Function,Function,BiFunction)}. - * - * @param intervalMap Maps the three facts to an item in the cluster - * @param startMap Maps the item to its start - * @param endMap Maps the item to its end - * @param differenceFunction Computes the difference between two points. The second argument is always - * larger than the first (ex: {@link Duration#between} - * or {@code (a,b) -> b - a}). - * @param type of the first mapped fact - * @param type of the second mapped fact - * @param type of the third mapped fact - * @param type of the item in the cluster - * @param type of the item endpoints - * @param type of difference between points - * @return never null - */ - public static , DifferenceType_ extends Comparable> - TriConstraintCollector, ConsecutiveIntervalInfo> - consecutiveIntervals(TriFunction intervalMap, Function startMap, - Function endMap, - BiFunction differenceFunction) { - return new TriConstraintCollector<>() { - @Override - public Supplier> supplier() { - return () -> new IntervalTree<>( - startMap, - endMap, differenceFunction); - } - - @Override - public QuadFunction, A, B, C, Runnable> accumulator() { - return (acc, a, b, c) -> { - IntervalType_ intervalObj = intervalMap.apply(a, b, c); - Interval interval = acc.getInterval(intervalObj); - acc.add(interval); - return () -> acc.remove(interval); - }; - } - - @Override - public Function, ConsecutiveIntervalInfo> - finisher() { - return IntervalTree::getConsecutiveIntervalData; - } - }; - } - - /** - * As defined by {@link #consecutiveTemporalIntervals(Function,Function)}. - * - * @param intervalMap Maps the three facts to an item in the cluster - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end - * @param type of the first mapped fact - * @param type of the second mapped fact - * @param type of the third mapped fact - * @param type of the item in the cluster - * @param temporal type of the endpoints - * @return never null - */ - public static > - TriConstraintCollector, ConsecutiveIntervalInfo> - consecutiveTemporalIntervals(TriFunction intervalMap, - Function startMap, Function endMap) { - return consecutiveIntervals(intervalMap, startMap, endMap, Duration::between); - } - - /** - * As defined by {@link #consecutiveIntervals(ToLongFunction, ToLongFunction)}. - * - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end - * @param type of the first mapped fact - * @param type of the second mapped fact - * @param type of the third mapped fact - * @param type of the item in the cluster - * @return never null - */ - public static - TriConstraintCollector, ConsecutiveIntervalInfo> - consecutiveIntervals(TriFunction intervalMap, ToLongFunction startMap, - ToLongFunction endMap) { - return consecutiveIntervals(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); - } - - /** - * As defined by {@link #consecutiveIntervals(Function,Function,BiFunction)}. - * - * @param intervalMap Maps the four facts to an item in the cluster - * @param startMap Maps the item to its start - * @param endMap Maps the item to its end - * @param differenceFunction Computes the difference between two points. The second argument is always - * larger than the first (ex: {@link Duration#between} - * or {@code (a,b) -> b - a}). - * @param type of the first mapped fact - * @param type of the second mapped fact - * @param type of the third mapped fact - * @param type of the fourth mapped fact - * @param type of the item in the cluster - * @param type of the item endpoints - * @param type of difference between points - * @return never null - */ - public static , DifferenceType_ extends Comparable> - QuadConstraintCollector, ConsecutiveIntervalInfo> - consecutiveIntervals(QuadFunction intervalMap, - Function startMap, Function endMap, - BiFunction differenceFunction) { - return new QuadConstraintCollector<>() { - @Override - public Supplier> supplier() { - return () -> new IntervalTree<>( - startMap, - endMap, differenceFunction); - } - - @Override - public PentaFunction, A, B, C, D, Runnable> accumulator() { - return (acc, a, b, c, d) -> { - IntervalType_ intervalObj = intervalMap.apply(a, b, c, d); - Interval interval = acc.getInterval(intervalObj); - acc.add(interval); - return () -> acc.remove(interval); - }; - } - - @Override - public Function, ConsecutiveIntervalInfo> - finisher() { - return IntervalTree::getConsecutiveIntervalData; - } - }; - } - - /** - * As defined by {@link #consecutiveTemporalIntervals(Function,Function)}. - * - * @param intervalMap Maps the three facts to an item in the cluster - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end - * @param type of the first mapped fact - * @param type of the second mapped fact - * @param type of the third mapped fact - * @param type of the fourth mapped fact - * @param type of the item in the cluster - * @param temporal type of the endpoints - * @return never null - */ - public static > - QuadConstraintCollector, ConsecutiveIntervalInfo> - consecutiveTemporalIntervals(QuadFunction intervalMap, - Function startMap, Function endMap) { - return consecutiveIntervals(intervalMap, startMap, endMap, Duration::between); - } - - /** - * As defined by {@link #consecutiveIntervals(ToLongFunction, ToLongFunction)}. - * - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end - * @param type of the first mapped fact - * @param type of the second mapped fact - * @param type of the third mapped fact - * @param type of the fourth mapped fact - * @param type of the item in the cluster - * @return never null - */ - public static - QuadConstraintCollector, ConsecutiveIntervalInfo> - consecutiveIntervals(QuadFunction intervalMap, ToLongFunction startMap, - ToLongFunction endMap) { - return consecutiveIntervals(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); - } - - // Hide constructor since this is a factory class - private ExperimentalConstraintCollectors() { - } -} diff --git a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/api/ConsecutiveIntervalInfo.java b/examples/src/main/java/ai/timefold/solver/examples/common/experimental/api/ConsecutiveIntervalInfo.java deleted file mode 100644 index cd0052c433..0000000000 --- a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/api/ConsecutiveIntervalInfo.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.timefold.solver.examples.common.experimental.api; - -public interface ConsecutiveIntervalInfo, Difference_ extends Comparable> { - - /** - * @return never null, an iterable that iterates through the interval clusters - * contained in the collection in ascending order - */ - Iterable> getIntervalClusters(); - - /** - * @return never null, an iterable that iterates through the breaks contained in - * the collection in ascending order - */ - Iterable> getBreaks(); -} diff --git a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalBreakImpl.java b/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalBreakImpl.java deleted file mode 100644 index 78203cde35..0000000000 --- a/examples/src/main/java/ai/timefold/solver/examples/common/experimental/impl/IntervalBreakImpl.java +++ /dev/null @@ -1,54 +0,0 @@ -package ai.timefold.solver.examples.common.experimental.impl; - -import ai.timefold.solver.examples.common.experimental.api.IntervalBreak; -import ai.timefold.solver.examples.common.experimental.api.IntervalCluster; - -final class IntervalBreakImpl, Difference_ extends Comparable> - implements IntervalBreak { - private IntervalCluster previousCluster; - private IntervalCluster nextCluster; - private Difference_ length; - - IntervalBreakImpl(IntervalCluster previousCluster, - IntervalCluster nextCluster, Difference_ length) { - this.previousCluster = previousCluster; - this.nextCluster = nextCluster; - this.length = length; - } - - @Override - public IntervalCluster getPreviousIntervalCluster() { - return previousCluster; - } - - @Override - public IntervalCluster getNextIntervalCluster() { - return nextCluster; - } - - @Override - public Difference_ getLength() { - return length; - } - - void setPreviousCluster(IntervalCluster previousCluster) { - this.previousCluster = previousCluster; - } - - void setNextCluster(IntervalCluster nextCluster) { - this.nextCluster = nextCluster; - } - - void setLength(Difference_ length) { - this.length = length; - } - - @Override - public String toString() { - return "IntervalBreak{" + - "previousCluster=" + previousCluster + - ", nextCluster=" + nextCluster + - ", length=" + length + - '}'; - } -} diff --git a/examples/src/test/java/ai/timefold/solver/examples/common/experimental/ExperimentalConstraintCollectorsTest.java b/examples/src/test/java/ai/timefold/solver/examples/common/experimental/ExperimentalConstraintCollectorsTest.java deleted file mode 100644 index d234ab9092..0000000000 --- a/examples/src/test/java/ai/timefold/solver/examples/common/experimental/ExperimentalConstraintCollectorsTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package ai.timefold.solver.examples.common.experimental; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Arrays; -import java.util.function.BiConsumer; - -import ai.timefold.solver.core.api.score.stream.uni.UniConstraintCollector; -import ai.timefold.solver.examples.common.experimental.impl.ConsecutiveIntervalInfoImpl; -import ai.timefold.solver.examples.common.experimental.impl.IntervalTree; - -import org.junit.jupiter.api.Test; - -class ExperimentalConstraintCollectorsTest { - - @Test - void consecutiveInterval() { - // Do a basic test w/o edge cases; edge cases are covered in ConsecutiveSetTreeTest - var collector = - ExperimentalConstraintCollectors.consecutiveIntervals(Interval::start, Interval::end, (a, b) -> b - a); - var container = collector.supplier().get(); - // Add first value, sequence is [(1,3)] - Interval firstValue = new Interval(1, 3); - Runnable firstRetractor = accumulate(collector, container, firstValue); - assertResult(collector, container, consecutiveIntervalData(firstValue)); - // Add second value, sequence is [(1,3),(2,4)] - Interval secondValue = new Interval(2, 4); - Runnable secondRetractor = accumulate(collector, container, secondValue); - assertResult(collector, container, consecutiveIntervalData(firstValue, secondValue)); - // Add third value, same as the second. Sequence is [{1,1},2}] - Runnable thirdRetractor = accumulate(collector, container, secondValue); - assertResult(collector, container, consecutiveIntervalData(firstValue, secondValue, secondValue)); - // Retract one instance of the second value; we only have two values now. - secondRetractor.run(); - assertResult(collector, container, consecutiveIntervalData(firstValue, secondValue)); - // Retract final instance of the second value; we only have one value now. - thirdRetractor.run(); - assertResult(collector, container, consecutiveIntervalData(firstValue)); - // Retract last value; there are no values now. - firstRetractor.run(); - assertResult(collector, container, consecutiveIntervalData()); - } - - private ConsecutiveIntervalInfoImpl consecutiveIntervalData(Interval... data) { - return Arrays.stream(data).collect( - () -> new IntervalTree<>(Interval::start, Interval::end, (a, b) -> b - a), - (tree, datum) -> tree.add(tree.getInterval(datum)), - mergingNotSupported()).getConsecutiveIntervalData(); - } - - private static BiConsumer mergingNotSupported() { - return (a, b) -> { - throw new UnsupportedOperationException(); - }; - } - - private static Runnable accumulate( - UniConstraintCollector collector, Container_ container, A value) { - return collector.accumulator().apply(container, value); - } - - private static void assertResult( - UniConstraintCollector collector, Container_ container, Result_ expectedResult) { - Result_ actualResult = collector.finisher().apply(container); - assertThat(actualResult) - .as("Collector (" + collector + ") did not produce expected result.") - .usingRecursiveComparison() - .ignoringFields("sourceTree", "indexFunction", "sequenceList", "startItemToSequence") - .isEqualTo(expectedResult); - } - - private record Interval(int start, int end) { - - } - -} From 9cdd0b77d1e120ab30bcfacc300529319812b7f1 Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Thu, 18 Apr 2024 17:18:06 -0400 Subject: [PATCH 2/9] docs: Add docs on the consecutive usage collector --- .../score/stream/ConstraintCollectors.java | 9 +++- .../score-calculation.adoc | 48 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java index 88778874f7..71f1f84c95 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java @@ -1963,16 +1963,21 @@ public static UniConstraintCollector * {@code - * IntervalClusters: [[Shift from=2, to=4] [Shift from=3, to=5]], [[Shift from=6, to=7] [Shift from=7, to=8]] + * ConcurrentUsage: [minConcurrentUsage: 1, maxConcurrentUsage: 2, + * [Equipment from=2, to=4] [Equipment from=3, to=5]], + * [minConcurrentUsage: 1, maxConcurrentUsage: 1, + * [Equipment from=6, to=7] [Equipment from=7, to=8]] * Breaks: [[Break from=5, to=6, length=1]] * } * * + * This can be used to ensure a limited resource is not over-assigned. + * * @param startMap Maps the fact to its start * @param endMap Maps the fact to its end * @param differenceFunction Computes the difference between two points. The second argument is always diff --git a/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc b/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc index e940a211ed..22d1ec13b0 100644 --- a/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc +++ b/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc @@ -804,6 +804,52 @@ and `matches` contains 6 matches, then the `weight` penalty will be `6`. If the number of consecutive matches is `4`, then the sequence is not violating the league requirement, and we can filter it out. + +[#collectorsConcurrentUsage] +==== Concurrent usage collectors + +Certain constraints requires tracking concurrent usage of a resource. +If the maximum concurrent capacity of the resource is unknown at the time of writing the constraints, you can implement this pattern using the `ConstraintCollectors.concurrentUsage(...)` collector: + +[source,java,options="nowrap"] +---- +Constraint doNotOverAssignEquipment(ConstraintFactory constraintFactory) { + return constraintFactory.forEach(Equipment.class) + .join(Job.class, Joiners.equal(Equipment::getId, Job::getRequiredEquipmentId)) + .collect((equipment, job) -> equipment, ConstraintCollectors.concurrentUsage((equipment, job) -> job, + Job::getStart, + Job::getEnd, + (a, b) -> b - a)) + .flattenLast(ConcurrentUsageInfo::getConcurrentUsages) + .filter((equipment, concurrentUsage) -> concurrentUsage.getMaximumConcurrentUsage() > equipment.getCapacity()) + .penalize(HardSoftScore.ONE_HARD, (equipment, concurrentUsage) -> concurrentUsage.getMaximumConcurrentUsage() - equipment.getCapacity()) + .asConstraint("Concurrent equipment usage over capacity"); +} +---- + +Let's take a closer look at the crucial part of this constraint: + +[source,java,options="nowrap"] +---- + .collect((equipment, job) -> equipment, ConstraintCollectors.concurrentUsage((equipment, job) -> job, + Job::getStart, + Job::getEnd, + (a, b) -> b - a)) + .flattenLast(ConcurrentUsageInfo::getConcurrentUsages) + .filter((equipment, concurrentUsage) -> concurrentUsage.getMaximumConcurrentUsage() > equipment.getCapacity()) +---- + +The `groupBy()` building block groups all jobs by required equipment, and for every such pair it creates a `ConcurrentUsageInfo` instance. +Any overlapping jobs will be put into the same `ConcurrentUsage` cluster. + +The `ConcurrentUsageInfo` then has several useful methods to not only get the list of resource usage, but also to get any and all breaks between resource usage. +In this case, we are only interested in the resource usage, and we use <> to convert each to its own tuple. + +Finally, we use <> to calculate the violation amount for each concurrent usage. +For example, if the equipment has a `capacity` of `3`, and the `maximumConcurrentUsage` of the `resource` is `5`, then `violationAmount` will be `2`. +If the amount is `0`, then the equipment is not being used over its capacity and we can filter the tuple out. + + [#collectorsConditional] ==== Conditional collectors @@ -1511,4 +1557,4 @@ Typically it means creating one `ConstraintMatchTotal` per constraint type and calling `addConstraintMatch()` for each constraint match. `getConstraintMatchTotals()` code often duplicates some logic of the normal `IncrementalScoreCalculator` methods. Constraint Streams doesn't have this disadvantage, because they are aware of constraint matches by design, -without any extra domain-specific code. \ No newline at end of file +without any extra domain-specific code. From 9a3f3b28bc9122c82801df1180eeeda82eed7409 Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Mon, 22 Apr 2024 12:59:58 -0400 Subject: [PATCH 3/9] chore: Rename classes and methods --- .../score/stream/ConstraintCollectors.java | 100 ++++++------- .../score/stream/common/ConcurrentUsage.java | 18 --- .../stream/common/ConcurrentUsageInfo.java | 16 -- .../score/stream/common/ConnectedRange.java | 18 +++ .../stream/common/ConnectedRangeChain.java | 16 ++ .../score/stream/common/IntervalBreak.java | 49 ------ .../api/score/stream/common/RangeGap.java | 35 +++++ ...or.java => ConnectedRangesCalculator.java} | 14 +- .../stream/collector/ObjectCalculator.java | 2 +- ...ConnectedRangesBiConstraintCollector.java} | 16 +- .../bi/InnerBiConstraintCollectors.java | 8 +- .../bi/ObjectCalculatorBiCollector.java | 2 +- .../concurrentUsage/IntervalBreakImpl.java | 72 --------- .../ConnectedRangeChainImpl.java} | 64 ++++---- .../ConnectedRangeImpl.java} | 40 ++--- .../Interval.java | 2 +- .../IntervalSplitPoint.java | 2 +- .../IntervalTree.java | 23 ++- .../IntervalTreeIterator.java | 2 +- .../connectedRanges/RangeGapImpl.java | 62 ++++++++ .../TreeMultiSet.java | 2 +- ...nnectedRangesQuadConstraintCollector.java} | 16 +- .../quad/InnerQuadConstraintCollectors.java | 8 +- .../quad/ObjectCalculatorQuadCollector.java | 2 +- ...onnectedRangesTriConstraintCollector.java} | 16 +- .../tri/InnerTriConstraintCollectors.java | 8 +- .../tri/ObjectCalculatorTriCollector.java | 2 +- ...onnectedRangesUniConstraintCollector.java} | 16 +- .../uni/InnerUniConstraintCollectors.java | 8 +- .../uni/ObjectCalculatorUniCollector.java | 2 +- .../AbstractConstraintCollectorsTest.java | 8 +- .../bi/InnerBiConstraintCollectorsTest.java | 6 +- .../IntervalTreeTest.java | 140 +++++++++--------- .../IterableList.java | 2 +- .../InnerQuadConstraintCollectorsTest.java | 6 +- .../tri/InnerTriConstraintCollectorsTest.java | 6 +- .../uni/InnerUniConstraintCollectorsTest.java | 6 +- .../score-calculation.adoc | 32 ++-- 38 files changed, 410 insertions(+), 437 deletions(-) delete mode 100644 core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsage.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsageInfo.java create mode 100644 core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRange.java create mode 100644 core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRangeChain.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/api/score/stream/common/IntervalBreak.java create mode 100644 core/src/main/java/ai/timefold/solver/core/api/score/stream/common/RangeGap.java rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{ConcurrentUsageCalculator.java => ConnectedRangesCalculator.java} (63%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/{ConcurrentUsageBiConstraintCollector.java => ConnectedRangesBiConstraintCollector.java} (72%) delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalBreakImpl.java rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{concurrentUsage/ConcurrentUsageInfoImpl.java => connectedRanges/ConnectedRangeChainImpl.java} (84%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{concurrentUsage/ConcurrentUsageImpl.java => connectedRanges/ConnectedRangeImpl.java} (84%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{concurrentUsage => connectedRanges}/Interval.java (95%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{concurrentUsage => connectedRanges}/IntervalSplitPoint.java (98%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{concurrentUsage => connectedRanges}/IntervalTree.java (80%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{concurrentUsage => connectedRanges}/IntervalTreeIterator.java (93%) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/RangeGapImpl.java rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{concurrentUsage => connectedRanges}/TreeMultiSet.java (96%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/{ConcurrentUsageQuadConstraintCollector.java => ConnectedRangesQuadConstraintCollector.java} (75%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/{ConcurrentUsageTriConstraintCollector.java => ConnectedRangesTriConstraintCollector.java} (73%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/{ConcurrentUsageUniConstraintCollector.java => ConnectedRangesUniConstraintCollector.java} (72%) rename core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/{concurrentUsage => connectedRanges}/IntervalTreeTest.java (72%) rename core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/{concurrentUsage => connectedRanges}/IterableList.java (94%) diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java index 71f1f84c95..03f39aa8b3 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java @@ -37,7 +37,7 @@ import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.function.TriPredicate; import ai.timefold.solver.core.api.score.stream.bi.BiConstraintCollector; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; import ai.timefold.solver.core.api.score.stream.quad.QuadConstraintCollector; import ai.timefold.solver.core.api.score.stream.tri.TriConstraintCollector; @@ -1958,10 +1958,10 @@ public static UniConstraintCollector UniConstraintCollector, DifferenceType_ extends Comparable> - UniConstraintCollector> - concurrentUsage(Function startMap, Function endMap, + UniConstraintCollector> + toConnectedRanges(Function startMap, Function endMap, BiFunction differenceFunction) { - return InnerUniConstraintCollectors.consecutiveUsages(ConstantLambdaUtils.identity(), startMap, endMap, + return InnerUniConstraintCollectors.toConnectedRanges(ConstantLambdaUtils.identity(), startMap, endMap, differenceFunction); } /** - * Specialized version of {@link #concurrentUsage(Function,Function,BiFunction)} for + * Specialized version of {@link #toConnectedRanges(Function,Function,BiFunction)} for * {@link Temporal} types. * * @param type of the first mapped fact @@ -2007,26 +2007,26 @@ public static UniConstraintCollector> - UniConstraintCollector> - concurrentUsageByTime(Function startMap, Function endMap) { - return concurrentUsage(startMap, endMap, Duration::between); + UniConstraintCollector> + toConnectedRangesByTime(Function startMap, Function endMap) { + return toConnectedRanges(startMap, endMap, Duration::between); } /** - * Specialized version of {@link #concurrentUsage(Function,Function,BiFunction)} for Long. + * Specialized version of {@link #toConnectedRanges(Function,Function,BiFunction)} for Long. * * @param startMap Maps the fact to its start * @param endMap Maps the fact to its end * @param type of the first mapped fact * @return never null */ - public static UniConstraintCollector> - concurrentUsage(ToLongFunction startMap, ToLongFunction endMap) { - return concurrentUsage(startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); + public static UniConstraintCollector> + toConnectedRanges(ToLongFunction startMap, ToLongFunction endMap) { + return toConnectedRanges(startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); } /** - * As defined by {@link #concurrentUsage(Function,Function,BiFunction)}. + * As defined by {@link #toConnectedRanges(Function,Function,BiFunction)}. * * @param intervalMap Maps both facts to an item in the cluster * @param startMap Maps the item to its start @@ -2042,15 +2042,15 @@ public static UniConstraintCollector, DifferenceType_ extends Comparable> - BiConstraintCollector> - concurrentUsage(BiFunction intervalMap, Function startMap, + BiConstraintCollector> + toConnectedRanges(BiFunction intervalMap, Function startMap, Function endMap, BiFunction differenceFunction) { - return InnerBiConstraintCollectors.consecutiveUsages(intervalMap, startMap, endMap, differenceFunction); + return InnerBiConstraintCollectors.toConnectedRanges(intervalMap, startMap, endMap, differenceFunction); } /** - * As defined by {@link #concurrentUsageByTime(Function,Function)}. + * As defined by {@link #toConnectedRangesByTime(Function,Function)}. * * @param intervalMap Maps the three facts to an item in the cluster * @param startMap Maps the fact to its start @@ -2062,14 +2062,14 @@ public static UniConstraintCollector> - BiConstraintCollector> - concurrentUsageByTime(BiFunction intervalMap, + BiConstraintCollector> + toConnectedRangesByTime(BiFunction intervalMap, Function startMap, Function endMap) { - return concurrentUsage(intervalMap, startMap, endMap, Duration::between); + return toConnectedRanges(intervalMap, startMap, endMap, Duration::between); } /** - * As defined by {@link #concurrentUsage(ToLongFunction, ToLongFunction)}. + * As defined by {@link #toConnectedRanges(ToLongFunction, ToLongFunction)}. * * @param startMap Maps the fact to its start * @param endMap Maps the fact to its end @@ -2079,14 +2079,14 @@ public static UniConstraintCollector - BiConstraintCollector> - concurrentUsage(BiFunction intervalMap, ToLongFunction startMap, + BiConstraintCollector> + toConnectedRanges(BiFunction intervalMap, ToLongFunction startMap, ToLongFunction endMap) { - return concurrentUsage(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); + return toConnectedRanges(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); } /** - * As defined by {@link #concurrentUsage(Function,Function,BiFunction)}. + * As defined by {@link #toConnectedRanges(Function,Function,BiFunction)}. * * @param intervalMap Maps the three facts to an item in the cluster * @param startMap Maps the item to its start @@ -2103,15 +2103,15 @@ public static UniConstraintCollector, DifferenceType_ extends Comparable> - TriConstraintCollector> - concurrentUsage(TriFunction intervalMap, Function startMap, + TriConstraintCollector> + toConnectedRanges(TriFunction intervalMap, Function startMap, Function endMap, BiFunction differenceFunction) { - return InnerTriConstraintCollectors.consecutiveUsages(intervalMap, startMap, endMap, differenceFunction); + return InnerTriConstraintCollectors.toConnectedRanges(intervalMap, startMap, endMap, differenceFunction); } /** - * As defined by {@link #concurrentUsageByTime(Function,Function)}. + * As defined by {@link #toConnectedRangesByTime(Function,Function)}. * * @param intervalMap Maps the three facts to an item in the cluster * @param startMap Maps the fact to its start @@ -2124,14 +2124,14 @@ public static UniConstraintCollector> - TriConstraintCollector> - concurrentUsageByTime(TriFunction intervalMap, + TriConstraintCollector> + toConnectedRangesByTime(TriFunction intervalMap, Function startMap, Function endMap) { - return concurrentUsage(intervalMap, startMap, endMap, Duration::between); + return toConnectedRanges(intervalMap, startMap, endMap, Duration::between); } /** - * As defined by {@link #concurrentUsage(ToLongFunction, ToLongFunction)}. + * As defined by {@link #toConnectedRanges(ToLongFunction, ToLongFunction)}. * * @param startMap Maps the fact to its start * @param endMap Maps the fact to its end @@ -2142,14 +2142,14 @@ public static UniConstraintCollector - TriConstraintCollector> - concurrentUsage(TriFunction intervalMap, ToLongFunction startMap, + TriConstraintCollector> + toConnectedRanges(TriFunction intervalMap, ToLongFunction startMap, ToLongFunction endMap) { - return concurrentUsage(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); + return toConnectedRanges(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); } /** - * As defined by {@link #concurrentUsage(Function,Function,BiFunction)}. + * As defined by {@link #toConnectedRanges(Function,Function,BiFunction)}. * * @param intervalMap Maps the four facts to an item in the cluster * @param startMap Maps the item to its start @@ -2167,15 +2167,15 @@ public static UniConstraintCollector, DifferenceType_ extends Comparable> - QuadConstraintCollector> - concurrentUsage(QuadFunction intervalMap, + QuadConstraintCollector> + toConnectedRanges(QuadFunction intervalMap, Function startMap, Function endMap, BiFunction differenceFunction) { - return InnerQuadConstraintCollectors.consecutiveUsages(intervalMap, startMap, endMap, differenceFunction); + return InnerQuadConstraintCollectors.toConnectedRanges(intervalMap, startMap, endMap, differenceFunction); } /** - * As defined by {@link #concurrentUsageByTime(Function,Function)}. + * As defined by {@link #toConnectedRangesByTime(Function,Function)}. * * @param intervalMap Maps the three facts to an item in the cluster * @param startMap Maps the fact to its start @@ -2189,14 +2189,14 @@ public static UniConstraintCollector> - QuadConstraintCollector> - concurrentUsageByTime(QuadFunction intervalMap, + QuadConstraintCollector> + toConnectedRangesByTime(QuadFunction intervalMap, Function startMap, Function endMap) { - return concurrentUsage(intervalMap, startMap, endMap, Duration::between); + return toConnectedRanges(intervalMap, startMap, endMap, Duration::between); } /** - * As defined by {@link #concurrentUsage(ToLongFunction, ToLongFunction)}. + * As defined by {@link #toConnectedRanges(ToLongFunction, ToLongFunction)}. * * @param startMap Maps the fact to its start * @param endMap Maps the fact to its end @@ -2208,10 +2208,10 @@ public static UniConstraintCollector - QuadConstraintCollector> - concurrentUsage(QuadFunction intervalMap, ToLongFunction startMap, + QuadConstraintCollector> + toConnectedRanges(QuadFunction intervalMap, ToLongFunction startMap, ToLongFunction endMap) { - return concurrentUsage(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); + return toConnectedRanges(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); } private ConstraintCollectors() { diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsage.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsage.java deleted file mode 100644 index 6f4c5c36fd..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsage.java +++ /dev/null @@ -1,18 +0,0 @@ -package ai.timefold.solver.core.api.score.stream.common; - -public interface ConcurrentUsage, Difference_ extends Comparable> - extends Iterable { - int size(); - - boolean hasOverlap(); - - int getMinimumConcurrentUsage(); - - int getMaximumConcurrentUsage(); - - Difference_ getLength(); - - Point_ getStart(); - - Point_ getEnd(); -} diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsageInfo.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsageInfo.java deleted file mode 100644 index e7a154ec6a..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConcurrentUsageInfo.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.timefold.solver.core.api.score.stream.common; - -public interface ConcurrentUsageInfo, Difference_ extends Comparable> { - - /** - * @return never null, an iterable that iterates through the interval clusters - * contained in the collection in ascending order - */ - Iterable> getConcurrentUsages(); - - /** - * @return never null, an iterable that iterates through the breaks contained in - * the collection in ascending order - */ - Iterable> getBreaks(); -} diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRange.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRange.java new file mode 100644 index 0000000000..de58a3c6bc --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRange.java @@ -0,0 +1,18 @@ +package ai.timefold.solver.core.api.score.stream.common; + +public interface ConnectedRange, Difference_ extends Comparable> + extends Iterable { + int getContainedRangeCount(); + + boolean hasOverlap(); + + int getMinimumOverlap(); + + int getMaximumOverlap(); + + Difference_ getLength(); + + Point_ getStart(); + + Point_ getEnd(); +} diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRangeChain.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRangeChain.java new file mode 100644 index 0000000000..c1e6495a56 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRangeChain.java @@ -0,0 +1,16 @@ +package ai.timefold.solver.core.api.score.stream.common; + +public interface ConnectedRangeChain, Difference_ extends Comparable> { + + /** + * @return never null, an iterable that iterates through the connected ranges + * contained in the collection in ascending order of their start points + */ + Iterable> getConnectedRanges(); + + /** + * @return never null, an iterable that iterates through the breaks contained in + * the collection in ascending order of their start points + */ + Iterable> getGaps(); +} diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/IntervalBreak.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/IntervalBreak.java deleted file mode 100644 index 827665fa13..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/IntervalBreak.java +++ /dev/null @@ -1,49 +0,0 @@ -package ai.timefold.solver.core.api.score.stream.common; - -/** - * An IntervalBreak is a gap between two consecutive interval clusters. For instance, - * the list [(1,3),(2,4),(3,5),(7,8)] has a break of length 2 between 5 and 7. - * - * @param The type of value in the sequence - * @param The type of difference between values in the sequence - */ -public interface IntervalBreak, Difference_ extends Comparable> { - /** - * @return never null, the interval cluster leading directly into this - */ - ConcurrentUsage getPreviousConcurrentUsage(); - - /** - * @return never null, the interval cluster immediately following this - */ - ConcurrentUsage getNextConcurrentUsage(); - - /** - * Return the end of the sequence before this break. For the - * break between 6 and 10, this will return 6. - * - * @return never null, the item this break is directly after - */ - default Point_ getPreviousConcurrentUsageEnd() { - return getPreviousConcurrentUsage().getEnd(); - }; - - /** - * Return the start of the sequence after this break. For the - * break between 6 and 10, this will return 10. - * - * @return never null, the item this break is directly before - */ - default Point_ getNextConcurrentUsageStart() { - return getNextConcurrentUsage().getStart(); - } - - /** - * Return the length of the break, which is the difference - * between {@link #getNextConcurrentUsageStart()} and {@link #getPreviousConcurrentUsageEnd()}. For the - * break between 6 and 10, this will return 4. - * - * @return never null, the length of this break - */ - Difference_ getLength(); -} diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/RangeGap.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/RangeGap.java new file mode 100644 index 0000000000..e6fa86d5df --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/RangeGap.java @@ -0,0 +1,35 @@ +package ai.timefold.solver.core.api.score.stream.common; + +/** + * A {@link RangeGap} is a gap between two consecutive {@link ConnectedRange}s. + * For instance, the list [(1,3),(2,4),(3,5),(7,8)] has a grap of length 2 between 5 and 7. + * + * @param The type for the ranges' start and end points + * @param The type of difference between values in the sequence + */ +public interface RangeGap, Difference_ extends Comparable> { + /** + * Return the end of the sequence before this gap. + * For the gap between 6 and 10, this will return 6. + * + * @return never null, the item this gap is directly after + */ + Point_ getPreviousRangeEnd(); + + /** + * Return the start of the sequence after this gap. + * For the gap between 6 and 10, this will return 10. + * + * @return never null, the item this gap is directly before + */ + Point_ getNextRangeStart(); + + /** + * Return the length of the break, which is the difference + * between {@link #getNextRangeStart()} and {@link #getPreviousRangeEnd()}. + * For the gap between 6 and 10, this will return 4. + * + * @return never null, the length of this break + */ + Difference_ getLength(); +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConcurrentUsageCalculator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConnectedRangesCalculator.java similarity index 63% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConcurrentUsageCalculator.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConnectedRangesCalculator.java index 0985475cf6..8da319802a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConcurrentUsageCalculator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConnectedRangesCalculator.java @@ -3,15 +3,15 @@ import java.util.function.BiFunction; import java.util.function.Function; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; -import ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage.IntervalTree; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; +import ai.timefold.solver.core.impl.score.stream.collector.connectedRanges.IntervalTree; -public final class ConcurrentUsageCalculator, Difference_ extends Comparable> - implements ObjectCalculator> { +public final class ConnectedRangesCalculator, Difference_ extends Comparable> + implements ObjectCalculator> { private final IntervalTree context; - public ConcurrentUsageCalculator(Function startMap, + public ConnectedRangesCalculator(Function startMap, Function endMap, BiFunction differenceFunction) { this.context = new IntervalTree<>( @@ -31,8 +31,8 @@ public void retract(Interval_ result) { } @Override - public ConcurrentUsageInfo result() { - return context.getConsecutiveIntervalData(); + public ConnectedRangeChain result() { + return context.getConnectedRangeChain(); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ObjectCalculator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ObjectCalculator.java index 8cf6197491..d3007f2c25 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ObjectCalculator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ObjectCalculator.java @@ -1,7 +1,7 @@ package ai.timefold.solver.core.impl.score.stream.collector; public sealed interface ObjectCalculator - permits ConcurrentUsageCalculator, IntDistinctCountCalculator, LongDistinctCountCalculator, ReferenceAverageCalculator, + permits ConnectedRangesCalculator, IntDistinctCountCalculator, LongDistinctCountCalculator, ReferenceAverageCalculator, ReferenceSumCalculator, SequenceCalculator { void insert(Input_ input); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConcurrentUsageBiConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConnectedRangesBiConstraintCollector.java similarity index 72% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConcurrentUsageBiConstraintCollector.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConnectedRangesBiConstraintCollector.java index 52f2356f54..5ec943825a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConcurrentUsageBiConstraintCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConnectedRangesBiConstraintCollector.java @@ -5,18 +5,18 @@ import java.util.function.Function; import java.util.function.Supplier; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; -import ai.timefold.solver.core.impl.score.stream.collector.ConcurrentUsageCalculator; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; +import ai.timefold.solver.core.impl.score.stream.collector.ConnectedRangesCalculator; -final class ConcurrentUsageBiConstraintCollector, Difference_ extends Comparable> +final class ConnectedRangesBiConstraintCollector, Difference_ extends Comparable> extends - ObjectCalculatorBiCollector, ConcurrentUsageCalculator> { + ObjectCalculatorBiCollector, ConnectedRangesCalculator> { private final Function startMap; private final Function endMap; private final BiFunction differenceFunction; - public ConcurrentUsageBiConstraintCollector(BiFunction mapper, + public ConnectedRangesBiConstraintCollector(BiFunction mapper, Function startMap, Function endMap, BiFunction differenceFunction) { super(mapper); @@ -26,15 +26,15 @@ public ConcurrentUsageBiConstraintCollector(BiFunction> supplier() { - return () -> new ConcurrentUsageCalculator<>(startMap, endMap, differenceFunction); + public Supplier> supplier() { + return () -> new ConnectedRangesCalculator<>(startMap, endMap, differenceFunction); } @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof ConcurrentUsageBiConstraintCollector that)) + if (!(o instanceof ConnectedRangesBiConstraintCollector that)) return false; if (!super.equals(o)) return false; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectors.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectors.java index 0606df9124..cc10a1488f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectors.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectors.java @@ -22,7 +22,7 @@ import ai.timefold.solver.core.api.function.QuadFunction; import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.score.stream.bi.BiConstraintCollector; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; import ai.timefold.solver.core.impl.score.stream.collector.ReferenceAverageCalculator; @@ -210,12 +210,12 @@ public static BiConstraintCollector, Difference_ extends Comparable> - BiConstraintCollector> - consecutiveUsages(BiFunction mapper, + BiConstraintCollector> + toConnectedRanges(BiFunction mapper, Function startMap, Function endMap, BiFunction differenceFunction) { - return new ConcurrentUsageBiConstraintCollector<>(mapper, startMap, endMap, + return new ConnectedRangesBiConstraintCollector<>(mapper, startMap, endMap, differenceFunction); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ObjectCalculatorBiCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ObjectCalculatorBiCollector.java index 87f85dbcec..2e197c5138 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ObjectCalculatorBiCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ObjectCalculatorBiCollector.java @@ -10,7 +10,7 @@ abstract sealed class ObjectCalculatorBiCollector> implements BiConstraintCollector - permits AverageReferenceBiCollector, ConcurrentUsageBiConstraintCollector, ConsecutiveSequencesBiConstraintCollector, + permits AverageReferenceBiCollector, ConnectedRangesBiConstraintCollector, ConsecutiveSequencesBiConstraintCollector, CountDistinctIntBiCollector, CountDistinctLongBiCollector, SumReferenceBiCollector { protected final BiFunction mapper; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalBreakImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalBreakImpl.java deleted file mode 100644 index 12475c663f..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalBreakImpl.java +++ /dev/null @@ -1,72 +0,0 @@ -package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; - -import ai.timefold.solver.core.api.score.stream.common.Break; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsage; - -final class IntervalBreakImpl, Difference_ extends Comparable> - implements Break { - private ConcurrentUsage previousCluster; - private ConcurrentUsage nextCluster; - private Difference_ length; - - IntervalBreakImpl(ConcurrentUsage previousCluster, - ConcurrentUsage nextCluster, Difference_ length) { - this.previousCluster = previousCluster; - this.nextCluster = nextCluster; - this.length = length; - } - - public ConcurrentUsage getPreviousConcurrentUsage() { - return previousCluster; - } - - public ConcurrentUsage getNextConcurrentUsage() { - return nextCluster; - } - - @Override - public boolean isFirst() { - return previousCluster == null; - } - - @Override - public boolean isLast() { - return nextCluster == null; - } - - @Override - public Point_ getPreviousSequenceEnd() { - return previousCluster.getEnd(); - } - - @Override - public Point_ getNextSequenceStart() { - return nextCluster.getStart(); - } - - @Override - public Difference_ getLength() { - return length; - } - - void setPreviousCluster(ConcurrentUsage previousCluster) { - this.previousCluster = previousCluster; - } - - void setNextCluster(ConcurrentUsage nextCluster) { - this.nextCluster = nextCluster; - } - - void setLength(Difference_ length) { - this.length = length; - } - - @Override - public String toString() { - return "IntervalBreak{" + - "previousCluster=" + previousCluster + - ", nextCluster=" + nextCluster + - ", length=" + length + - '}'; - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageInfoImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeChainImpl.java similarity index 84% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageInfoImpl.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeChainImpl.java index 23955bccbc..ee0debe6eb 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageInfoImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeChainImpl.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; +package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; import java.util.NavigableMap; import java.util.NavigableSet; @@ -7,19 +7,19 @@ import java.util.TreeSet; import java.util.function.BiFunction; -import ai.timefold.solver.core.api.score.stream.common.Break; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsage; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRange; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; +import ai.timefold.solver.core.api.score.stream.common.RangeGap; -public final class ConcurrentUsageInfoImpl, Difference_ extends Comparable> - implements ConcurrentUsageInfo { +public final class ConnectedRangeChainImpl, Difference_ extends Comparable> + implements ConnectedRangeChain { - private final NavigableMap, ConcurrentUsageImpl> clusterStartSplitPointToCluster; + private final NavigableMap, ConnectedRangeImpl> clusterStartSplitPointToCluster; private final NavigableSet> splitPointSet; - private final NavigableMap, IntervalBreakImpl> clusterStartSplitPointToNextBreak; + private final NavigableMap, RangeGapImpl> clusterStartSplitPointToNextBreak; private final BiFunction differenceFunction; - public ConcurrentUsageInfoImpl(TreeSet> splitPointSet, + public ConnectedRangeChainImpl(TreeSet> splitPointSet, BiFunction differenceFunction) { this.clusterStartSplitPointToCluster = new TreeMap<>(); this.clusterStartSplitPointToNextBreak = new TreeMap<>(); @@ -78,8 +78,8 @@ void addInterval(Interval interval) { var nextBreak = clusterStartSplitPointToNextBreak.get(firstIntersectedIntervalCluster.getStartSplitPoint()); if (nextBreak != null) { nextBreak.setPreviousCluster(firstIntersectedIntervalCluster); - nextBreak.setLength(differenceFunction.apply(nextBreak.getPreviousSequenceEnd(), - nextBreak.getNextSequenceStart())); + nextBreak.setLength(differenceFunction.apply(nextBreak.getPreviousRangeEnd(), + nextBreak.getNextRangeStart())); } } } @@ -90,7 +90,7 @@ private void createNewIntervalCluster(Interval interval) { // ----- //---- ----- var startSplitPoint = splitPointSet.floor(interval.getStartSplitPoint()); - var newCluster = new ConcurrentUsageImpl<>(splitPointSet, differenceFunction, startSplitPoint); + var newCluster = new ConnectedRangeImpl<>(splitPointSet, differenceFunction, startSplitPoint); clusterStartSplitPointToCluster.put(startSplitPoint, newCluster); // If there a cluster after this interval, add a new break @@ -99,7 +99,7 @@ private void createNewIntervalCluster(Interval interval) { if (nextClusterEntry != null) { var nextCluster = nextClusterEntry.getValue(); var difference = differenceFunction.apply(newCluster.getEnd(), nextCluster.getStart()); - var newBreak = new IntervalBreakImpl<>(newCluster, nextCluster, difference); + var newBreak = new RangeGapImpl<>(newCluster, nextCluster, difference); clusterStartSplitPointToNextBreak.put(startSplitPoint, newBreak); } @@ -110,13 +110,13 @@ private void createNewIntervalCluster(Interval interval) { if (previousClusterEntry != null) { var previousCluster = previousClusterEntry.getValue(); var difference = differenceFunction.apply(previousCluster.getEnd(), newCluster.getStart()); - var newBreak = new IntervalBreakImpl<>(previousCluster, newCluster, difference); + var newBreak = new RangeGapImpl<>(previousCluster, newCluster, difference); clusterStartSplitPointToNextBreak.put(previousClusterEntry.getKey(), newBreak); } } private void removeSpannedBreaksAndUpdateIntersectedBreaks(Interval interval, - ConcurrentUsageImpl intervalCluster) { + ConnectedRangeImpl intervalCluster) { var firstBreakSplitPointBeforeInterval = Objects.requireNonNullElseGet( clusterStartSplitPointToNextBreak.floorKey(interval.getStartSplitPoint()), interval::getStartSplitPoint); var intersectedIntervalBreakMap = clusterStartSplitPointToNextBreak.subMap(firstBreakSplitPointBeforeInterval, true, @@ -127,10 +127,10 @@ private void removeSpannedBreaksAndUpdateIntersectedBreaks(Interval) (intersectedIntervalBreakMap.firstEntry().getValue() + (ConnectedRangeImpl) (intersectedIntervalBreakMap.firstEntry().getValue() .getPreviousConcurrentUsage()); var clusterAfterFinalIntersectedBreak = - (ConcurrentUsageImpl) (intersectedIntervalBreakMap.lastEntry().getValue() + (ConnectedRangeImpl) (intersectedIntervalBreakMap.lastEntry().getValue() .getNextConcurrentUsage()); // All breaks that are not the first or last intersected breaks will @@ -151,8 +151,8 @@ private void removeSpannedBreaksAndUpdateIntersectedBreaks(Interval) (previousBreak + .put(((ConnectedRangeImpl) (previousBreak .getPreviousConcurrentUsage())).getStartSplitPoint(), previousBreak); } else { // Case: interval does not span either the first or final break @@ -176,14 +176,14 @@ private void removeSpannedBreaksAndUpdateIntersectedBreaks(Interval interval) { var previousBreak = (previousBreakEntry != null) ? previousBreakEntry.getValue() : null; var previousIntervalCluster = (previousBreak != null) - ? (ConcurrentUsageImpl) previousBreak.getPreviousConcurrentUsage() + ? (ConnectedRangeImpl) previousBreak.getPreviousConcurrentUsage() : null; for (var newIntervalCluster : intervalCluster.removeInterval(interval)) { @@ -210,10 +210,10 @@ void removeInterval(Interval interval) { previousBreak.setLength(differenceFunction.apply(previousBreak.getPreviousConcurrentUsage().getEnd(), newIntervalCluster.getStart())); clusterStartSplitPointToNextBreak - .put(((ConcurrentUsageImpl) previousBreak + .put(((ConnectedRangeImpl) previousBreak .getPreviousConcurrentUsage()).getStartSplitPoint(), previousBreak); } - previousBreak = new IntervalBreakImpl<>(newIntervalCluster, null, null); + previousBreak = new RangeGapImpl<>(newIntervalCluster, null, null); previousIntervalCluster = newIntervalCluster; clusterStartSplitPointToCluster.put(newIntervalCluster.getStartSplitPoint(), newIntervalCluster); } @@ -235,12 +235,12 @@ void removeInterval(Interval interval) { } @Override - public Iterable> getConcurrentUsages() { + public Iterable> getConnectedRanges() { return (Iterable) clusterStartSplitPointToCluster.values(); } @Override - public Iterable> getBreaks() { + public Iterable> getGaps() { return (Iterable) clusterStartSplitPointToNextBreak.values(); } @@ -248,7 +248,7 @@ public Iterable> getBreaks() { public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof ConcurrentUsageInfoImpl that)) + if (!(o instanceof ConnectedRangeChainImpl that)) return false; return Objects.equals(clusterStartSplitPointToCluster, that.clusterStartSplitPointToCluster) @@ -266,8 +266,8 @@ public int hashCode() { @Override public String toString() { return "Clusters {" + - "intervalClusters=" + getConcurrentUsages() + - ", breaks=" + getBreaks() + + "intervalClusters=" + getConnectedRanges() + + ", breaks=" + getGaps() + '}'; } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeImpl.java similarity index 84% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageImpl.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeImpl.java index a2a5acdc4a..5e356c37e6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/ConcurrentUsageImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeImpl.java @@ -1,14 +1,14 @@ -package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; +package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; import java.util.Iterator; import java.util.NavigableSet; import java.util.Objects; import java.util.function.BiFunction; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsage; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRange; -final class ConcurrentUsageImpl, Difference_ extends Comparable> - implements ConcurrentUsage { +final class ConnectedRangeImpl, Difference_ extends Comparable> + implements ConnectedRange { private final NavigableSet> splitPointSet; private final BiFunction differenceFunction; @@ -20,7 +20,7 @@ final class ConcurrentUsageImpl, Di private int maximumOverlap; private boolean hasOverlap; - ConcurrentUsageImpl(NavigableSet> splitPointSet, + ConnectedRangeImpl(NavigableSet> splitPointSet, BiFunction differenceFunction, IntervalSplitPoint start) { if (start == null) { @@ -55,7 +55,7 @@ final class ConcurrentUsageImpl, Di this.endSplitPoint = (current != null) ? splitPointSet.lower(current) : splitPointSet.last(); } - ConcurrentUsageImpl(NavigableSet> splitPointSet, + ConnectedRangeImpl(NavigableSet> splitPointSet, BiFunction differenceFunction, IntervalSplitPoint start, IntervalSplitPoint end, int count, @@ -95,11 +95,11 @@ void addInterval(Interval interval) { count++; } - Iterable> removeInterval(Interval interval) { + Iterable> removeInterval(Interval interval) { return IntervalClusterIterator::new; } - void mergeIntervalCluster(ConcurrentUsageImpl laterIntervalCluster) { + void mergeIntervalCluster(ConnectedRangeImpl laterIntervalCluster) { if (endSplitPoint.compareTo(laterIntervalCluster.startSplitPoint) > 0) { hasOverlap = true; } @@ -118,7 +118,7 @@ public Iterator iterator() { } @Override - public int size() { + public int getContainedRangeCount() { return count; } @@ -143,7 +143,7 @@ private void recalculateMinimumAndMaximumOverlap() { } @Override - public int getMinimumConcurrentUsage() { + public int getMinimumOverlap() { if (minimumOverlap == -1) { recalculateMinimumAndMaximumOverlap(); } @@ -151,7 +151,7 @@ public int getMinimumConcurrentUsage() { } @Override - public int getMaximumConcurrentUsage() { + public int getMaximumOverlap() { if (maximumOverlap == -1) { recalculateMinimumAndMaximumOverlap(); } @@ -177,11 +177,11 @@ public Difference_ getLength() { public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof ConcurrentUsageImpl that)) + if (!(o instanceof ConnectedRangeImpl that)) return false; return count == that.count && - getMinimumConcurrentUsage() == that.getMinimumConcurrentUsage() - && getMaximumConcurrentUsage() == that.getMaximumConcurrentUsage() + getMinimumOverlap() == that.getMinimumOverlap() + && getMaximumOverlap() == that.getMaximumOverlap() && hasOverlap == that.hasOverlap && Objects.equals( splitPointSet, that.splitPointSet) && Objects.equals(startSplitPoint, @@ -192,7 +192,7 @@ && getMaximumConcurrentUsage() == that.getMaximumConcurrentUsage() @Override public int hashCode() { return Objects.hash(splitPointSet, startSplitPoint, endSplitPoint, count, - getMinimumConcurrentUsage(), getMaximumConcurrentUsage(), hasOverlap); + getMinimumOverlap(), getMaximumOverlap(), hasOverlap); } @Override @@ -201,8 +201,8 @@ public String toString() { "start=" + startSplitPoint + ", end=" + endSplitPoint + ", count=" + count + - ", minimumOverlap=" + getMinimumConcurrentUsage() + - ", maximumOverlap=" + getMaximumConcurrentUsage() + + ", minimumOverlap=" + getMinimumOverlap() + + ", maximumOverlap=" + getMaximumOverlap() + ", hasOverlap=" + hasOverlap + ", set=" + splitPointSet + '}'; @@ -210,7 +210,7 @@ public String toString() { // TODO: Make this incremental by only checking between the interval's start and end points private final class IntervalClusterIterator - implements Iterator> { + implements Iterator> { private IntervalSplitPoint current = getStart(startSplitPoint); @@ -228,7 +228,7 @@ public boolean hasNext() { } @Override - public ConcurrentUsageImpl next() { + public ConnectedRangeImpl next() { IntervalSplitPoint start = current; IntervalSplitPoint end; int activeIntervals = 0; @@ -258,7 +258,7 @@ public ConcurrentUsageImpl next() { } hasOverlap = anyOverlap; - return new ConcurrentUsageImpl<>(splitPointSet, differenceFunction, start, end, count, + return new ConnectedRangeImpl<>(splitPointSet, differenceFunction, start, end, count, minimumOverlap, maximumOverlap, hasOverlap); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/Interval.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/Interval.java similarity index 95% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/Interval.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/Interval.java index cfdab122fa..50da2355a3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/Interval.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/Interval.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; +package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; import java.util.function.Function; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalSplitPoint.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalSplitPoint.java similarity index 98% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalSplitPoint.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalSplitPoint.java index 4777ae444d..af53f3d122 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalSplitPoint.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalSplitPoint.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; +package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; import java.util.Comparator; import java.util.IdentityHashMap; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTree.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTree.java similarity index 80% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTree.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTree.java index 77874e762f..aa5bfdf3e9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTree.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTree.java @@ -1,18 +1,18 @@ -package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; +package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; import java.util.Iterator; import java.util.TreeSet; import java.util.function.BiFunction; import java.util.function.Function; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; public final class IntervalTree, Difference_ extends Comparable> { private final Function startMapping; private final Function endMapping; private final TreeSet> splitPointSet; - private final ConcurrentUsageInfoImpl consecutiveIntervalData; + private final ConnectedRangeChainImpl consecutiveIntervalData; public IntervalTree(Function startMapping, Function endMapping, @@ -20,7 +20,7 @@ public IntervalTree(Function startMapping, this.startMapping = startMapping; this.endMapping = endMapping; this.splitPointSet = new TreeSet<>(); - this.consecutiveIntervalData = new ConcurrentUsageInfoImpl<>(splitPointSet, differenceFunction); + this.consecutiveIntervalData = new ConnectedRangeChainImpl<>(splitPointSet, differenceFunction); } public Interval getInterval(Interval_ intervalValue) { @@ -50,29 +50,26 @@ public Iterator iterator() { public boolean add(Interval interval) { var startSplitPoint = interval.getStartSplitPoint(); var endSplitPoint = interval.getEndSplitPoint(); - var anyChanged = false; var flooredStartSplitPoint = splitPointSet.floor(startSplitPoint); if (flooredStartSplitPoint == null || !flooredStartSplitPoint.equals(startSplitPoint)) { splitPointSet.add(startSplitPoint); startSplitPoint.createCollections(); - anyChanged |= startSplitPoint.addIntervalStartingAtSplitPoint(interval); + startSplitPoint.addIntervalStartingAtSplitPoint(interval); } else { - anyChanged |= flooredStartSplitPoint.addIntervalStartingAtSplitPoint(interval); + flooredStartSplitPoint.addIntervalStartingAtSplitPoint(interval); } var ceilingEndSplitPoint = splitPointSet.ceiling(endSplitPoint); if (ceilingEndSplitPoint == null || !ceilingEndSplitPoint.equals(endSplitPoint)) { splitPointSet.add(endSplitPoint); endSplitPoint.createCollections(); - anyChanged |= endSplitPoint.addIntervalEndingAtSplitPoint(interval); + endSplitPoint.addIntervalEndingAtSplitPoint(interval); } else { - anyChanged |= ceilingEndSplitPoint.addIntervalEndingAtSplitPoint(interval); + ceilingEndSplitPoint.addIntervalEndingAtSplitPoint(interval); } - if (true || anyChanged) { - consecutiveIntervalData.addInterval(interval); - } + consecutiveIntervalData.addInterval(interval); return true; } @@ -100,7 +97,7 @@ public boolean remove(Interval interval) { return true; } - public ConcurrentUsageInfo getConsecutiveIntervalData() { + public ConnectedRangeChain getConnectedRangeChain() { return consecutiveIntervalData; } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTreeIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTreeIterator.java similarity index 93% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTreeIterator.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTreeIterator.java index 28c3903469..d4aa6b5d49 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTreeIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTreeIterator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; +package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; import java.util.Iterator; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/RangeGapImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/RangeGapImpl.java new file mode 100644 index 0000000000..6ff6a99b73 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/RangeGapImpl.java @@ -0,0 +1,62 @@ +package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; + +import ai.timefold.solver.core.api.score.stream.common.ConnectedRange; +import ai.timefold.solver.core.api.score.stream.common.RangeGap; + +final class RangeGapImpl, Difference_ extends Comparable> + implements RangeGap { + private ConnectedRange previousCluster; + private ConnectedRange nextCluster; + private Difference_ length; + + RangeGapImpl(ConnectedRange previousCluster, + ConnectedRange nextCluster, Difference_ length) { + this.previousCluster = previousCluster; + this.nextCluster = nextCluster; + this.length = length; + } + + public ConnectedRange getPreviousConcurrentUsage() { + return previousCluster; + } + + public ConnectedRange getNextConcurrentUsage() { + return nextCluster; + } + + @Override + public Point_ getPreviousRangeEnd() { + return previousCluster.getEnd(); + } + + @Override + public Point_ getNextRangeStart() { + return nextCluster.getStart(); + } + + @Override + public Difference_ getLength() { + return length; + } + + void setPreviousCluster(ConnectedRange previousCluster) { + this.previousCluster = previousCluster; + } + + void setNextCluster(ConnectedRange nextCluster) { + this.nextCluster = nextCluster; + } + + void setLength(Difference_ length) { + this.length = length; + } + + @Override + public String toString() { + return "RangeGap{" + + "start=" + getPreviousRangeEnd() + + ", end=" + getNextRangeStart() + + ", length=" + length + + '}'; + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/TreeMultiSet.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/TreeMultiSet.java similarity index 96% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/TreeMultiSet.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/TreeMultiSet.java index 3a9508e223..8ad99051bf 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/TreeMultiSet.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/TreeMultiSet.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; +package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; import java.util.AbstractSet; import java.util.Collection; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConcurrentUsageQuadConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConnectedRangesQuadConstraintCollector.java similarity index 75% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConcurrentUsageQuadConstraintCollector.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConnectedRangesQuadConstraintCollector.java index a71be9f550..4902c48047 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConcurrentUsageQuadConstraintCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConnectedRangesQuadConstraintCollector.java @@ -6,18 +6,18 @@ import java.util.function.Supplier; import ai.timefold.solver.core.api.function.QuadFunction; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; -import ai.timefold.solver.core.impl.score.stream.collector.ConcurrentUsageCalculator; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; +import ai.timefold.solver.core.impl.score.stream.collector.ConnectedRangesCalculator; -final class ConcurrentUsageQuadConstraintCollector, Difference_ extends Comparable> +final class ConnectedRangesQuadConstraintCollector, Difference_ extends Comparable> extends - ObjectCalculatorQuadCollector, ConcurrentUsageCalculator> { + ObjectCalculatorQuadCollector, ConnectedRangesCalculator> { private final Function startMap; private final Function endMap; private final BiFunction differenceFunction; - public ConcurrentUsageQuadConstraintCollector( + public ConnectedRangesQuadConstraintCollector( QuadFunction mapper, Function startMap, Function endMap, BiFunction differenceFunction) { @@ -28,15 +28,15 @@ public ConcurrentUsageQuadConstraintCollector( } @Override - public Supplier> supplier() { - return () -> new ConcurrentUsageCalculator<>(startMap, endMap, differenceFunction); + public Supplier> supplier() { + return () -> new ConnectedRangesCalculator<>(startMap, endMap, differenceFunction); } @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof ConcurrentUsageQuadConstraintCollector that)) + if (!(o instanceof ConnectedRangesQuadConstraintCollector that)) return false; if (!super.equals(o)) return false; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectors.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectors.java index 511a1fcac8..145306e20d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectors.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectors.java @@ -21,7 +21,7 @@ import ai.timefold.solver.core.api.function.ToIntQuadFunction; import ai.timefold.solver.core.api.function.ToLongQuadFunction; import ai.timefold.solver.core.api.function.TriFunction; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; import ai.timefold.solver.core.api.score.stream.quad.QuadConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.ReferenceAverageCalculator; @@ -218,12 +218,12 @@ public class InnerQuadConstraintCollectors { } public static , Difference_ extends Comparable> - QuadConstraintCollector> - consecutiveUsages(QuadFunction mapper, + QuadConstraintCollector> + toConnectedRanges(QuadFunction mapper, Function startMap, Function endMap, BiFunction differenceFunction) { - return new ConcurrentUsageQuadConstraintCollector<>(mapper, startMap, endMap, + return new ConnectedRangesQuadConstraintCollector<>(mapper, startMap, endMap, differenceFunction); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ObjectCalculatorQuadCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ObjectCalculatorQuadCollector.java index 99da978303..ee508a4a8f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ObjectCalculatorQuadCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ObjectCalculatorQuadCollector.java @@ -10,7 +10,7 @@ abstract sealed class ObjectCalculatorQuadCollector> implements QuadConstraintCollector - permits AverageReferenceQuadCollector, ConcurrentUsageQuadConstraintCollector, + permits AverageReferenceQuadCollector, ConnectedRangesQuadConstraintCollector, ConsecutiveSequencesQuadConstraintCollector, CountDistinctIntQuadCollector, CountDistinctLongQuadCollector, SumReferenceQuadCollector { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConcurrentUsageTriConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConnectedRangesTriConstraintCollector.java similarity index 73% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConcurrentUsageTriConstraintCollector.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConnectedRangesTriConstraintCollector.java index 342c2ef52e..17851a680e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConcurrentUsageTriConstraintCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConnectedRangesTriConstraintCollector.java @@ -6,18 +6,18 @@ import java.util.function.Supplier; import ai.timefold.solver.core.api.function.TriFunction; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; -import ai.timefold.solver.core.impl.score.stream.collector.ConcurrentUsageCalculator; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; +import ai.timefold.solver.core.impl.score.stream.collector.ConnectedRangesCalculator; -final class ConcurrentUsageTriConstraintCollector, Difference_ extends Comparable> +final class ConnectedRangesTriConstraintCollector, Difference_ extends Comparable> extends - ObjectCalculatorTriCollector, ConcurrentUsageCalculator> { + ObjectCalculatorTriCollector, ConnectedRangesCalculator> { private final Function startMap; private final Function endMap; private final BiFunction differenceFunction; - public ConcurrentUsageTriConstraintCollector(TriFunction mapper, + public ConnectedRangesTriConstraintCollector(TriFunction mapper, Function startMap, Function endMap, BiFunction differenceFunction) { super(mapper); @@ -27,15 +27,15 @@ public ConcurrentUsageTriConstraintCollector(TriFunction> supplier() { - return () -> new ConcurrentUsageCalculator<>(startMap, endMap, differenceFunction); + public Supplier> supplier() { + return () -> new ConnectedRangesCalculator<>(startMap, endMap, differenceFunction); } @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof ConcurrentUsageTriConstraintCollector that)) + if (!(o instanceof ConnectedRangesTriConstraintCollector that)) return false; if (!super.equals(o)) return false; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectors.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectors.java index 5796bed016..4154db696e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectors.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectors.java @@ -21,7 +21,7 @@ import ai.timefold.solver.core.api.function.ToLongTriFunction; import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.function.TriPredicate; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; import ai.timefold.solver.core.api.score.stream.tri.TriConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.ReferenceAverageCalculator; @@ -217,12 +217,12 @@ public class InnerTriConstraintCollectors { } public static , Difference_ extends Comparable> - TriConstraintCollector> - consecutiveUsages(TriFunction mapper, + TriConstraintCollector> + toConnectedRanges(TriFunction mapper, Function startMap, Function endMap, BiFunction differenceFunction) { - return new ConcurrentUsageTriConstraintCollector<>(mapper, startMap, endMap, + return new ConnectedRangesTriConstraintCollector<>(mapper, startMap, endMap, differenceFunction); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ObjectCalculatorTriCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ObjectCalculatorTriCollector.java index c11ec17dcd..d57b95b9f5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ObjectCalculatorTriCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ObjectCalculatorTriCollector.java @@ -10,7 +10,7 @@ abstract sealed class ObjectCalculatorTriCollector> implements TriConstraintCollector - permits AverageReferenceTriCollector, ConcurrentUsageTriConstraintCollector, ConsecutiveSequencesTriConstraintCollector, + permits AverageReferenceTriCollector, ConnectedRangesTriConstraintCollector, ConsecutiveSequencesTriConstraintCollector, CountDistinctIntTriCollector, CountDistinctLongTriCollector, SumReferenceTriCollector { protected final TriFunction mapper; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConcurrentUsageUniConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConnectedRangesUniConstraintCollector.java similarity index 72% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConcurrentUsageUniConstraintCollector.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConnectedRangesUniConstraintCollector.java index 201554a9e5..20557c211b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConcurrentUsageUniConstraintCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConnectedRangesUniConstraintCollector.java @@ -5,18 +5,18 @@ import java.util.function.Function; import java.util.function.Supplier; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; -import ai.timefold.solver.core.impl.score.stream.collector.ConcurrentUsageCalculator; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; +import ai.timefold.solver.core.impl.score.stream.collector.ConnectedRangesCalculator; -final class ConcurrentUsageUniConstraintCollector, Difference_ extends Comparable> +final class ConnectedRangesUniConstraintCollector, Difference_ extends Comparable> extends - ObjectCalculatorUniCollector, ConcurrentUsageCalculator> { + ObjectCalculatorUniCollector, ConnectedRangesCalculator> { private final Function startMap; private final Function endMap; private final BiFunction differenceFunction; - public ConcurrentUsageUniConstraintCollector(Function mapper, + public ConnectedRangesUniConstraintCollector(Function mapper, Function startMap, Function endMap, BiFunction differenceFunction) { super(mapper); @@ -26,15 +26,15 @@ public ConcurrentUsageUniConstraintCollector(Function> supplier() { - return () -> new ConcurrentUsageCalculator<>(startMap, endMap, differenceFunction); + public Supplier> supplier() { + return () -> new ConnectedRangesCalculator<>(startMap, endMap, differenceFunction); } @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof ConcurrentUsageUniConstraintCollector that)) + if (!(o instanceof ConnectedRangesUniConstraintCollector that)) return false; if (!super.equals(o)) return false; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectors.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectors.java index 8c00c811c0..2854a87221 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectors.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectors.java @@ -20,7 +20,7 @@ import ai.timefold.solver.core.api.function.QuadFunction; import ai.timefold.solver.core.api.function.TriFunction; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; import ai.timefold.solver.core.api.score.stream.uni.UniConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.ReferenceAverageCalculator; @@ -197,12 +197,12 @@ public static UniConstraintCollector, Difference_ extends Comparable> - UniConstraintCollector> - consecutiveUsages(Function mapper, + UniConstraintCollector> + toConnectedRanges(Function mapper, Function startMap, Function endMap, BiFunction differenceFunction) { - return new ConcurrentUsageUniConstraintCollector<>(mapper, startMap, endMap, + return new ConnectedRangesUniConstraintCollector<>(mapper, startMap, endMap, differenceFunction); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ObjectCalculatorUniCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ObjectCalculatorUniCollector.java index 3f444257be..a7f6c56811 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ObjectCalculatorUniCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ObjectCalculatorUniCollector.java @@ -9,7 +9,7 @@ abstract sealed class ObjectCalculatorUniCollector> implements UniConstraintCollector - permits AverageReferenceUniCollector, ConcurrentUsageUniConstraintCollector, ConsecutiveSequencesUniConstraintCollector, + permits AverageReferenceUniCollector, ConnectedRangesUniConstraintCollector, ConsecutiveSequencesUniConstraintCollector, CountDistinctIntUniCollector, CountDistinctLongUniCollector, SumReferenceUniCollector { protected final Function mapper; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java index 8f25290083..c8030c9e51 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java @@ -2,9 +2,9 @@ import java.util.Arrays; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; -import ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage.IntervalTree; +import ai.timefold.solver.core.impl.score.stream.collector.connectedRanges.IntervalTree; import ai.timefold.solver.core.impl.score.stream.collector.consecutive.ConsecutiveSetTree; import org.junit.jupiter.api.Test; @@ -122,13 +122,13 @@ protected static SequenceChain buildSequenceChain(Integer... d }); } - protected ConcurrentUsageInfo buildConsecutiveUsage(Interval... data) { + protected ConnectedRangeChain buildConsecutiveUsage(Interval... data) { return Arrays.stream(data).collect( () -> new IntervalTree<>(Interval::start, Interval::end, (a, b) -> b - a), (tree, datum) -> tree.add(tree.getInterval(datum)), (a, b) -> { throw new UnsupportedOperationException(); - }).getConsecutiveIntervalData(); + }).getConnectedRangeChain(); } public record Interval(int start, int end) { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectorsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectorsTest.java index b9571fb236..a1ce13483f 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectorsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/bi/InnerBiConstraintCollectorsTest.java @@ -32,7 +32,7 @@ import ai.timefold.solver.core.api.score.stream.ConstraintCollectors; import ai.timefold.solver.core.api.score.stream.bi.BiConstraintCollector; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.impl.score.stream.collector.AbstractConstraintCollectorsTest; import ai.timefold.solver.core.impl.util.Pair; import ai.timefold.solver.core.impl.util.Quadruple; @@ -1070,8 +1070,8 @@ public void toConsecutiveSequences() { @Override @Test public void consecutiveUsage() { - BiConstraintCollector> collector = - ConstraintCollectors.concurrentUsage(Interval::new, + BiConstraintCollector> collector = + ConstraintCollectors.toConnectedRanges(Interval::new, Interval::start, Interval::end, (a, b) -> b - a); var container = collector.supplier().get(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTreeTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTreeTest.java similarity index 72% rename from core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTreeTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTreeTest.java index d3afddae60..3f7165a5a0 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IntervalTreeTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTreeTest.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; +package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; import static org.assertj.core.api.Assertions.assertThat; @@ -11,8 +11,8 @@ import java.util.TreeSet; import java.util.stream.Collectors; -import ai.timefold.solver.core.api.score.stream.common.Break; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsage; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRange; +import ai.timefold.solver.core.api.score.stream.common.RangeGap; import org.junit.jupiter.api.Test; @@ -78,23 +78,23 @@ void testNonConsecutiveIntervals() { tree.add(c); var clusterList = - new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); + new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); assertThat(clusterList).hasSize(3); assertThat(clusterList.get(0)).containsExactly(new TestInterval(0, 2)); assertThat(clusterList.get(0).hasOverlap()).isFalse(); - assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(1); assertThat(clusterList.get(1)).containsExactly(new TestInterval(3, 4)); assertThat(clusterList.get(1).hasOverlap()).isFalse(); - assertThat(clusterList.get(1).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(1).getMaximumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(1).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(1).getMaximumOverlap()).isEqualTo(1); assertThat(clusterList.get(2)).containsExactly(new TestInterval(5, 7)); assertThat(clusterList.get(2).hasOverlap()).isFalse(); - assertThat(clusterList.get(2).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(2).getMaximumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(2).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(2).getMaximumOverlap()).isEqualTo(1); verifyBreaks(tree); } @@ -110,12 +110,12 @@ void testConsecutiveIntervals() { tree.add(c); var clusterList = - new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); + new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); assertThat(clusterList).hasSize(1); assertThat(clusterList.get(0)).containsExactly(new TestInterval(0, 2), new TestInterval(2, 4), new TestInterval(4, 7)); - assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(1); verifyBreaks(tree); } @@ -129,15 +129,15 @@ void testDuplicateIntervals() { tree.add(b); var clusterList = - new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); + new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); assertThat(clusterList).hasSize(2); assertThat(clusterList.get(0)).containsExactly(a.getValue(), a.getValue()); - assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(2); - assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(2); + assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(2); + assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(2); assertThat(clusterList.get(1)).containsExactly(b.getValue()); - assertThat(clusterList.get(1).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(1).getMaximumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(1).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(1).getMaximumOverlap()).isEqualTo(1); verifyBreaks(tree); } @@ -159,7 +159,7 @@ void testIntervalRemoval() { tree.remove(b); var clusterList = - new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); + new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); assertThat(clusterList).hasSize(2); assertThat(clusterList.get(0)).containsExactly(new TestInterval(0, 2)); @@ -214,23 +214,23 @@ void testOverlappingInterval() { tree.add(removedInterval2); var clusterList = - new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); + new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); assertThat(clusterList).hasSize(3); assertThat(clusterList.get(0)).containsExactly(a.getValue(), removedTestInterval1, c.getValue()); assertThat(clusterList.get(0).hasOverlap()).isTrue(); - assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(2); + assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(2); assertThat(clusterList.get(1)).containsExactly(d.getValue()); assertThat(clusterList.get(1).hasOverlap()).isFalse(); - assertThat(clusterList.get(1).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(1).getMaximumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(1).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(1).getMaximumOverlap()).isEqualTo(1); assertThat(clusterList.get(2)).containsExactly(e.getValue(), removedTestInterval2); assertThat(clusterList.get(2).hasOverlap()).isTrue(); - assertThat(clusterList.get(2).getMinimumConcurrentUsage()).isEqualTo(2); - assertThat(clusterList.get(2).getMaximumConcurrentUsage()).isEqualTo(2); + assertThat(clusterList.get(2).getMinimumOverlap()).isEqualTo(2); + assertThat(clusterList.get(2).getMaximumOverlap()).isEqualTo(2); verifyBreaks(tree); @@ -240,23 +240,23 @@ void testOverlappingInterval() { tree.remove(removedInterval1); - clusterList = new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); + clusterList = new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); assertThat(clusterList).hasSize(3); assertThat(clusterList.get(0)).containsExactly(a.getValue(), c.getValue()); assertThat(clusterList.get(0).hasOverlap()).isFalse(); - assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(1); assertThat(clusterList.get(1)).containsExactly(d.getValue()); assertThat(clusterList.get(1).hasOverlap()).isFalse(); - assertThat(clusterList.get(1).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(1).getMaximumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(1).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(1).getMaximumOverlap()).isEqualTo(1); assertThat(clusterList.get(2)).containsExactly(e.getValue(), removedTestInterval2); assertThat(clusterList.get(2).hasOverlap()).isTrue(); - assertThat(clusterList.get(2).getMinimumConcurrentUsage()).isEqualTo(2); - assertThat(clusterList.get(2).getMaximumConcurrentUsage()).isEqualTo(2); + assertThat(clusterList.get(2).getMinimumOverlap()).isEqualTo(2); + assertThat(clusterList.get(2).getMaximumOverlap()).isEqualTo(2); verifyBreaks(tree); @@ -265,68 +265,68 @@ void testOverlappingInterval() { removedTestInterval2.setEnd(4); tree.remove(removedInterval2); - clusterList = new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); + clusterList = new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); assertThat(clusterList).hasSize(3); assertThat(clusterList.get(0)).containsExactly(a.getValue(), c.getValue()); assertThat(clusterList.get(0).hasOverlap()).isFalse(); - assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(1); assertThat(clusterList.get(1)).containsExactly(d.getValue()); assertThat(clusterList.get(1).hasOverlap()).isFalse(); - assertThat(clusterList.get(1).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(1).getMaximumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(1).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(1).getMaximumOverlap()).isEqualTo(1); assertThat(clusterList.get(2)).containsExactly(e.getValue()); assertThat(clusterList.get(2).hasOverlap()).isFalse(); - assertThat(clusterList.get(2).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(2).getMaximumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(2).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(2).getMaximumOverlap()).isEqualTo(1); verifyBreaks(tree); Interval g = tree.getInterval(new TestInterval(6, 7)); tree.add(g); - clusterList = new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); + clusterList = new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); assertThat(clusterList).hasSize(2); assertThat(clusterList.get(0)).containsExactly(a.getValue(), c.getValue()); assertThat(clusterList.get(0).hasOverlap()).isFalse(); - assertThat(clusterList.get(0).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(0).getMaximumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(1); assertThat(clusterList.get(1)).containsExactly(d.getValue(), g.getValue(), e.getValue()); assertThat(clusterList.get(1).hasOverlap()).isFalse(); - assertThat(clusterList.get(1).getMinimumConcurrentUsage()).isEqualTo(1); - assertThat(clusterList.get(1).getMaximumConcurrentUsage()).isEqualTo(1); + assertThat(clusterList.get(1).getMinimumOverlap()).isEqualTo(1); + assertThat(clusterList.get(1).getMaximumOverlap()).isEqualTo(1); } void verifyBreaks(IntervalTree tree) { var clusterList = - new IterableList<>(tree.getConsecutiveIntervalData().getConcurrentUsages()); + new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); var breakList = - new IterableList<>(tree.getConsecutiveIntervalData().getBreaks()); + new IterableList<>(tree.getConnectedRangeChain().getGaps()); if (clusterList.size() == 0) { return; } assertThat(breakList).hasSize(clusterList.size() - 1); for (int i = 0; i < clusterList.size() - 1; i++) { - assertThat(breakList.get(i).getPreviousSequenceEnd()).isEqualTo(clusterList.get(i).getEnd()); - assertThat(breakList.get(i).getNextSequenceStart()).isEqualTo(clusterList.get(i + 1).getStart()); + assertThat(breakList.get(i).getPreviousRangeEnd()).isEqualTo(clusterList.get(i).getEnd()); + assertThat(breakList.get(i).getNextRangeStart()).isEqualTo(clusterList.get(i + 1).getStart()); assertThat(breakList.get(i).getLength()).isEqualTo(clusterList.get(i + 1).getStart() - clusterList.get(i).getEnd()); } } - private static int intervalBreakCompare(Break a, - Break b) { + private static int intervalBreakCompare(RangeGap a, + RangeGap b) { if (a == b) { return 0; } if (a == null || b == null) { return (a == null) ? -1 : 1; } - boolean out = Objects.equals(a.getPreviousSequenceEnd(), b.getPreviousSequenceEnd()) && - Objects.equals(a.getNextSequenceStart(), b.getNextSequenceStart()) && + boolean out = Objects.equals(a.getPreviousRangeEnd(), b.getPreviousRangeEnd()) && + Objects.equals(a.getNextRangeStart(), b.getNextRangeStart()) && Objects.equals(a.getLength(), b.getLength()); if (out) { @@ -335,8 +335,8 @@ private static int intervalBreakCompare(Break a, return a.hashCode() - b.hashCode(); } - private static int intervalClusterCompare(ConcurrentUsage a, - ConcurrentUsage b) { + private static int intervalClusterCompare(ConnectedRange a, + ConnectedRange b) { if (a == b) { return 0; } @@ -344,17 +344,17 @@ private static int intervalClusterCompare(ConcurrentUsage) a; - var second = (ConcurrentUsageImpl) b; + var first = (ConnectedRangeImpl) a; + var second = (ConnectedRangeImpl) b; boolean out = first.getStartSplitPoint().compareTo(second.getStartSplitPoint()) == 0 && first.getEndSplitPoint().compareTo(second.getEndSplitPoint()) == 0 && - first.getMinimumConcurrentUsage() == second.getMinimumConcurrentUsage() && - first.getMaximumConcurrentUsage() == second.getMaximumConcurrentUsage(); + first.getMinimumOverlap() == second.getMinimumOverlap() && + first.getMaximumOverlap() == second.getMaximumOverlap(); if (out) { return 0; } @@ -419,16 +419,16 @@ void testRandomIntervals() { // Recompute all interval clusters IntervalSplitPoint previous = null; IntervalSplitPoint current = splitPoints.isEmpty() ? null : splitPoints.first(); - List> intervalClusterList = new ArrayList<>(); - List> breakList = new ArrayList<>(); + List> intervalClusterList = new ArrayList<>(); + List> breakList = new ArrayList<>(); while (current != null) { - intervalClusterList.add(new ConcurrentUsageImpl<>(splitPoints, (a, b) -> a - b, current)); + intervalClusterList.add(new ConnectedRangeImpl<>(splitPoints, (a, b) -> a - b, current)); if (previous != null) { - ConcurrentUsageImpl before = + ConnectedRangeImpl before = intervalClusterList.get(intervalClusterList.size() - 2); - ConcurrentUsageImpl after = + ConnectedRangeImpl after = intervalClusterList.get(intervalClusterList.size() - 1); - breakList.add(new IntervalBreakImpl<>(before, after, after.getStart() - before.getEnd())); + breakList.add(new RangeGapImpl<>(before, after, after.getStart() - before.getEnd())); } previous = current; current = splitPoints.higher(intervalClusterList.get(intervalClusterList.size() - 1).getEndSplitPoint()); @@ -436,11 +436,11 @@ void testRandomIntervals() { // Verify the mutable version matches the recompute version verifyBreaks(tree); - assertThat(tree.getConsecutiveIntervalData().getConcurrentUsages()) + assertThat(tree.getConnectedRangeChain().getConnectedRanges()) .as(op + " interval " + interval + " to " + old) .usingElementComparator(IntervalTreeTest::intervalClusterCompare) .containsExactlyElementsOf(intervalClusterList); - assertThat(tree.getConsecutiveIntervalData().getBreaks()) + assertThat(tree.getConnectedRangeChain().getGaps()) .as(op + " interval " + interval + " to " + old) .usingElementComparator(IntervalTreeTest::intervalBreakCompare) .containsExactlyElementsOf(breakList); @@ -450,8 +450,8 @@ void testRandomIntervals() { private String formatIntervalTree(IntervalTree intervalTree) { List> listOfIntervalClusters = new ArrayList<>(); - for (ConcurrentUsage cluster : intervalTree.getConsecutiveIntervalData() - .getConcurrentUsages()) { + for (ConnectedRange cluster : intervalTree.getConnectedRangeChain() + .getConnectedRanges()) { List intervalsInCluster = new ArrayList<>(); for (TestInterval interval : cluster) { intervalsInCluster.add(interval); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IterableList.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IterableList.java similarity index 94% rename from core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IterableList.java rename to core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IterableList.java index 4e7ff1ddba..1527ab632e 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/concurrentUsage/IterableList.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IterableList.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.concurrentUsage; +package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; import java.util.Iterator; import java.util.Objects; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectorsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectorsTest.java index aa1d21b3a3..edc23c8974 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectorsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/quad/InnerQuadConstraintCollectorsTest.java @@ -30,7 +30,7 @@ import java.util.SortedSet; import ai.timefold.solver.core.api.score.stream.ConstraintCollectors; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.quad.QuadConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.AbstractConstraintCollectorsTest; import ai.timefold.solver.core.impl.util.Pair; @@ -1122,8 +1122,8 @@ public void toConsecutiveSequences() { @Override @Test public void consecutiveUsage() { - QuadConstraintCollector> collector = - ConstraintCollectors.concurrentUsage((a, b, c, d) -> new Interval(a, b), + QuadConstraintCollector> collector = + ConstraintCollectors.toConnectedRanges((a, b, c, d) -> new Interval(a, b), Interval::start, Interval::end, (a, b) -> b - a); var container = collector.supplier().get(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectorsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectorsTest.java index d736c63e5f..83b09f1e89 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectorsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/tri/InnerTriConstraintCollectorsTest.java @@ -30,7 +30,7 @@ import java.util.SortedSet; import ai.timefold.solver.core.api.score.stream.ConstraintCollectors; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.tri.TriConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.AbstractConstraintCollectorsTest; import ai.timefold.solver.core.impl.util.Pair; @@ -1075,8 +1075,8 @@ public void toConsecutiveSequences() { @Override @Test public void consecutiveUsage() { - TriConstraintCollector> collector = - ConstraintCollectors.concurrentUsage((a, b, c) -> new Interval(a, b), + TriConstraintCollector> collector = + ConstraintCollectors.toConnectedRanges((a, b, c) -> new Interval(a, b), Interval::start, Interval::end, (a, b) -> b - a); var container = collector.supplier().get(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectorsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectorsTest.java index 8773a5478d..58378d1054 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectorsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/uni/InnerUniConstraintCollectorsTest.java @@ -32,7 +32,7 @@ import ai.timefold.solver.core.api.function.QuadFunction; import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.score.stream.ConstraintCollectors; -import ai.timefold.solver.core.api.score.stream.common.ConcurrentUsageInfo; +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.uni.UniConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.AbstractConstraintCollectorsTest; import ai.timefold.solver.core.impl.util.Pair; @@ -990,8 +990,8 @@ public void toConsecutiveSequences() { @Override @Test public void consecutiveUsage() { - UniConstraintCollector> collector = - ConstraintCollectors.concurrentUsage( + UniConstraintCollector> collector = + ConstraintCollectors.toConnectedRanges( Interval::start, Interval::end, (a, b) -> b - a); var container = collector.supplier().get(); diff --git a/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc b/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc index 22d1ec13b0..64e8bfeb04 100644 --- a/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc +++ b/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc @@ -804,25 +804,25 @@ and `matches` contains 6 matches, then the `weight` penalty will be `6`. If the number of consecutive matches is `4`, then the sequence is not violating the league requirement, and we can filter it out. +[#collectorsConnectedRanges] +==== Connected ranges collectors -[#collectorsConcurrentUsage] -==== Concurrent usage collectors - -Certain constraints requires tracking concurrent usage of a resource. -If the maximum concurrent capacity of the resource is unknown at the time of writing the constraints, you can implement this pattern using the `ConstraintCollectors.concurrentUsage(...)` collector: +Certain constraints require tracking properties of connected ranges. +For instance, to ensure equipment allocated to several timeslots across the same connected range is not allocated over its capacity. +This can be achieved using the `ConstraintCollectors.toConnectedRanges(...)` collector: [source,java,options="nowrap"] ---- Constraint doNotOverAssignEquipment(ConstraintFactory constraintFactory) { return constraintFactory.forEach(Equipment.class) .join(Job.class, Joiners.equal(Equipment::getId, Job::getRequiredEquipmentId)) - .collect((equipment, job) -> equipment, ConstraintCollectors.concurrentUsage((equipment, job) -> job, + .collect((equipment, job) -> equipment, ConstraintCollectors.toConnectedRanges((equipment, job) -> job, Job::getStart, Job::getEnd, (a, b) -> b - a)) - .flattenLast(ConcurrentUsageInfo::getConcurrentUsages) - .filter((equipment, concurrentUsage) -> concurrentUsage.getMaximumConcurrentUsage() > equipment.getCapacity()) - .penalize(HardSoftScore.ONE_HARD, (equipment, concurrentUsage) -> concurrentUsage.getMaximumConcurrentUsage() - equipment.getCapacity()) + .flattenLast(ConnectedRangeChain::getConnectedRanges) + .filter((equipment, connectedRange) -> connectedRange.getMaximumOverlap() > equipment.getCapacity()) + .penalize(HardSoftScore.ONE_HARD, (equipment, connectedRange) -> connectedRange.getMaximumOverlap() - equipment.getCapacity()) .asConstraint("Concurrent equipment usage over capacity"); } ---- @@ -831,22 +831,22 @@ Let's take a closer look at the crucial part of this constraint: [source,java,options="nowrap"] ---- - .collect((equipment, job) -> equipment, ConstraintCollectors.concurrentUsage((equipment, job) -> job, + .collect((equipment, job) -> equipment, ConstraintCollectors.toConnectedRanges((equipment, job) -> job, Job::getStart, Job::getEnd, (a, b) -> b - a)) - .flattenLast(ConcurrentUsageInfo::getConcurrentUsages) - .filter((equipment, concurrentUsage) -> concurrentUsage.getMaximumConcurrentUsage() > equipment.getCapacity()) + .flattenLast((ConnectedRangeChain::getConnectedRanges) + .filter((equipment, connectedRange) -> connectedRange.getMaximumOverlap() > equipment.getCapacity()) ---- -The `groupBy()` building block groups all jobs by required equipment, and for every such pair it creates a `ConcurrentUsageInfo` instance. -Any overlapping jobs will be put into the same `ConcurrentUsage` cluster. +The `groupBy()` building block groups all jobs by required equipment, and for every such pair it creates a `ConnectedRangeChain` instance. +Any overlapping jobs will be put into the same `ConnectedRange` cluster. -The `ConcurrentUsageInfo` then has several useful methods to not only get the list of resource usage, but also to get any and all breaks between resource usage. +The `ConnectedRangeChain` then has several useful methods to not only get the list of resource usage, but also to get any and all gaps between resource usage. In this case, we are only interested in the resource usage, and we use <> to convert each to its own tuple. Finally, we use <> to calculate the violation amount for each concurrent usage. -For example, if the equipment has a `capacity` of `3`, and the `maximumConcurrentUsage` of the `resource` is `5`, then `violationAmount` will be `2`. +For example, if the equipment has a `capacity` of `3`, and the `maximumOverlap` of the `resource` is `5`, then `violationAmount` will be `2`. If the amount is `0`, then the equipment is not being used over its capacity and we can filter the tuple out. From 4482959e1c53732bf36e838cc97ed7f8b8f0f66f Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Mon, 22 Apr 2024 13:43:37 -0400 Subject: [PATCH 4/9] chore: Sonar issues --- .../connectedRanges/ConnectedRangeChainImpl.java | 3 +-- .../connectedRanges/ConnectedRangeImpl.java | 4 ++++ .../collector/connectedRanges/IntervalTree.java | 3 ++- .../collector/consecutive/ConsecutiveSetTree.java | 5 +++-- .../consecutive/ConsecutiveSetTreeTest.java | 14 +++++++------- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeChainImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeChainImpl.java index ee0debe6eb..b19584ae9d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeChainImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeChainImpl.java @@ -4,7 +4,6 @@ import java.util.NavigableSet; import java.util.Objects; import java.util.TreeMap; -import java.util.TreeSet; import java.util.function.BiFunction; import ai.timefold.solver.core.api.score.stream.common.ConnectedRange; @@ -19,7 +18,7 @@ public final class ConnectedRangeChainImpl, RangeGapImpl> clusterStartSplitPointToNextBreak; private final BiFunction differenceFunction; - public ConnectedRangeChainImpl(TreeSet> splitPointSet, + public ConnectedRangeChainImpl(NavigableSet> splitPointSet, BiFunction differenceFunction) { this.clusterStartSplitPointToCluster = new TreeMap<>(); this.clusterStartSplitPointToNextBreak = new TreeMap<>(); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeImpl.java index 5e356c37e6..a25fdd47d3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeImpl.java @@ -2,6 +2,7 @@ import java.util.Iterator; import java.util.NavigableSet; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.function.BiFunction; @@ -229,6 +230,9 @@ public boolean hasNext() { @Override public ConnectedRangeImpl next() { + if (current == null) { + throw new NoSuchElementException(); + } IntervalSplitPoint start = current; IntervalSplitPoint end; int activeIntervals = 0; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTree.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTree.java index aa5bfdf3e9..baa1aacb45 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTree.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTree.java @@ -1,6 +1,7 @@ package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; import java.util.Iterator; +import java.util.NavigableSet; import java.util.TreeSet; import java.util.function.BiFunction; import java.util.function.Function; @@ -11,7 +12,7 @@ public final class IntervalTree, Di private final Function startMapping; private final Function endMapping; - private final TreeSet> splitPointSet; + private final NavigableSet> splitPointSet; private final ConnectedRangeChainImpl consecutiveIntervalData; public IntervalTree(Function startMapping, diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTree.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTree.java index 7368b15a0f..ce7ecd8292 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTree.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTree.java @@ -8,6 +8,7 @@ import java.util.Objects; import java.util.TreeMap; import java.util.function.BiFunction; +import java.util.function.BinaryOperator; import ai.timefold.solver.core.api.score.stream.common.Break; import ai.timefold.solver.core.api.score.stream.common.Sequence; @@ -43,7 +44,7 @@ public final class ConsecutiveSetTree, private ComparableValue lastItem; public ConsecutiveSetTree(BiFunction differenceFunction, - BiFunction sumFunction, Difference_ maxDifference, + BinaryOperator sumFunction, Difference_ maxDifference, Difference_ zeroDifference) { this.differenceFunction = differenceFunction; this.sequenceLengthFunction = (first, last) -> sumFunction.apply(maxDifference, differenceFunction.apply(first, last)); @@ -352,7 +353,7 @@ public String toString() { '}'; } - private final static class ValueCount { + private static final class ValueCount { private final Value_ value; private int count; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTreeTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTreeTest.java index 50a5731542..c0e5c2698c 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTreeTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTreeTest.java @@ -124,17 +124,17 @@ void testDuplicateNumbers() { tree.remove(duplicateValue); assertThat(sequenceList).hasSize(1); assertThat(sequenceList.get(0).getCount()).isEqualTo(3); - assertThat(breakList).hasSize(0); + assertThat(breakList).isEmpty(); tree.remove(duplicateValue); assertThat(sequenceList).hasSize(1); assertThat(sequenceList.get(0).getCount()).isEqualTo(3); - assertThat(breakList).hasSize(0); + assertThat(breakList).isEmpty(); tree.remove(duplicateValue); assertThat(sequenceList).hasSize(1); assertThat(sequenceList.get(0).getCount()).isEqualTo(2); - assertThat(tree.getBreaks()).hasSize(0); + assertThat(tree.getBreaks()).isEmpty(); } @Test @@ -180,7 +180,7 @@ void testJoinOfTwoChains() { assertThat(sequenceList).hasSize(1); assertThat(sequenceList.get(0).getCount()).isEqualTo(8); - assertThat(tree.getBreaks()).hasSize(0); + assertThat(tree.getBreaks()).isEmpty(); } @Test @@ -241,7 +241,7 @@ void testChainRemoval() { assertThat(sequenceList).hasSize(1); assertThat(sequenceList.get(0).getCount()).isEqualTo(3); - assertThat(tree.getBreaks()).hasSize(0); + assertThat(tree.getBreaks()).isEmpty(); } @Test @@ -267,7 +267,7 @@ void testShorteningOfChain() { assertThat(sequenceList).hasSize(1); assertThat(sequenceList.get(0).getCount()).isEqualTo(6); - assertThat(tree.getBreaks()).hasSize(0); + assertThat(tree.getBreaks()).isEmpty(); // mimic changing planning variable start.set(3); @@ -275,7 +275,7 @@ void testShorteningOfChain() { tree.remove(start); assertThat(sequenceList).hasSize(1); assertThat(sequenceList.get(0).getCount()).isEqualTo(5); - assertThat(tree.getBreaks()).hasSize(0); + assertThat(tree.getBreaks()).isEmpty(); } @Test From 7276a4426609e38561c34c1c617a64d83f199fbb Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Mon, 22 Apr 2024 14:45:24 -0400 Subject: [PATCH 5/9] chore: Rename package --- .../impl/score/stream/collector/ConnectedRangesCalculator.java | 2 +- .../ConnectedRangeChainImpl.java | 2 +- .../ConnectedRangeImpl.java | 2 +- .../{connectedRanges => connected_ranges}/Interval.java | 2 +- .../IntervalSplitPoint.java | 2 +- .../{connectedRanges => connected_ranges}/IntervalTree.java | 2 +- .../IntervalTreeIterator.java | 2 +- .../{connectedRanges => connected_ranges}/RangeGapImpl.java | 2 +- .../{connectedRanges => connected_ranges}/TreeMultiSet.java | 2 +- .../stream/collector/AbstractConstraintCollectorsTest.java | 2 +- .../{connectedRanges => connected_ranges}/IntervalTreeTest.java | 2 +- .../{connectedRanges => connected_ranges}/IterableList.java | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{connectedRanges => connected_ranges}/ConnectedRangeChainImpl.java (99%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{connectedRanges => connected_ranges}/ConnectedRangeImpl.java (99%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{connectedRanges => connected_ranges}/Interval.java (99%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{connectedRanges => connected_ranges}/IntervalSplitPoint.java (99%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{connectedRanges => connected_ranges}/IntervalTree.java (99%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{connectedRanges => connected_ranges}/IntervalTreeIterator.java (98%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{connectedRanges => connected_ranges}/RangeGapImpl.java (99%) rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/{connectedRanges => connected_ranges}/TreeMultiSet.java (99%) rename core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/{connectedRanges => connected_ranges}/IntervalTreeTest.java (99%) rename core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/{connectedRanges => connected_ranges}/IterableList.java (99%) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConnectedRangesCalculator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConnectedRangesCalculator.java index 8da319802a..9d13a8185f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConnectedRangesCalculator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConnectedRangesCalculator.java @@ -4,7 +4,7 @@ import java.util.function.Function; import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; -import ai.timefold.solver.core.impl.score.stream.collector.connectedRanges.IntervalTree; +import ai.timefold.solver.core.impl.score.stream.collector.connected_ranges.IntervalTree; public final class ConnectedRangesCalculator, Difference_ extends Comparable> implements ObjectCalculator> { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeChainImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java similarity index 99% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeChainImpl.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java index b19584ae9d..fc97886c12 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeChainImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; import java.util.NavigableMap; import java.util.NavigableSet; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeImpl.java similarity index 99% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeImpl.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeImpl.java index a25fdd47d3..3dab9da2dd 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/ConnectedRangeImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeImpl.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; import java.util.Iterator; import java.util.NavigableSet; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/Interval.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/Interval.java similarity index 99% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/Interval.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/Interval.java index 50da2355a3..31712cfd8c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/Interval.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/Interval.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; import java.util.function.Function; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalSplitPoint.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalSplitPoint.java similarity index 99% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalSplitPoint.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalSplitPoint.java index af53f3d122..89ef22c371 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalSplitPoint.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalSplitPoint.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; import java.util.Comparator; import java.util.IdentityHashMap; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTree.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTree.java similarity index 99% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTree.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTree.java index baa1aacb45..18241baae1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTree.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTree.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; import java.util.Iterator; import java.util.NavigableSet; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTreeIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTreeIterator.java similarity index 98% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTreeIterator.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTreeIterator.java index d4aa6b5d49..0140b6e218 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTreeIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTreeIterator.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; import java.util.Iterator; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/RangeGapImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java similarity index 99% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/RangeGapImpl.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java index 6ff6a99b73..5e0a58e02a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/RangeGapImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; import ai.timefold.solver.core.api.score.stream.common.ConnectedRange; import ai.timefold.solver.core.api.score.stream.common.RangeGap; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/TreeMultiSet.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/TreeMultiSet.java similarity index 99% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/TreeMultiSet.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/TreeMultiSet.java index 8ad99051bf..bd5c03becd 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/TreeMultiSet.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/TreeMultiSet.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; import java.util.AbstractSet; import java.util.Collection; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java index c8030c9e51..42b32ce453 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java @@ -4,7 +4,7 @@ import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; -import ai.timefold.solver.core.impl.score.stream.collector.connectedRanges.IntervalTree; +import ai.timefold.solver.core.impl.score.stream.collector.connected_ranges.IntervalTree; import ai.timefold.solver.core.impl.score.stream.collector.consecutive.ConsecutiveSetTree; import org.junit.jupiter.api.Test; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTreeTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTreeTest.java similarity index 99% rename from core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTreeTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTreeTest.java index 3f7165a5a0..540f7ca2d1 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IntervalTreeTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTreeTest.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; import static org.assertj.core.api.Assertions.assertThat; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IterableList.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IterableList.java similarity index 99% rename from core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IterableList.java rename to core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IterableList.java index 1527ab632e..1ec96b6dfa 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connectedRanges/IterableList.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IterableList.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.score.stream.collector.connectedRanges; +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; import java.util.Iterator; import java.util.Objects; From dd503eaa065480d6c8244e9a982bc2d9e430b81d Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Tue, 23 Apr 2024 13:24:12 -0400 Subject: [PATCH 6/9] chore: Update docs, rename methods + parameters --- .../score/stream/ConstraintCollectors.java | 137 ++++++++++-------- .../score/stream/common/ConnectedRange.java | 49 +++++++ .../stream/common/ConnectedRangeChain.java | 9 +- .../consecutive/ConsecutiveSetTree.java | 6 +- 4 files changed, 134 insertions(+), 67 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java index 03f39aa8b3..97d69c7a93 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java @@ -1963,23 +1963,24 @@ public static UniConstraintCollector * {@code - * ConcurrentUsage: [minConcurrentUsage: 1, maxConcurrentUsage: 2, - * [Equipment from=2, to=4] [Equipment from=3, to=5]], + * ConnectedRanges: [minOverlap: 1, maxOverlap: 2, + * [Equipment fromInclusive=2, toExclusive=4] [Equipment fromInclusive=3, toExclusive=5]], * [minConcurrentUsage: 1, maxConcurrentUsage: 1, - * [Equipment from=6, to=7] [Equipment from=7, to=8]] + * [Equipment fromInclusive=6, toExclusive=7] [Equipment fromInclusive=7, toExclusive=8]] * Breaks: [[Break from=5, to=6, length=1]] * } * * * This can be used to ensure a limited resource is not over-assigned. * - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end + * @param startInclusiveMap Maps the fact to its start + * @param endExclusiveMap Maps the fact to its end * @param differenceFunction Computes the difference between two points. The second argument is always * larger than the first (ex: {@link Duration#between} * or {@code (a,b) -> b - a}). @@ -1990,9 +1991,10 @@ public static UniConstraintCollector, DifferenceType_ extends Comparable> UniConstraintCollector> - toConnectedRanges(Function startMap, Function endMap, + toConnectedRanges(Function startInclusiveMap, Function endExclusiveMap, BiFunction differenceFunction) { - return InnerUniConstraintCollectors.toConnectedRanges(ConstantLambdaUtils.identity(), startMap, endMap, + return InnerUniConstraintCollectors.toConnectedRanges(ConstantLambdaUtils.identity(), startInclusiveMap, + endExclusiveMap, differenceFunction); } @@ -2002,35 +2004,35 @@ public static UniConstraintCollector type of the first mapped fact * @param temporal type of the endpoints - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end + * @param startInclusiveMap Maps the fact to its start + * @param endExclusiveMap Maps the fact to its end * @return never null */ public static > UniConstraintCollector> - toConnectedRangesByTime(Function startMap, Function endMap) { - return toConnectedRanges(startMap, endMap, Duration::between); + toConnectedTemporalRanges(Function startInclusiveMap, Function endExclusiveMap) { + return toConnectedRanges(startInclusiveMap, endExclusiveMap, Duration::between); } /** * Specialized version of {@link #toConnectedRanges(Function,Function,BiFunction)} for Long. * - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end + * @param startInclusiveMap Maps the fact to its start + * @param endExclusiveMap Maps the fact to its end * @param type of the first mapped fact * @return never null */ public static UniConstraintCollector> - toConnectedRanges(ToLongFunction startMap, ToLongFunction endMap) { - return toConnectedRanges(startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); + toConnectedRanges(ToLongFunction startInclusiveMap, ToLongFunction endExclusiveMap) { + return toConnectedRanges(startInclusiveMap::applyAsLong, endExclusiveMap::applyAsLong, (a, b) -> b - a); } /** * As defined by {@link #toConnectedRanges(Function,Function,BiFunction)}. * * @param intervalMap Maps both facts to an item in the cluster - * @param startMap Maps the item to its start - * @param endMap Maps the item to its end + * @param startInclusiveMap Maps the item to its start + * @param endExclusiveMap Maps the item to its end * @param differenceFunction Computes the difference between two points. The second argument is always * larger than the first (ex: {@link Duration#between} * or {@code (a,b) -> b - a}). @@ -2043,18 +2045,20 @@ public static UniConstraintCollector, DifferenceType_ extends Comparable> BiConstraintCollector> - toConnectedRanges(BiFunction intervalMap, Function startMap, - Function endMap, + toConnectedRanges(BiFunction intervalMap, + Function startInclusiveMap, + Function endExclusiveMap, BiFunction differenceFunction) { - return InnerBiConstraintCollectors.toConnectedRanges(intervalMap, startMap, endMap, differenceFunction); + return InnerBiConstraintCollectors.toConnectedRanges(intervalMap, startInclusiveMap, endExclusiveMap, + differenceFunction); } /** - * As defined by {@link #toConnectedRangesByTime(Function,Function)}. + * As defined by {@link #toConnectedTemporalRanges(Function,Function)}. * * @param intervalMap Maps the three facts to an item in the cluster - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end + * @param startInclusiveMap Maps the fact to its start + * @param endExclusiveMap Maps the fact to its end * @param type of the first mapped fact * @param type of the second mapped fact * @param type of the item in the cluster @@ -2063,16 +2067,17 @@ public static UniConstraintCollector> BiConstraintCollector> - toConnectedRangesByTime(BiFunction intervalMap, - Function startMap, Function endMap) { - return toConnectedRanges(intervalMap, startMap, endMap, Duration::between); + toConnectedTemporalRanges(BiFunction intervalMap, + Function startInclusiveMap, + Function endExclusiveMap) { + return toConnectedRanges(intervalMap, startInclusiveMap, endExclusiveMap, Duration::between); } /** * As defined by {@link #toConnectedRanges(ToLongFunction, ToLongFunction)}. * - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end + * @param startInclusiveMap Maps the fact to its start + * @param endExclusiveMap Maps the fact to its end * @param type of the first mapped fact * @param type of the second mapped fact * @param type of the item in the cluster @@ -2080,17 +2085,17 @@ public static UniConstraintCollector BiConstraintCollector> - toConnectedRanges(BiFunction intervalMap, ToLongFunction startMap, - ToLongFunction endMap) { - return toConnectedRanges(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); + toConnectedRanges(BiFunction intervalMap, ToLongFunction startInclusiveMap, + ToLongFunction endExclusiveMap) { + return toConnectedRanges(intervalMap, startInclusiveMap::applyAsLong, endExclusiveMap::applyAsLong, (a, b) -> b - a); } /** * As defined by {@link #toConnectedRanges(Function,Function,BiFunction)}. * * @param intervalMap Maps the three facts to an item in the cluster - * @param startMap Maps the item to its start - * @param endMap Maps the item to its end + * @param startInclusiveMap Maps the item to its start + * @param endExclusiveMap Maps the item to its end * @param differenceFunction Computes the difference between two points. The second argument is always * larger than the first (ex: {@link Duration#between} * or {@code (a,b) -> b - a}). @@ -2104,18 +2109,20 @@ public static UniConstraintCollector, DifferenceType_ extends Comparable> TriConstraintCollector> - toConnectedRanges(TriFunction intervalMap, Function startMap, - Function endMap, + toConnectedRanges(TriFunction intervalMap, + Function startInclusiveMap, + Function endExclusiveMap, BiFunction differenceFunction) { - return InnerTriConstraintCollectors.toConnectedRanges(intervalMap, startMap, endMap, differenceFunction); + return InnerTriConstraintCollectors.toConnectedRanges(intervalMap, startInclusiveMap, endExclusiveMap, + differenceFunction); } /** - * As defined by {@link #toConnectedRangesByTime(Function,Function)}. + * As defined by {@link #toConnectedTemporalRanges(Function,Function)}. * * @param intervalMap Maps the three facts to an item in the cluster - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end + * @param startInclusiveMap Maps the fact to its start + * @param endExclusiveMap Maps the fact to its end * @param type of the first mapped fact * @param type of the second mapped fact * @param type of the third mapped fact @@ -2125,16 +2132,17 @@ public static UniConstraintCollector> TriConstraintCollector> - toConnectedRangesByTime(TriFunction intervalMap, - Function startMap, Function endMap) { - return toConnectedRanges(intervalMap, startMap, endMap, Duration::between); + toConnectedTemporalRanges(TriFunction intervalMap, + Function startInclusiveMap, + Function endExclusiveMap) { + return toConnectedRanges(intervalMap, startInclusiveMap, endExclusiveMap, Duration::between); } /** * As defined by {@link #toConnectedRanges(ToLongFunction, ToLongFunction)}. * - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end + * @param startInclusiveMap Maps the fact to its start + * @param endExclusiveMap Maps the fact to its end * @param type of the first mapped fact * @param type of the second mapped fact * @param type of the third mapped fact @@ -2143,17 +2151,17 @@ public static UniConstraintCollector TriConstraintCollector> - toConnectedRanges(TriFunction intervalMap, ToLongFunction startMap, - ToLongFunction endMap) { - return toConnectedRanges(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); + toConnectedRanges(TriFunction intervalMap, ToLongFunction startInclusiveMap, + ToLongFunction endExclusiveMap) { + return toConnectedRanges(intervalMap, startInclusiveMap::applyAsLong, endExclusiveMap::applyAsLong, (a, b) -> b - a); } /** * As defined by {@link #toConnectedRanges(Function,Function,BiFunction)}. * * @param intervalMap Maps the four facts to an item in the cluster - * @param startMap Maps the item to its start - * @param endMap Maps the item to its end + * @param startInclusiveMap Maps the item to its start + * @param endExclusiveMap Maps the item to its end * @param differenceFunction Computes the difference between two points. The second argument is always * larger than the first (ex: {@link Duration#between} * or {@code (a,b) -> b - a}). @@ -2169,17 +2177,18 @@ public static UniConstraintCollector, DifferenceType_ extends Comparable> QuadConstraintCollector> toConnectedRanges(QuadFunction intervalMap, - Function startMap, Function endMap, + Function startInclusiveMap, Function endExclusiveMap, BiFunction differenceFunction) { - return InnerQuadConstraintCollectors.toConnectedRanges(intervalMap, startMap, endMap, differenceFunction); + return InnerQuadConstraintCollectors.toConnectedRanges(intervalMap, startInclusiveMap, endExclusiveMap, + differenceFunction); } /** - * As defined by {@link #toConnectedRangesByTime(Function,Function)}. + * As defined by {@link #toConnectedTemporalRanges(Function,Function)}. * * @param intervalMap Maps the three facts to an item in the cluster - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end + * @param startInclusiveMap Maps the fact to its start + * @param endExclusiveMap Maps the fact to its end * @param type of the first mapped fact * @param type of the second mapped fact * @param type of the third mapped fact @@ -2190,16 +2199,17 @@ public static UniConstraintCollector> QuadConstraintCollector> - toConnectedRangesByTime(QuadFunction intervalMap, - Function startMap, Function endMap) { - return toConnectedRanges(intervalMap, startMap, endMap, Duration::between); + toConnectedTemporalRanges(QuadFunction intervalMap, + Function startInclusiveMap, + Function endExclusiveMap) { + return toConnectedRanges(intervalMap, startInclusiveMap, endExclusiveMap, Duration::between); } /** * As defined by {@link #toConnectedRanges(ToLongFunction, ToLongFunction)}. * - * @param startMap Maps the fact to its start - * @param endMap Maps the fact to its end + * @param startInclusiveMap Maps the fact to its start + * @param endExclusiveMap Maps the fact to its end * @param type of the first mapped fact * @param type of the second mapped fact * @param type of the third mapped fact @@ -2209,9 +2219,10 @@ public static UniConstraintCollector QuadConstraintCollector> - toConnectedRanges(QuadFunction intervalMap, ToLongFunction startMap, - ToLongFunction endMap) { - return toConnectedRanges(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a); + toConnectedRanges(QuadFunction intervalMap, + ToLongFunction startInclusiveMap, + ToLongFunction endExclusiveMap) { + return toConnectedRanges(intervalMap, startInclusiveMap::applyAsLong, endExclusiveMap::applyAsLong, (a, b) -> b - a); } private ConstraintCollectors() { diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRange.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRange.java index de58a3c6bc..d348abe7af 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRange.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRange.java @@ -1,18 +1,67 @@ package ai.timefold.solver.core.api.score.stream.common; +/** + * Represents a collection of ranges that are connected, meaning + * the union of all the ranges results in the range + * [{@link #getStart()}, {@link #getEnd()}) without gaps. + * + * @param The type of range in the collection. + * @param The type of the start and end points for each range. + * @param The type of difference between start and end points. + */ public interface ConnectedRange, Difference_ extends Comparable> extends Iterable { + /** + * Get the number of ranges contained by this {@link ConnectedRange}. + * + * @return never null, the number of ranges contained by this {@link ConnectedRange}. + */ int getContainedRangeCount(); + /** + * True if this {@link ConnectedRange} has at least one pair of + * ranges that overlaps each other, false otherwise. + * + * @return never null, true iff there at least one pair of overlapping ranges in this {@link ConnectedRange}. + */ boolean hasOverlap(); + /** + * Get the minimum number of overlapping ranges for any point contained by + * this {@link ConnectedRange}. + * + * @return never null, the minimum number of overlapping ranges for any point + * in this {@link ConnectedRange}. + */ int getMinimumOverlap(); + /** + * Get the maximum number of overlapping ranges for any point contained by + * this {@link ConnectedRange}. + * + * @return never null, the maximum number of overlapping ranges for any point + * in this {@link ConnectedRange}. + */ int getMaximumOverlap(); + /** + * Get the length of this {@link ConnectedRange}. + * + * @return The difference between {@link #getEnd()} and {@link #getStart()}. + */ Difference_ getLength(); + /** + * Gets the first start point represented by this {@link ConnectedRange}. + * + * @return never null, the first start point represented by this {@link ConnectedRange}. + */ Point_ getStart(); + /** + * Gets the last end point represented by this {@link ConnectedRange}. + * + * @return never null, the last end point represented by this {@link ConnectedRange}. + */ Point_ getEnd(); } diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRangeChain.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRangeChain.java index c1e6495a56..0066b5027a 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRangeChain.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRangeChain.java @@ -1,5 +1,12 @@ package ai.timefold.solver.core.api.score.stream.common; +/** + * Contains info regarding connected ranges and gaps for a collection of ranges. + * + * @param The type of range in the collection. + * @param The type of the start and end points for each range. + * @param The type of difference between start and end points. + */ public interface ConnectedRangeChain, Difference_ extends Comparable> { /** @@ -9,7 +16,7 @@ public interface ConnectedRangeChain> getConnectedRanges(); /** - * @return never null, an iterable that iterates through the breaks contained in + * @return never null, an iterable that iterates through the gaps contained in * the collection in ascending order of their start points */ Iterable> getGaps(); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTree.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTree.java index ce7ecd8292..c171f500fb 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTree.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/consecutive/ConsecutiveSetTree.java @@ -274,11 +274,11 @@ private void removeItemFromBag(SequenceImpl bag, Co bag.setStart(itemMap.higherKey(item)); startItemToSequence.remove(sequenceStart); var extendedBreak = startItemToPreviousBreak.remove(sequenceStart); - var firstItem = bag.firstItem; - startItemToSequence.put(firstItem, bag); + var bagFirstItem = bag.firstItem; + startItemToSequence.put(bagFirstItem, bag); if (extendedBreak != null) { extendedBreak.updateLength(); - startItemToPreviousBreak.put(firstItem, extendedBreak); + startItemToPreviousBreak.put(bagFirstItem, extendedBreak); } return; } From c9603024be0c38f99f91146f2937c9ea05fff925 Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Tue, 23 Apr 2024 17:10:26 -0400 Subject: [PATCH 7/9] chore: Rename methods/classes, move iterator outside class --- .../ConnectedRangeChainImpl.java | 21 ++++-- .../connected_ranges/ConnectedRangeImpl.java | 69 ++--------------- .../ConnectedSubrangeIterator.java | 75 +++++++++++++++++++ ...rator.java => ContainedRangeIterator.java} | 4 +- .../connected_ranges/IntervalTree.java | 2 +- .../connected_ranges/RangeGapImpl.java | 4 +- 6 files changed, 99 insertions(+), 76 deletions(-) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedSubrangeIterator.java rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/{IntervalTreeIterator.java => ContainedRangeIterator.java} (83%) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java index fc97886c12..b9efa904ad 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java @@ -60,7 +60,7 @@ void addInterval(Interval interval) { // Merge all the intersected interval clusters into the first intersected // interval cluster intersectedIntervalClusterMap.tailMap(oldStartSplitPoint, false).values() - .forEach(firstIntersectedIntervalCluster::mergeIntervalCluster); + .forEach(firstIntersectedIntervalCluster::mergeConnectedRange); // Remove all the intersected interval clusters after the first intersected // one, since they are now merged in the first @@ -127,10 +127,10 @@ private void removeSpannedBreaksAndUpdateIntersectedBreaks(Interval) (intersectedIntervalBreakMap.firstEntry().getValue() - .getPreviousConcurrentUsage()); + .getPreviousConnectedRange()); var clusterAfterFinalIntersectedBreak = (ConnectedRangeImpl) (intersectedIntervalBreakMap.lastEntry().getValue() - .getNextConcurrentUsage()); + .getNextConnectedRange()); // All breaks that are not the first or last intersected breaks will // be removed (as interval span them) @@ -168,7 +168,7 @@ private void removeSpannedBreaksAndUpdateIntersectedBreaks(Interval) (previousBreak - .getPreviousConcurrentUsage())).getStartSplitPoint(), previousBreak); + .getPreviousConnectedRange())).getStartSplitPoint(), previousBreak); } else { // Case: interval does not span either the first or final break // Ex: @@ -200,17 +200,22 @@ void removeInterval(Interval interval) { var previousBreak = (previousBreakEntry != null) ? previousBreakEntry.getValue() : null; var previousIntervalCluster = (previousBreak != null) - ? (ConnectedRangeImpl) previousBreak.getPreviousConcurrentUsage() + ? (ConnectedRangeImpl) previousBreak.getPreviousConnectedRange() : null; - for (var newIntervalCluster : intervalCluster.removeInterval(interval)) { + var iterator = new ConnectedSubrangeIterator<>(splitPointSet, + intervalCluster.getStartSplitPoint(), + intervalCluster.getEndSplitPoint(), + differenceFunction); + while (iterator.hasNext()) { + var newIntervalCluster = iterator.next(); if (previousBreak != null) { previousBreak.setNextCluster(newIntervalCluster); - previousBreak.setLength(differenceFunction.apply(previousBreak.getPreviousConcurrentUsage().getEnd(), + previousBreak.setLength(differenceFunction.apply(previousBreak.getPreviousConnectedRange().getEnd(), newIntervalCluster.getStart())); clusterStartSplitPointToNextBreak .put(((ConnectedRangeImpl) previousBreak - .getPreviousConcurrentUsage()).getStartSplitPoint(), previousBreak); + .getPreviousConnectedRange()).getStartSplitPoint(), previousBreak); } previousBreak = new RangeGapImpl<>(newIntervalCluster, null, null); previousIntervalCluster = newIntervalCluster; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeImpl.java index 3dab9da2dd..84e6b335d8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeImpl.java @@ -2,7 +2,6 @@ import java.util.Iterator; import java.util.NavigableSet; -import java.util.NoSuchElementException; import java.util.Objects; import java.util.function.BiFunction; @@ -96,11 +95,12 @@ void addInterval(Interval interval) { count++; } - Iterable> removeInterval(Interval interval) { - return IntervalClusterIterator::new; + Iterable> getNewConnectedRanges( + final NavigableSet> newSplitPointSet) { + return () -> new ConnectedSubrangeIterator<>(newSplitPointSet, startSplitPoint, endSplitPoint, differenceFunction); } - void mergeIntervalCluster(ConnectedRangeImpl laterIntervalCluster) { + void mergeConnectedRange(ConnectedRangeImpl laterIntervalCluster) { if (endSplitPoint.compareTo(laterIntervalCluster.startSplitPoint) > 0) { hasOverlap = true; } @@ -115,7 +115,7 @@ void mergeIntervalCluster(ConnectedRangeImpl lat @Override public Iterator iterator() { - return new IntervalTreeIterator<>(splitPointSet.subSet(startSplitPoint, true, endSplitPoint, true)); + return new ContainedRangeIterator<>(splitPointSet.subSet(startSplitPoint, true, endSplitPoint, true)); } @Override @@ -198,7 +198,7 @@ public int hashCode() { @Override public String toString() { - return "ConcurrentUsage {" + + return "ConnectedRange {" + "start=" + startSplitPoint + ", end=" + endSplitPoint + ", count=" + count + @@ -209,61 +209,4 @@ public String toString() { '}'; } - // TODO: Make this incremental by only checking between the interval's start and end points - private final class IntervalClusterIterator - implements Iterator> { - - private IntervalSplitPoint current = getStart(startSplitPoint); - - private IntervalSplitPoint - getStart(IntervalSplitPoint start) { - while (start != null && start.isEmpty()) { - start = splitPointSet.higher(start); - } - return start; - } - - @Override - public boolean hasNext() { - return current != null && current.compareTo(endSplitPoint) <= 0 && !splitPointSet.isEmpty(); - } - - @Override - public ConnectedRangeImpl next() { - if (current == null) { - throw new NoSuchElementException(); - } - IntervalSplitPoint start = current; - IntervalSplitPoint end; - int activeIntervals = 0; - minimumOverlap = Integer.MAX_VALUE; - maximumOverlap = Integer.MIN_VALUE; - count = 0; - boolean anyOverlap = false; - do { - count += current.intervalsStartingAtSplitPointSet.size(); - activeIntervals += - current.intervalsStartingAtSplitPointSet.size() - current.intervalsEndingAtSplitPointSet.size(); - if (activeIntervals > 0) { - minimumOverlap = Math.min(minimumOverlap, activeIntervals); - maximumOverlap = Math.max(maximumOverlap, activeIntervals); - if (activeIntervals > 1) { - anyOverlap = true; - } - } - current = splitPointSet.higher(current); - } while (activeIntervals > 0 && current != null); - - if (current != null) { - end = splitPointSet.lower(current); - current = getStart(current); - } else { - end = splitPointSet.last(); - } - hasOverlap = anyOverlap; - - return new ConnectedRangeImpl<>(splitPointSet, differenceFunction, start, end, count, - minimumOverlap, maximumOverlap, hasOverlap); - } - } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedSubrangeIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedSubrangeIterator.java new file mode 100644 index 0000000000..232b8358c6 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedSubrangeIterator.java @@ -0,0 +1,75 @@ +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; + +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.function.BiFunction; + +final class ConnectedSubrangeIterator, Difference_ extends Comparable> + implements Iterator> { + // TODO: Make this incremental by only checking between the interval's start and end points + private final NavigableSet> splitPointSet; + private final BiFunction differenceFunction; + private final IntervalSplitPoint endSplitPoint; + private IntervalSplitPoint current; + + public ConnectedSubrangeIterator(NavigableSet> splitPointSet, + IntervalSplitPoint startSplitPoint, + IntervalSplitPoint endSplitPoint, + BiFunction differenceFunction) { + this.splitPointSet = splitPointSet; + this.current = getStart(startSplitPoint); + this.endSplitPoint = endSplitPoint; + this.differenceFunction = differenceFunction; + } + + private IntervalSplitPoint + getStart(IntervalSplitPoint start) { + while (start != null && start.isEmpty()) { + start = splitPointSet.higher(start); + } + return start; + } + + @Override + public boolean hasNext() { + return current != null && current.compareTo(endSplitPoint) <= 0 && !splitPointSet.isEmpty(); + } + + @Override + public ConnectedRangeImpl next() { + if (current == null) { + throw new NoSuchElementException(); + } + IntervalSplitPoint start = current; + IntervalSplitPoint end; + int activeIntervals = 0; + int minimumOverlap = Integer.MAX_VALUE; + int maximumOverlap = Integer.MIN_VALUE; + int count = 0; + boolean anyOverlap = false; + do { + count += current.intervalsStartingAtSplitPointSet.size(); + activeIntervals += + current.intervalsStartingAtSplitPointSet.size() - current.intervalsEndingAtSplitPointSet.size(); + if (activeIntervals > 0) { + minimumOverlap = Math.min(minimumOverlap, activeIntervals); + maximumOverlap = Math.max(maximumOverlap, activeIntervals); + if (activeIntervals > 1) { + anyOverlap = true; + } + } + current = splitPointSet.higher(current); + } while (activeIntervals > 0 && current != null); + + if (current != null) { + end = splitPointSet.lower(current); + current = getStart(current); + } else { + end = splitPointSet.last(); + } + + return new ConnectedRangeImpl<>(splitPointSet, differenceFunction, start, end, count, + minimumOverlap, maximumOverlap, anyOverlap); + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTreeIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ContainedRangeIterator.java similarity index 83% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTreeIterator.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ContainedRangeIterator.java index 0140b6e218..1bab9282f9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTreeIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ContainedRangeIterator.java @@ -2,12 +2,12 @@ import java.util.Iterator; -final class IntervalTreeIterator> implements Iterator { +final class ContainedRangeIterator> implements Iterator { private final Iterator> splitPointSetIterator; private Iterator splitPointValueIterator; - IntervalTreeIterator(Iterable> splitPointSet) { + ContainedRangeIterator(Iterable> splitPointSet) { this.splitPointSetIterator = splitPointSet.iterator(); if (splitPointSetIterator.hasNext()) { splitPointValueIterator = splitPointSetIterator.next().getValuesStartingFromSplitPointIterator(); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTree.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTree.java index 18241baae1..bbd0f3c45a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTree.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTree.java @@ -45,7 +45,7 @@ public boolean contains(Interval_ o) { } public Iterator iterator() { - return new IntervalTreeIterator<>(splitPointSet); + return new ContainedRangeIterator<>(splitPointSet); } public boolean add(Interval interval) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java index 5e0a58e02a..5096d7fc99 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java @@ -16,11 +16,11 @@ final class RangeGapImpl, Differenc this.length = length; } - public ConnectedRange getPreviousConcurrentUsage() { + ConnectedRange getPreviousConnectedRange() { return previousCluster; } - public ConnectedRange getNextConcurrentUsage() { + ConnectedRange getNextConnectedRange() { return nextCluster; } From 52a40514e08eaa76d29529ed53199341cb985509 Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Wed, 24 Apr 2024 10:46:17 -0400 Subject: [PATCH 8/9] chore: Rename terms to use ranges and gaps - Also replaced a constructor of ConnectedRange with a static method that uses ConnectedSubrangeIterator --- .../score/stream/common/ConnectedRange.java | 6 +- .../stream/common/ConnectedRangeChain.java | 12 +- .../api/score/stream/common/RangeGap.java | 4 +- .../collector/ConnectedRangesCalculator.java | 10 +- .../ConnectedRangeChainImpl.java | 327 ++++++------ .../connected_ranges/ConnectedRangeImpl.java | 108 ++-- .../ConnectedRangeTracker.java | 104 ++++ .../ConnectedSubrangeIterator.java | 46 +- .../ContainedRangeIterator.java | 10 +- .../connected_ranges/IntervalSplitPoint.java | 116 ----- .../connected_ranges/IntervalTree.java | 104 ---- .../{Interval.java => Range.java} | 26 +- .../connected_ranges/RangeGapImpl.java | 18 +- .../connected_ranges/RangeSplitPoint.java | 116 +++++ .../AbstractConstraintCollectorsTest.java | 6 +- .../ConnectedRangeTrackerTest.java | 468 ++++++++++++++++++ .../connected_ranges/IntervalTreeTest.java | 466 ----------------- 17 files changed, 961 insertions(+), 986 deletions(-) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeTracker.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalSplitPoint.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTree.java rename core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/{Interval.java => Range.java} (58%) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeSplitPoint.java create mode 100644 core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeTrackerTest.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTreeTest.java diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRange.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRange.java index d348abe7af..b83bb408ff 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRange.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRange.java @@ -5,12 +5,12 @@ * the union of all the ranges results in the range * [{@link #getStart()}, {@link #getEnd()}) without gaps. * - * @param The type of range in the collection. + * @param The type of range in the collection. * @param The type of the start and end points for each range. * @param The type of difference between start and end points. */ -public interface ConnectedRange, Difference_ extends Comparable> - extends Iterable { +public interface ConnectedRange, Difference_ extends Comparable> + extends Iterable { /** * Get the number of ranges contained by this {@link ConnectedRange}. * diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRangeChain.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRangeChain.java index 0066b5027a..55a6c4555a 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRangeChain.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/ConnectedRangeChain.java @@ -1,22 +1,22 @@ package ai.timefold.solver.core.api.score.stream.common; /** - * Contains info regarding connected ranges and gaps for a collection of ranges. + * Contains info regarding {@link ConnectedRange}s and {@link RangeGap}s for a collection of ranges. * - * @param The type of range in the collection. + * @param The type of range in the collection. * @param The type of the start and end points for each range. * @param The type of difference between start and end points. */ -public interface ConnectedRangeChain, Difference_ extends Comparable> { +public interface ConnectedRangeChain, Difference_ extends Comparable> { /** - * @return never null, an iterable that iterates through the connected ranges + * @return never null, an iterable that iterates through the {@link ConnectedRange}s * contained in the collection in ascending order of their start points */ - Iterable> getConnectedRanges(); + Iterable> getConnectedRanges(); /** - * @return never null, an iterable that iterates through the gaps contained in + * @return never null, an iterable that iterates through the {@link RangeGap}s contained in * the collection in ascending order of their start points */ Iterable> getGaps(); diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/RangeGap.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/RangeGap.java index e6fa86d5df..e84a8ce4b9 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/RangeGap.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/common/RangeGap.java @@ -9,7 +9,7 @@ */ public interface RangeGap, Difference_ extends Comparable> { /** - * Return the end of the sequence before this gap. + * Return the end of the {@link ConnectedRange} before this gap. * For the gap between 6 and 10, this will return 6. * * @return never null, the item this gap is directly after @@ -17,7 +17,7 @@ public interface RangeGap, Difference_ extends Point_ getPreviousRangeEnd(); /** - * Return the start of the sequence after this gap. + * Return the start of the {@link ConnectedRange} after this gap. * For the gap between 6 and 10, this will return 10. * * @return never null, the item this gap is directly before diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConnectedRangesCalculator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConnectedRangesCalculator.java index 9d13a8185f..c49e11f76f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConnectedRangesCalculator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ConnectedRangesCalculator.java @@ -4,17 +4,17 @@ import java.util.function.Function; import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; -import ai.timefold.solver.core.impl.score.stream.collector.connected_ranges.IntervalTree; +import ai.timefold.solver.core.impl.score.stream.collector.connected_ranges.ConnectedRangeTracker; public final class ConnectedRangesCalculator, Difference_ extends Comparable> implements ObjectCalculator> { - private final IntervalTree context; + private final ConnectedRangeTracker context; public ConnectedRangesCalculator(Function startMap, Function endMap, BiFunction differenceFunction) { - this.context = new IntervalTree<>( + this.context = new ConnectedRangeTracker<>( startMap, endMap, differenceFunction); @@ -22,12 +22,12 @@ public ConnectedRangesCalculator(Function s @Override public void insert(Interval_ result) { - context.add(context.getInterval(result)); + context.add(context.getRange(result)); } @Override public void retract(Interval_ result) { - context.remove(context.getInterval(result)); + context.remove(context.getRange(result)); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java index b9efa904ad..0fb00196f2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java @@ -10,242 +10,243 @@ import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.RangeGap; -public final class ConnectedRangeChainImpl, Difference_ extends Comparable> - implements ConnectedRangeChain { +public final class ConnectedRangeChainImpl, Difference_ extends Comparable> + implements ConnectedRangeChain { - private final NavigableMap, ConnectedRangeImpl> clusterStartSplitPointToCluster; - private final NavigableSet> splitPointSet; - private final NavigableMap, RangeGapImpl> clusterStartSplitPointToNextBreak; + private final NavigableMap, ConnectedRangeImpl> startSplitPointToConnectedRange; + private final NavigableSet> splitPointSet; + private final NavigableMap, RangeGapImpl> startSplitPointToNextGap; private final BiFunction differenceFunction; - public ConnectedRangeChainImpl(NavigableSet> splitPointSet, + public ConnectedRangeChainImpl(NavigableSet> splitPointSet, BiFunction differenceFunction) { - this.clusterStartSplitPointToCluster = new TreeMap<>(); - this.clusterStartSplitPointToNextBreak = new TreeMap<>(); + this.startSplitPointToConnectedRange = new TreeMap<>(); + this.startSplitPointToNextGap = new TreeMap<>(); this.splitPointSet = splitPointSet; this.differenceFunction = differenceFunction; } - void addInterval(Interval interval) { - var intersectedIntervalClusterMap = clusterStartSplitPointToCluster - .subMap(Objects.requireNonNullElseGet(clusterStartSplitPointToCluster.floorKey(interval.getStartSplitPoint()), - interval::getStartSplitPoint), true, interval.getEndSplitPoint(), true); + void addRange(Range range) { + var intersectedConnectedRangeMap = startSplitPointToConnectedRange + .subMap(Objects.requireNonNullElseGet(startSplitPointToConnectedRange.floorKey(range.getStartSplitPoint()), + range::getStartSplitPoint), true, range.getEndSplitPoint(), true); - // Case: the interval cluster before this interval does not intersect this interval - if (!intersectedIntervalClusterMap.isEmpty() - && intersectedIntervalClusterMap.firstEntry().getValue().getEndSplitPoint() - .isBefore(interval.getStartSplitPoint())) { - // Get the tail map after the first cluster - intersectedIntervalClusterMap = intersectedIntervalClusterMap.subMap(intersectedIntervalClusterMap.firstKey(), - false, intersectedIntervalClusterMap.lastKey(), true); + // Case: the connected range before this range does not intersect this range + if (!intersectedConnectedRangeMap.isEmpty() + && intersectedConnectedRangeMap.firstEntry().getValue().getEndSplitPoint() + .isBefore(range.getStartSplitPoint())) { + // Get the tail map after the first connected range + intersectedConnectedRangeMap = intersectedConnectedRangeMap.subMap(intersectedConnectedRangeMap.firstKey(), + false, intersectedConnectedRangeMap.lastKey(), true); } - if (intersectedIntervalClusterMap.isEmpty()) { - // Interval does not intersect anything + if (intersectedConnectedRangeMap.isEmpty()) { + // Range does not intersect anything // Ex: // ----- //---- ----- - createNewIntervalCluster(interval); + createNewConnectedRange(range); return; } - // Interval intersect at least one cluster + // Range intersect at least one connected range // Ex: // ----------------- // ------ ------ --- ---- - var firstIntersectedIntervalCluster = intersectedIntervalClusterMap.firstEntry().getValue(); - var oldStartSplitPoint = firstIntersectedIntervalCluster.getStartSplitPoint(); - firstIntersectedIntervalCluster.addInterval(interval); + var firstIntersectedConnectedRange = intersectedConnectedRangeMap.firstEntry().getValue(); + var oldStartSplitPoint = firstIntersectedConnectedRange.getStartSplitPoint(); + firstIntersectedConnectedRange.addRange(range); - // Merge all the intersected interval clusters into the first intersected - // interval cluster - intersectedIntervalClusterMap.tailMap(oldStartSplitPoint, false).values() - .forEach(firstIntersectedIntervalCluster::mergeConnectedRange); + // Merge all the intersected connected range into the first intersected + // connected range + intersectedConnectedRangeMap.tailMap(oldStartSplitPoint, false).values() + .forEach(firstIntersectedConnectedRange::mergeConnectedRange); - // Remove all the intersected interval clusters after the first intersected + // Remove all the intersected connected ranges after the first intersected // one, since they are now merged in the first - intersectedIntervalClusterMap.tailMap(oldStartSplitPoint, false).clear(); - removeSpannedBreaksAndUpdateIntersectedBreaks(interval, firstIntersectedIntervalCluster); + intersectedConnectedRangeMap.tailMap(oldStartSplitPoint, false).clear(); + removeSpannedGapsAndUpdateIntersectedGaps(range, firstIntersectedConnectedRange); - // If the first intersected interval cluster start after the interval, - // we need to make the interval start point the key for this interval - // cluster in the map - if (oldStartSplitPoint.isAfter(firstIntersectedIntervalCluster.getStartSplitPoint())) { - clusterStartSplitPointToCluster.remove(oldStartSplitPoint); - clusterStartSplitPointToCluster.put(firstIntersectedIntervalCluster.getStartSplitPoint(), - firstIntersectedIntervalCluster); - var nextBreak = clusterStartSplitPointToNextBreak.get(firstIntersectedIntervalCluster.getStartSplitPoint()); - if (nextBreak != null) { - nextBreak.setPreviousCluster(firstIntersectedIntervalCluster); - nextBreak.setLength(differenceFunction.apply(nextBreak.getPreviousRangeEnd(), - nextBreak.getNextRangeStart())); + // If the first intersected connected range starts after the range, + // we need to make the range start point the key for this connected range + // in the map + if (oldStartSplitPoint.isAfter(firstIntersectedConnectedRange.getStartSplitPoint())) { + startSplitPointToConnectedRange.remove(oldStartSplitPoint); + startSplitPointToConnectedRange.put(firstIntersectedConnectedRange.getStartSplitPoint(), + firstIntersectedConnectedRange); + var nextGap = startSplitPointToNextGap.get(firstIntersectedConnectedRange.getStartSplitPoint()); + if (nextGap != null) { + nextGap.setPreviousCluster(firstIntersectedConnectedRange); + nextGap.setLength(differenceFunction.apply(nextGap.getPreviousRangeEnd(), + nextGap.getNextRangeStart())); } } } - private void createNewIntervalCluster(Interval interval) { - // Interval does not intersect anything + private void createNewConnectedRange(Range range) { + // Range does not intersect anything // Ex: // ----- //---- ----- - var startSplitPoint = splitPointSet.floor(interval.getStartSplitPoint()); - var newCluster = new ConnectedRangeImpl<>(splitPointSet, differenceFunction, startSplitPoint); - clusterStartSplitPointToCluster.put(startSplitPoint, newCluster); + var startSplitPoint = splitPointSet.floor(range.getStartSplitPoint()); + var newConnectedRange = + ConnectedRangeImpl.getConnectedRangeStartingAt(splitPointSet, differenceFunction, startSplitPoint); + startSplitPointToConnectedRange.put(startSplitPoint, newConnectedRange); - // If there a cluster after this interval, add a new break - // between this interval and the next cluster - var nextClusterEntry = clusterStartSplitPointToCluster.higherEntry(startSplitPoint); - if (nextClusterEntry != null) { - var nextCluster = nextClusterEntry.getValue(); - var difference = differenceFunction.apply(newCluster.getEnd(), nextCluster.getStart()); - var newBreak = new RangeGapImpl<>(newCluster, nextCluster, difference); - clusterStartSplitPointToNextBreak.put(startSplitPoint, newBreak); + // If there is a connected range after this range, add a new gap + // between this range and the next connected range + var nextConnectedRangeEntry = startSplitPointToConnectedRange.higherEntry(startSplitPoint); + if (nextConnectedRangeEntry != null) { + var nextConnectedRange = nextConnectedRangeEntry.getValue(); + var difference = differenceFunction.apply(newConnectedRange.getEnd(), nextConnectedRange.getStart()); + var newGap = new RangeGapImpl<>(newConnectedRange, nextConnectedRange, difference); + startSplitPointToNextGap.put(startSplitPoint, newGap); } - // If there a cluster before this interval, add a new break - // between this interval and the previous cluster - // (this will replace the old break, if there was one) - var previousClusterEntry = clusterStartSplitPointToCluster.lowerEntry(startSplitPoint); - if (previousClusterEntry != null) { - var previousCluster = previousClusterEntry.getValue(); - var difference = differenceFunction.apply(previousCluster.getEnd(), newCluster.getStart()); - var newBreak = new RangeGapImpl<>(previousCluster, newCluster, difference); - clusterStartSplitPointToNextBreak.put(previousClusterEntry.getKey(), newBreak); + // If there is a connected range before this range, add a new gap + // between this range and the previous connected range + // (this will replace the old gap, if there was one) + var previousConnectedRangeEntry = startSplitPointToConnectedRange.lowerEntry(startSplitPoint); + if (previousConnectedRangeEntry != null) { + var previousConnectedRange = previousConnectedRangeEntry.getValue(); + var difference = differenceFunction.apply(previousConnectedRange.getEnd(), newConnectedRange.getStart()); + var newGap = new RangeGapImpl<>(previousConnectedRange, newConnectedRange, difference); + startSplitPointToNextGap.put(previousConnectedRangeEntry.getKey(), newGap); } } - private void removeSpannedBreaksAndUpdateIntersectedBreaks(Interval interval, - ConnectedRangeImpl intervalCluster) { - var firstBreakSplitPointBeforeInterval = Objects.requireNonNullElseGet( - clusterStartSplitPointToNextBreak.floorKey(interval.getStartSplitPoint()), interval::getStartSplitPoint); - var intersectedIntervalBreakMap = clusterStartSplitPointToNextBreak.subMap(firstBreakSplitPointBeforeInterval, true, - interval.getEndSplitPoint(), true); + private void removeSpannedGapsAndUpdateIntersectedGaps(Range range, + ConnectedRangeImpl connectedRange) { + var firstGapSplitPointBeforeRange = Objects.requireNonNullElseGet( + startSplitPointToNextGap.floorKey(range.getStartSplitPoint()), range::getStartSplitPoint); + var intersectedRangeGapMap = startSplitPointToNextGap.subMap(firstGapSplitPointBeforeRange, true, + range.getEndSplitPoint(), true); - if (intersectedIntervalBreakMap.isEmpty()) { + if (intersectedRangeGapMap.isEmpty()) { return; } - var clusterBeforeFirstIntersectedBreak = - (ConnectedRangeImpl) (intersectedIntervalBreakMap.firstEntry().getValue() + var connectedRangeBeforeFirstIntersectedGap = + (ConnectedRangeImpl) (intersectedRangeGapMap.firstEntry().getValue() .getPreviousConnectedRange()); - var clusterAfterFinalIntersectedBreak = - (ConnectedRangeImpl) (intersectedIntervalBreakMap.lastEntry().getValue() + var connectedRangeAfterFinalIntersectedGap = + (ConnectedRangeImpl) (intersectedRangeGapMap.lastEntry().getValue() .getNextConnectedRange()); - // All breaks that are not the first or last intersected breaks will - // be removed (as interval span them) - if (!interval.getStartSplitPoint() - .isAfter(clusterBeforeFirstIntersectedBreak.getEndSplitPoint())) { - if (!interval.getEndSplitPoint().isBefore(clusterAfterFinalIntersectedBreak.getStartSplitPoint())) { - // Case: interval spans all breaks + // All gaps that are not the first or last intersected gap will + // be removed (as the range spans them) + if (!range.getStartSplitPoint() + .isAfter(connectedRangeBeforeFirstIntersectedGap.getEndSplitPoint())) { + if (!range.getEndSplitPoint().isBefore(connectedRangeAfterFinalIntersectedGap.getStartSplitPoint())) { + // Case: range spans all gaps // Ex: // ----------- //---- ------ ----- - intersectedIntervalBreakMap.clear(); + intersectedRangeGapMap.clear(); } else { - // Case: interval span first break, but does not span the final break + // Case: range span first gap, but does not span the final gap // Ex: // ----------- //---- ------ ----- - var finalBreak = intersectedIntervalBreakMap.lastEntry().getValue(); - finalBreak.setPreviousCluster(intervalCluster); - finalBreak.setLength( - differenceFunction.apply(finalBreak.getPreviousRangeEnd(), - finalBreak.getNextRangeStart())); - intersectedIntervalBreakMap.clear(); - clusterStartSplitPointToNextBreak.put(intervalCluster.getStartSplitPoint(), finalBreak); + var finalGap = intersectedRangeGapMap.lastEntry().getValue(); + finalGap.setPreviousCluster(connectedRange); + finalGap.setLength( + differenceFunction.apply(finalGap.getPreviousRangeEnd(), + finalGap.getNextRangeStart())); + intersectedRangeGapMap.clear(); + startSplitPointToNextGap.put(connectedRange.getStartSplitPoint(), finalGap); } - } else if (!interval.getEndSplitPoint().isBefore(clusterAfterFinalIntersectedBreak.getStartSplitPoint())) { - // Case: interval span final break, but does not span the first break + } else if (!range.getEndSplitPoint().isBefore(connectedRangeAfterFinalIntersectedGap.getStartSplitPoint())) { + // Case: range span final gap, but does not span the first gap // Ex: // ----------- //---- ----- ----- - var previousBreakEntry = intersectedIntervalBreakMap.firstEntry(); - var previousBreak = previousBreakEntry.getValue(); - previousBreak.setNextCluster(intervalCluster); - previousBreak.setLength( - differenceFunction.apply(previousBreak.getPreviousRangeEnd(), intervalCluster.getStart())); - intersectedIntervalBreakMap.clear(); - clusterStartSplitPointToNextBreak - .put(((ConnectedRangeImpl) (previousBreak - .getPreviousConnectedRange())).getStartSplitPoint(), previousBreak); + var previousGapEntry = intersectedRangeGapMap.firstEntry(); + var previousGap = previousGapEntry.getValue(); + previousGap.setNextCluster(connectedRange); + previousGap.setLength( + differenceFunction.apply(previousGap.getPreviousRangeEnd(), connectedRange.getStart())); + intersectedRangeGapMap.clear(); + startSplitPointToNextGap + .put(((ConnectedRangeImpl) (previousGap + .getPreviousConnectedRange())).getStartSplitPoint(), previousGap); } else { - // Case: interval does not span either the first or final break + // Case: range does not span either the first or final gap // Ex: // --------- //---- ------ ----- - var finalBreak = intersectedIntervalBreakMap.lastEntry().getValue(); - finalBreak.setLength(differenceFunction.apply(finalBreak.getPreviousRangeEnd(), - finalBreak.getNextRangeStart())); + var finalGap = intersectedRangeGapMap.lastEntry().getValue(); + finalGap.setLength(differenceFunction.apply(finalGap.getPreviousRangeEnd(), + finalGap.getNextRangeStart())); - var previousBreakEntry = intersectedIntervalBreakMap.firstEntry(); - var previousBreak = previousBreakEntry.getValue(); - previousBreak.setNextCluster(intervalCluster); - previousBreak.setLength( - differenceFunction.apply(previousBreak.getPreviousRangeEnd(), intervalCluster.getStart())); + var previousGapEntry = intersectedRangeGapMap.firstEntry(); + var previousGap = previousGapEntry.getValue(); + previousGap.setNextCluster(connectedRange); + previousGap.setLength( + differenceFunction.apply(previousGap.getPreviousRangeEnd(), connectedRange.getStart())); - intersectedIntervalBreakMap.clear(); - clusterStartSplitPointToNextBreak.put(previousBreakEntry.getKey(), previousBreak); - clusterStartSplitPointToNextBreak.put(intervalCluster.getStartSplitPoint(), finalBreak); + intersectedRangeGapMap.clear(); + startSplitPointToNextGap.put(previousGapEntry.getKey(), previousGap); + startSplitPointToNextGap.put(connectedRange.getStartSplitPoint(), finalGap); } } - void removeInterval(Interval interval) { - var intervalClusterEntry = clusterStartSplitPointToCluster.floorEntry(interval.getStartSplitPoint()); - var intervalCluster = intervalClusterEntry.getValue(); - clusterStartSplitPointToCluster.remove(intervalClusterEntry.getKey()); - var previousBreakEntry = clusterStartSplitPointToNextBreak.lowerEntry(intervalClusterEntry.getKey()); - var nextIntervalClusterEntry = clusterStartSplitPointToCluster.higherEntry(intervalClusterEntry.getKey()); - clusterStartSplitPointToNextBreak.remove(intervalClusterEntry.getKey()); + void removeRange(Range range) { + var connectedRangeEntry = startSplitPointToConnectedRange.floorEntry(range.getStartSplitPoint()); + var connectedRange = connectedRangeEntry.getValue(); + startSplitPointToConnectedRange.remove(connectedRangeEntry.getKey()); + var previousGapEntry = startSplitPointToNextGap.lowerEntry(connectedRangeEntry.getKey()); + var nextConnectedRangeEntry = startSplitPointToConnectedRange.higherEntry(connectedRangeEntry.getKey()); + startSplitPointToNextGap.remove(connectedRangeEntry.getKey()); - var previousBreak = (previousBreakEntry != null) ? previousBreakEntry.getValue() : null; - var previousIntervalCluster = (previousBreak != null) - ? (ConnectedRangeImpl) previousBreak.getPreviousConnectedRange() + var previousGap = (previousGapEntry != null) ? previousGapEntry.getValue() : null; + var previousConnectedRange = (previousGap != null) + ? (ConnectedRangeImpl) previousGap.getPreviousConnectedRange() : null; var iterator = new ConnectedSubrangeIterator<>(splitPointSet, - intervalCluster.getStartSplitPoint(), - intervalCluster.getEndSplitPoint(), + connectedRange.getStartSplitPoint(), + connectedRange.getEndSplitPoint(), differenceFunction); while (iterator.hasNext()) { - var newIntervalCluster = iterator.next(); - if (previousBreak != null) { - previousBreak.setNextCluster(newIntervalCluster); - previousBreak.setLength(differenceFunction.apply(previousBreak.getPreviousConnectedRange().getEnd(), - newIntervalCluster.getStart())); - clusterStartSplitPointToNextBreak - .put(((ConnectedRangeImpl) previousBreak - .getPreviousConnectedRange()).getStartSplitPoint(), previousBreak); + var newConnectedRange = iterator.next(); + if (previousGap != null) { + previousGap.setNextCluster(newConnectedRange); + previousGap.setLength(differenceFunction.apply(previousGap.getPreviousConnectedRange().getEnd(), + newConnectedRange.getStart())); + startSplitPointToNextGap + .put(((ConnectedRangeImpl) previousGap + .getPreviousConnectedRange()).getStartSplitPoint(), previousGap); } - previousBreak = new RangeGapImpl<>(newIntervalCluster, null, null); - previousIntervalCluster = newIntervalCluster; - clusterStartSplitPointToCluster.put(newIntervalCluster.getStartSplitPoint(), newIntervalCluster); + previousGap = new RangeGapImpl<>(newConnectedRange, null, null); + previousConnectedRange = newConnectedRange; + startSplitPointToConnectedRange.put(newConnectedRange.getStartSplitPoint(), newConnectedRange); } - if (nextIntervalClusterEntry != null && previousBreak != null) { - previousBreak.setNextCluster(nextIntervalClusterEntry.getValue()); - previousBreak.setLength(differenceFunction.apply(previousIntervalCluster.getEnd(), - nextIntervalClusterEntry.getValue().getStart())); - clusterStartSplitPointToNextBreak.put(previousIntervalCluster.getStartSplitPoint(), - previousBreak); - } else if (previousBreakEntry != null && previousBreak == previousBreakEntry.getValue()) { - // i.e. interval was the last interval in the cluster, - // (previousBreak == previousBreakEntry.getValue()), - // and there is no interval cluster after it - // (previousBreak != null as previousBreakEntry != null, - // so it must be the case nextIntervalClusterEntry == null) - clusterStartSplitPointToNextBreak.remove(previousBreakEntry.getKey()); + if (nextConnectedRangeEntry != null && previousGap != null) { + previousGap.setNextCluster(nextConnectedRangeEntry.getValue()); + previousGap.setLength(differenceFunction.apply(previousConnectedRange.getEnd(), + nextConnectedRangeEntry.getValue().getStart())); + startSplitPointToNextGap.put(previousConnectedRange.getStartSplitPoint(), + previousGap); + } else if (previousGapEntry != null && previousGap == previousGapEntry.getValue()) { + // i.e. range was the last range in the cluster, + // (previousGap == previousGapEntry.getValue()), + // and there is no range cluster after it + // (previousGap != null as previousGapEntry != null, + // so it must be the case nextConnectedRangeEntry == null) + startSplitPointToNextGap.remove(previousGapEntry.getKey()); } } @Override - public Iterable> getConnectedRanges() { - return (Iterable) clusterStartSplitPointToCluster.values(); + public Iterable> getConnectedRanges() { + return (Iterable) startSplitPointToConnectedRange.values(); } @Override public Iterable> getGaps() { - return (Iterable) clusterStartSplitPointToNextBreak.values(); + return (Iterable) startSplitPointToNextGap.values(); } @Override @@ -254,24 +255,24 @@ public boolean equals(Object o) { return true; if (!(o instanceof ConnectedRangeChainImpl that)) return false; - return Objects.equals(clusterStartSplitPointToCluster, - that.clusterStartSplitPointToCluster) + return Objects.equals(startSplitPointToConnectedRange, + that.startSplitPointToConnectedRange) && Objects.equals(splitPointSet, that.splitPointSet) - && Objects.equals(clusterStartSplitPointToNextBreak, - that.clusterStartSplitPointToNextBreak); + && Objects.equals(startSplitPointToNextGap, + that.startSplitPointToNextGap); } @Override public int hashCode() { - return Objects.hash(clusterStartSplitPointToCluster, splitPointSet, clusterStartSplitPointToNextBreak); + return Objects.hash(startSplitPointToConnectedRange, splitPointSet, startSplitPointToNextGap); } @Override public String toString() { - return "Clusters {" + - "intervalClusters=" + getConnectedRanges() + - ", breaks=" + getGaps() + + return "ConnectedRangeChain {" + + "connectedRanges=" + getConnectedRanges() + + ", gaps=" + getGaps() + '}'; } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeImpl.java index 84e6b335d8..ac00933d07 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeImpl.java @@ -7,58 +7,23 @@ import ai.timefold.solver.core.api.score.stream.common.ConnectedRange; -final class ConnectedRangeImpl, Difference_ extends Comparable> - implements ConnectedRange { +final class ConnectedRangeImpl, Difference_ extends Comparable> + implements ConnectedRange { - private final NavigableSet> splitPointSet; + private final NavigableSet> splitPointSet; private final BiFunction differenceFunction; - private IntervalSplitPoint startSplitPoint; - private IntervalSplitPoint endSplitPoint; + private RangeSplitPoint startSplitPoint; + private RangeSplitPoint endSplitPoint; private int count; private int minimumOverlap; private int maximumOverlap; private boolean hasOverlap; - ConnectedRangeImpl(NavigableSet> splitPointSet, + ConnectedRangeImpl(NavigableSet> splitPointSet, BiFunction differenceFunction, - IntervalSplitPoint start) { - if (start == null) { - throw new IllegalArgumentException("start (" + start + ") is null"); - } - if (differenceFunction == null) { - throw new IllegalArgumentException("differenceFunction (" + differenceFunction + ") is null"); - } - this.splitPointSet = splitPointSet; - this.startSplitPoint = start; - this.endSplitPoint = start; - this.differenceFunction = differenceFunction; - this.count = 0; - this.minimumOverlap = Integer.MAX_VALUE; - this.maximumOverlap = Integer.MIN_VALUE; - var activeIntervals = 0; - var anyOverlap = false; - var current = start; - do { - this.count += current.intervalsStartingAtSplitPointSet.size(); - activeIntervals += current.intervalsStartingAtSplitPointSet.size() - current.intervalsEndingAtSplitPointSet.size(); - if (activeIntervals > 0) { - minimumOverlap = Math.min(minimumOverlap, activeIntervals); - maximumOverlap = Math.max(maximumOverlap, activeIntervals); - if (activeIntervals > 1) { - anyOverlap = true; - } - } - current = splitPointSet.higher(current); - } while (activeIntervals > 0 && current != null); - this.hasOverlap = anyOverlap; - this.endSplitPoint = (current != null) ? splitPointSet.lower(current) : splitPointSet.last(); - } - - ConnectedRangeImpl(NavigableSet> splitPointSet, - BiFunction differenceFunction, - IntervalSplitPoint start, - IntervalSplitPoint end, int count, + RangeSplitPoint start, + RangeSplitPoint end, int count, int minimumOverlap, int maximumOverlap, boolean hasOverlap) { this.splitPointSet = splitPointSet; @@ -71,50 +36,58 @@ final class ConnectedRangeImpl, Dif this.hasOverlap = hasOverlap; } - IntervalSplitPoint getStartSplitPoint() { + static , Difference_ extends Comparable> + ConnectedRangeImpl + getConnectedRangeStartingAt(NavigableSet> splitPointSet, + BiFunction differenceFunction, + RangeSplitPoint start) { + return new ConnectedSubrangeIterator<>(splitPointSet, start, splitPointSet.last(), differenceFunction).next(); + } + + RangeSplitPoint getStartSplitPoint() { return startSplitPoint; } - IntervalSplitPoint getEndSplitPoint() { + RangeSplitPoint getEndSplitPoint() { return endSplitPoint; } - void addInterval(Interval interval) { - if (interval.getEndSplitPoint().compareTo(getStartSplitPoint()) > 0 - && interval.getStartSplitPoint().compareTo(getEndSplitPoint()) < 0) { + void addRange(Range range) { + if (range.getEndSplitPoint().compareTo(getStartSplitPoint()) > 0 + && range.getStartSplitPoint().compareTo(getEndSplitPoint()) < 0) { hasOverlap = true; } - if (interval.getStartSplitPoint().compareTo(startSplitPoint) < 0) { - startSplitPoint = splitPointSet.floor(interval.getStartSplitPoint()); + if (range.getStartSplitPoint().compareTo(startSplitPoint) < 0) { + startSplitPoint = splitPointSet.floor(range.getStartSplitPoint()); } - if (interval.getEndSplitPoint().compareTo(endSplitPoint) > 0) { - endSplitPoint = splitPointSet.ceiling(interval.getEndSplitPoint()); + if (range.getEndSplitPoint().compareTo(endSplitPoint) > 0) { + endSplitPoint = splitPointSet.ceiling(range.getEndSplitPoint()); } minimumOverlap = -1; maximumOverlap = -1; count++; } - Iterable> getNewConnectedRanges( - final NavigableSet> newSplitPointSet) { + Iterable> getNewConnectedRanges( + final NavigableSet> newSplitPointSet) { return () -> new ConnectedSubrangeIterator<>(newSplitPointSet, startSplitPoint, endSplitPoint, differenceFunction); } - void mergeConnectedRange(ConnectedRangeImpl laterIntervalCluster) { - if (endSplitPoint.compareTo(laterIntervalCluster.startSplitPoint) > 0) { + void mergeConnectedRange(ConnectedRangeImpl laterConnectedRange) { + if (endSplitPoint.compareTo(laterConnectedRange.startSplitPoint) > 0) { hasOverlap = true; } - if (endSplitPoint.compareTo(laterIntervalCluster.endSplitPoint) < 0) { - endSplitPoint = laterIntervalCluster.endSplitPoint; + if (endSplitPoint.compareTo(laterConnectedRange.endSplitPoint) < 0) { + endSplitPoint = laterConnectedRange.endSplitPoint; } - count += laterIntervalCluster.count; + count += laterConnectedRange.count; minimumOverlap = -1; maximumOverlap = -1; - hasOverlap |= laterIntervalCluster.hasOverlap; + hasOverlap |= laterConnectedRange.hasOverlap; } @Override - public Iterator iterator() { + public Iterator iterator() { return new ContainedRangeIterator<>(splitPointSet.subSet(startSplitPoint, true, endSplitPoint, true)); } @@ -130,17 +103,17 @@ public boolean hasOverlap() { private void recalculateMinimumAndMaximumOverlap() { var current = startSplitPoint; - var activeIntervals = 0; + var activeRangeCount = 0; minimumOverlap = Integer.MAX_VALUE; maximumOverlap = Integer.MIN_VALUE; do { - activeIntervals += current.intervalsStartingAtSplitPointSet.size() - current.intervalsEndingAtSplitPointSet.size(); - if (activeIntervals > 0) { - minimumOverlap = Math.min(minimumOverlap, activeIntervals); - maximumOverlap = Math.max(maximumOverlap, activeIntervals); + activeRangeCount += current.rangesStartingAtSplitPointSet.size() - current.rangesEndingAtSplitPointSet.size(); + if (activeRangeCount > 0) { + minimumOverlap = Math.min(minimumOverlap, activeRangeCount); + maximumOverlap = Math.max(maximumOverlap, activeRangeCount); } current = splitPointSet.higher(current); - } while (activeIntervals > 0 && current != null); + } while (activeRangeCount > 0 && current != null); } @Override @@ -205,7 +178,6 @@ public String toString() { ", minimumOverlap=" + getMinimumOverlap() + ", maximumOverlap=" + getMaximumOverlap() + ", hasOverlap=" + hasOverlap + - ", set=" + splitPointSet + '}'; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeTracker.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeTracker.java new file mode 100644 index 0000000000..586d32e1df --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeTracker.java @@ -0,0 +1,104 @@ +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; + +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.TreeSet; +import java.util.function.BiFunction; +import java.util.function.Function; + +import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; + +public final class ConnectedRangeTracker, Difference_ extends Comparable> { + + private final Function startMapping; + private final Function endMapping; + private final NavigableSet> splitPointSet; + private final ConnectedRangeChainImpl connectedRangeChain; + + public ConnectedRangeTracker(Function startMapping, + Function endMapping, + BiFunction differenceFunction) { + this.startMapping = startMapping; + this.endMapping = endMapping; + this.splitPointSet = new TreeSet<>(); + this.connectedRangeChain = new ConnectedRangeChainImpl<>(splitPointSet, differenceFunction); + } + + public Range getRange(Range_ rangeValue) { + return new Range<>(rangeValue, startMapping, endMapping); + } + + public boolean isEmpty() { + return splitPointSet.isEmpty(); + } + + public boolean contains(Range_ o) { + if (null == o || splitPointSet.isEmpty()) { + return false; + } + var range = getRange(o); + var floorStartSplitPoint = splitPointSet.floor(range.getStartSplitPoint()); + if (floorStartSplitPoint == null) { + return false; + } + return floorStartSplitPoint.containsRangeStarting(range); + } + + public Iterator iterator() { + return new ContainedRangeIterator<>(splitPointSet); + } + + public boolean add(Range range) { + var startSplitPoint = range.getStartSplitPoint(); + var endSplitPoint = range.getEndSplitPoint(); + + var flooredStartSplitPoint = splitPointSet.floor(startSplitPoint); + if (flooredStartSplitPoint == null || !flooredStartSplitPoint.equals(startSplitPoint)) { + splitPointSet.add(startSplitPoint); + startSplitPoint.createCollections(); + startSplitPoint.addRangeStartingAtSplitPoint(range); + } else { + flooredStartSplitPoint.addRangeStartingAtSplitPoint(range); + } + + var ceilingEndSplitPoint = splitPointSet.ceiling(endSplitPoint); + if (ceilingEndSplitPoint == null || !ceilingEndSplitPoint.equals(endSplitPoint)) { + splitPointSet.add(endSplitPoint); + endSplitPoint.createCollections(); + endSplitPoint.addRangeEndingAtSplitPoint(range); + } else { + ceilingEndSplitPoint.addRangeEndingAtSplitPoint(range); + } + + connectedRangeChain.addRange(range); + return true; + } + + public boolean remove(Range range) { + var startSplitPoint = range.getStartSplitPoint(); + var endSplitPoint = range.getEndSplitPoint(); + var flooredStartSplitPoint = splitPointSet.floor(startSplitPoint); + if (flooredStartSplitPoint == null || !flooredStartSplitPoint.containsRangeStarting(range)) { + return false; + } + + flooredStartSplitPoint.removeRangeStartingAtSplitPoint(range); + if (flooredStartSplitPoint.isEmpty()) { + splitPointSet.remove(flooredStartSplitPoint); + } + + var ceilEndSplitPoint = splitPointSet.ceiling(endSplitPoint); + // Not null since the start point contained the range + ceilEndSplitPoint.removeRangeEndingAtSplitPoint(range); + if (ceilEndSplitPoint.isEmpty()) { + splitPointSet.remove(ceilEndSplitPoint); + } + + connectedRangeChain.removeRange(range); + return true; + } + + public ConnectedRangeChain getConnectedRangeChain() { + return connectedRangeChain; + } +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedSubrangeIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedSubrangeIterator.java index 232b8358c6..605e4f1959 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedSubrangeIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedSubrangeIterator.java @@ -5,17 +5,17 @@ import java.util.NoSuchElementException; import java.util.function.BiFunction; -final class ConnectedSubrangeIterator, Difference_ extends Comparable> - implements Iterator> { - // TODO: Make this incremental by only checking between the interval's start and end points - private final NavigableSet> splitPointSet; +final class ConnectedSubrangeIterator, Difference_ extends Comparable> + implements Iterator> { + // TODO: Make this incremental by only checking between the range's start and end points + private final NavigableSet> splitPointSet; private final BiFunction differenceFunction; - private final IntervalSplitPoint endSplitPoint; - private IntervalSplitPoint current; + private final RangeSplitPoint endSplitPoint; + private RangeSplitPoint current; - public ConnectedSubrangeIterator(NavigableSet> splitPointSet, - IntervalSplitPoint startSplitPoint, - IntervalSplitPoint endSplitPoint, + public ConnectedSubrangeIterator(NavigableSet> splitPointSet, + RangeSplitPoint startSplitPoint, + RangeSplitPoint endSplitPoint, BiFunction differenceFunction) { this.splitPointSet = splitPointSet; this.current = getStart(startSplitPoint); @@ -23,8 +23,8 @@ public ConnectedSubrangeIterator(NavigableSet - getStart(IntervalSplitPoint start) { + private RangeSplitPoint + getStart(RangeSplitPoint start) { while (start != null && start.isEmpty()) { start = splitPointSet.higher(start); } @@ -37,30 +37,30 @@ public boolean hasNext() { } @Override - public ConnectedRangeImpl next() { + public ConnectedRangeImpl next() { if (current == null) { throw new NoSuchElementException(); } - IntervalSplitPoint start = current; - IntervalSplitPoint end; - int activeIntervals = 0; + RangeSplitPoint start = current; + RangeSplitPoint end; + int activeRangeCount = 0; int minimumOverlap = Integer.MAX_VALUE; int maximumOverlap = Integer.MIN_VALUE; int count = 0; boolean anyOverlap = false; do { - count += current.intervalsStartingAtSplitPointSet.size(); - activeIntervals += - current.intervalsStartingAtSplitPointSet.size() - current.intervalsEndingAtSplitPointSet.size(); - if (activeIntervals > 0) { - minimumOverlap = Math.min(minimumOverlap, activeIntervals); - maximumOverlap = Math.max(maximumOverlap, activeIntervals); - if (activeIntervals > 1) { + count += current.rangesStartingAtSplitPointSet.size(); + activeRangeCount += + current.rangesStartingAtSplitPointSet.size() - current.rangesEndingAtSplitPointSet.size(); + if (activeRangeCount > 0) { + minimumOverlap = Math.min(minimumOverlap, activeRangeCount); + maximumOverlap = Math.max(maximumOverlap, activeRangeCount); + if (activeRangeCount > 1) { anyOverlap = true; } } current = splitPointSet.higher(current); - } while (activeIntervals > 0 && current != null); + } while (activeRangeCount > 0 && current != null); if (current != null) { end = splitPointSet.lower(current); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ContainedRangeIterator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ContainedRangeIterator.java index 1bab9282f9..83b61223b1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ContainedRangeIterator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ContainedRangeIterator.java @@ -2,12 +2,12 @@ import java.util.Iterator; -final class ContainedRangeIterator> implements Iterator { +final class ContainedRangeIterator> implements Iterator { - private final Iterator> splitPointSetIterator; - private Iterator splitPointValueIterator; + private final Iterator> splitPointSetIterator; + private Iterator splitPointValueIterator; - ContainedRangeIterator(Iterable> splitPointSet) { + ContainedRangeIterator(Iterable> splitPointSet) { this.splitPointSetIterator = splitPointSet.iterator(); if (splitPointSetIterator.hasNext()) { splitPointValueIterator = splitPointSetIterator.next().getValuesStartingFromSplitPointIterator(); @@ -20,7 +20,7 @@ public boolean hasNext() { } @Override - public Interval_ next() { + public Range_ next() { var next = splitPointValueIterator.next(); while (!splitPointValueIterator.hasNext() && splitPointSetIterator.hasNext()) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalSplitPoint.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalSplitPoint.java deleted file mode 100644 index 89ef22c371..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalSplitPoint.java +++ /dev/null @@ -1,116 +0,0 @@ -package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; - -import java.util.Comparator; -import java.util.IdentityHashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; - -public class IntervalSplitPoint> - implements Comparable> { - final Point_ splitPoint; - Map startIntervalToCountMap; - Map endIntervalToCountMap; - TreeMultiSet> intervalsStartingAtSplitPointSet; - TreeMultiSet> intervalsEndingAtSplitPointSet; - - public IntervalSplitPoint(Point_ splitPoint) { - this.splitPoint = splitPoint; - } - - protected void createCollections() { - startIntervalToCountMap = new IdentityHashMap<>(); - endIntervalToCountMap = new IdentityHashMap<>(); - intervalsStartingAtSplitPointSet = new TreeMultiSet<>( - Comparator., Point_> comparing(Interval::getEnd) - .thenComparingInt(interval -> System.identityHashCode(interval.getValue()))); - intervalsEndingAtSplitPointSet = new TreeMultiSet<>( - Comparator., Point_> comparing(Interval::getStart) - .thenComparingInt(interval -> System.identityHashCode(interval.getValue()))); - } - - public boolean addIntervalStartingAtSplitPoint(Interval interval) { - startIntervalToCountMap.merge(interval.getValue(), 1, Integer::sum); - return intervalsStartingAtSplitPointSet.add(interval); - } - - public void removeIntervalStartingAtSplitPoint(Interval interval) { - Integer newCount = startIntervalToCountMap.computeIfPresent(interval.getValue(), (key, count) -> { - if (count > 1) { - return count - 1; - } - return null; - }); - if (null == newCount) { - intervalsStartingAtSplitPointSet.remove(interval); - } - } - - public boolean addIntervalEndingAtSplitPoint(Interval interval) { - endIntervalToCountMap.merge(interval.getValue(), 1, Integer::sum); - return intervalsEndingAtSplitPointSet.add(interval); - } - - public void removeIntervalEndingAtSplitPoint(Interval interval) { - Integer newCount = endIntervalToCountMap.computeIfPresent(interval.getValue(), (key, count) -> { - if (count > 1) { - return count - 1; - } - return null; - }); - if (null == newCount) { - intervalsEndingAtSplitPointSet.remove(interval); - } - } - - public boolean containsIntervalStarting(Interval interval) { - return intervalsStartingAtSplitPointSet.contains(interval); - } - - public boolean containsIntervalEnding(Interval interval) { - return intervalsEndingAtSplitPointSet.contains(interval); - } - - public Iterator getValuesStartingFromSplitPointIterator() { - return intervalsStartingAtSplitPointSet.stream() - .map(Interval::getValue) - .iterator(); - } - - public boolean isEmpty() { - return intervalsStartingAtSplitPointSet.isEmpty() && intervalsEndingAtSplitPointSet.isEmpty(); - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - IntervalSplitPoint that = (IntervalSplitPoint) o; - return splitPoint.equals(that.splitPoint); - } - - public boolean isBefore(IntervalSplitPoint other) { - return compareTo(other) < 0; - } - - public boolean isAfter(IntervalSplitPoint other) { - return compareTo(other) > 0; - } - - @Override - public int hashCode() { - return Objects.hash(splitPoint); - } - - @Override - public int compareTo(IntervalSplitPoint other) { - return splitPoint.compareTo(other.splitPoint); - } - - @Override - public String toString() { - return splitPoint.toString(); - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTree.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTree.java deleted file mode 100644 index bbd0f3c45a..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTree.java +++ /dev/null @@ -1,104 +0,0 @@ -package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; - -import java.util.Iterator; -import java.util.NavigableSet; -import java.util.TreeSet; -import java.util.function.BiFunction; -import java.util.function.Function; - -import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; - -public final class IntervalTree, Difference_ extends Comparable> { - - private final Function startMapping; - private final Function endMapping; - private final NavigableSet> splitPointSet; - private final ConnectedRangeChainImpl consecutiveIntervalData; - - public IntervalTree(Function startMapping, - Function endMapping, - BiFunction differenceFunction) { - this.startMapping = startMapping; - this.endMapping = endMapping; - this.splitPointSet = new TreeSet<>(); - this.consecutiveIntervalData = new ConnectedRangeChainImpl<>(splitPointSet, differenceFunction); - } - - public Interval getInterval(Interval_ intervalValue) { - return new Interval<>(intervalValue, startMapping, endMapping); - } - - public boolean isEmpty() { - return splitPointSet.isEmpty(); - } - - public boolean contains(Interval_ o) { - if (null == o || splitPointSet.isEmpty()) { - return false; - } - var interval = getInterval(o); - var floorStartSplitPoint = splitPointSet.floor(interval.getStartSplitPoint()); - if (floorStartSplitPoint == null) { - return false; - } - return floorStartSplitPoint.containsIntervalStarting(interval); - } - - public Iterator iterator() { - return new ContainedRangeIterator<>(splitPointSet); - } - - public boolean add(Interval interval) { - var startSplitPoint = interval.getStartSplitPoint(); - var endSplitPoint = interval.getEndSplitPoint(); - - var flooredStartSplitPoint = splitPointSet.floor(startSplitPoint); - if (flooredStartSplitPoint == null || !flooredStartSplitPoint.equals(startSplitPoint)) { - splitPointSet.add(startSplitPoint); - startSplitPoint.createCollections(); - startSplitPoint.addIntervalStartingAtSplitPoint(interval); - } else { - flooredStartSplitPoint.addIntervalStartingAtSplitPoint(interval); - } - - var ceilingEndSplitPoint = splitPointSet.ceiling(endSplitPoint); - if (ceilingEndSplitPoint == null || !ceilingEndSplitPoint.equals(endSplitPoint)) { - splitPointSet.add(endSplitPoint); - endSplitPoint.createCollections(); - endSplitPoint.addIntervalEndingAtSplitPoint(interval); - } else { - ceilingEndSplitPoint.addIntervalEndingAtSplitPoint(interval); - } - - consecutiveIntervalData.addInterval(interval); - return true; - } - - public boolean remove(Interval interval) { - var startSplitPoint = interval.getStartSplitPoint(); - var endSplitPoint = interval.getEndSplitPoint(); - var flooredStartSplitPoint = splitPointSet.floor(startSplitPoint); - if (flooredStartSplitPoint == null || !flooredStartSplitPoint.containsIntervalStarting(interval)) { - return false; - } - - flooredStartSplitPoint.removeIntervalStartingAtSplitPoint(interval); - if (flooredStartSplitPoint.isEmpty()) { - splitPointSet.remove(flooredStartSplitPoint); - } - - var ceilEndSplitPoint = splitPointSet.ceiling(endSplitPoint); - // Not null since the start point contained the interval - ceilEndSplitPoint.removeIntervalEndingAtSplitPoint(interval); - if (ceilEndSplitPoint.isEmpty()) { - splitPointSet.remove(ceilEndSplitPoint); - } - - consecutiveIntervalData.removeInterval(interval); - return true; - } - - public ConnectedRangeChain getConnectedRangeChain() { - return consecutiveIntervalData; - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/Interval.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/Range.java similarity index 58% rename from core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/Interval.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/Range.java index 31712cfd8c..9942fc1ecd 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/Interval.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/Range.java @@ -2,21 +2,21 @@ import java.util.function.Function; -public final class Interval> { - private final Interval_ value; - private final IntervalSplitPoint startSplitPoint; - private final IntervalSplitPoint endSplitPoint; +public final class Range> { + private final Range_ value; + private final RangeSplitPoint startSplitPoint; + private final RangeSplitPoint endSplitPoint; - public Interval(Interval_ value, Function startMapping, - Function endMapping) { + public Range(Range_ value, Function startMapping, + Function endMapping) { this.value = value; var start = startMapping.apply(value); var end = endMapping.apply(value); - this.startSplitPoint = new IntervalSplitPoint<>(start); - this.endSplitPoint = (start == end) ? this.startSplitPoint : new IntervalSplitPoint<>(end); + this.startSplitPoint = new RangeSplitPoint<>(start); + this.endSplitPoint = (start == end) ? this.startSplitPoint : new RangeSplitPoint<>(end); } - public Interval_ getValue() { + public Range_ getValue() { return value; } @@ -28,11 +28,11 @@ public Point_ getEnd() { return endSplitPoint.splitPoint; } - public IntervalSplitPoint getStartSplitPoint() { + public RangeSplitPoint getStartSplitPoint() { return startSplitPoint; } - public IntervalSplitPoint getEndSplitPoint() { + public RangeSplitPoint getEndSplitPoint() { return endSplitPoint; } @@ -42,7 +42,7 @@ public boolean equals(Object o) { return true; if (o == null || getClass() != o.getClass()) return false; - Interval that = (Interval) o; + Range that = (Range) o; return value == that.value; } @@ -53,7 +53,7 @@ public int hashCode() { @Override public String toString() { - return "Interval{" + + return "Range{" + "value=" + value + ", start=" + getStart() + ", end=" + getEnd() + diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java index 5096d7fc99..fa996b4408 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java @@ -3,24 +3,24 @@ import ai.timefold.solver.core.api.score.stream.common.ConnectedRange; import ai.timefold.solver.core.api.score.stream.common.RangeGap; -final class RangeGapImpl, Difference_ extends Comparable> +final class RangeGapImpl, Difference_ extends Comparable> implements RangeGap { - private ConnectedRange previousCluster; - private ConnectedRange nextCluster; + private ConnectedRange previousCluster; + private ConnectedRange nextCluster; private Difference_ length; - RangeGapImpl(ConnectedRange previousCluster, - ConnectedRange nextCluster, Difference_ length) { + RangeGapImpl(ConnectedRange previousCluster, + ConnectedRange nextCluster, Difference_ length) { this.previousCluster = previousCluster; this.nextCluster = nextCluster; this.length = length; } - ConnectedRange getPreviousConnectedRange() { + ConnectedRange getPreviousConnectedRange() { return previousCluster; } - ConnectedRange getNextConnectedRange() { + ConnectedRange getNextConnectedRange() { return nextCluster; } @@ -39,11 +39,11 @@ public Difference_ getLength() { return length; } - void setPreviousCluster(ConnectedRange previousCluster) { + void setPreviousCluster(ConnectedRange previousCluster) { this.previousCluster = previousCluster; } - void setNextCluster(ConnectedRange nextCluster) { + void setNextCluster(ConnectedRange nextCluster) { this.nextCluster = nextCluster; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeSplitPoint.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeSplitPoint.java new file mode 100644 index 0000000000..dae7e650a7 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeSplitPoint.java @@ -0,0 +1,116 @@ +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; + +import java.util.Comparator; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; + +public class RangeSplitPoint> + implements Comparable> { + final Point_ splitPoint; + Map startpointRangeToCountMap; + Map endpointRangeToCountMap; + TreeMultiSet> rangesStartingAtSplitPointSet; + TreeMultiSet> rangesEndingAtSplitPointSet; + + public RangeSplitPoint(Point_ splitPoint) { + this.splitPoint = splitPoint; + } + + protected void createCollections() { + startpointRangeToCountMap = new IdentityHashMap<>(); + endpointRangeToCountMap = new IdentityHashMap<>(); + rangesStartingAtSplitPointSet = new TreeMultiSet<>( + Comparator., Point_> comparing(Range::getEnd) + .thenComparingInt(range -> System.identityHashCode(range.getValue()))); + rangesEndingAtSplitPointSet = new TreeMultiSet<>( + Comparator., Point_> comparing(Range::getStart) + .thenComparingInt(range -> System.identityHashCode(range.getValue()))); + } + + public boolean addRangeStartingAtSplitPoint(Range range) { + startpointRangeToCountMap.merge(range.getValue(), 1, Integer::sum); + return rangesStartingAtSplitPointSet.add(range); + } + + public void removeRangeStartingAtSplitPoint(Range range) { + Integer newCount = startpointRangeToCountMap.computeIfPresent(range.getValue(), (key, count) -> { + if (count > 1) { + return count - 1; + } + return null; + }); + if (null == newCount) { + rangesStartingAtSplitPointSet.remove(range); + } + } + + public boolean addRangeEndingAtSplitPoint(Range range) { + endpointRangeToCountMap.merge(range.getValue(), 1, Integer::sum); + return rangesEndingAtSplitPointSet.add(range); + } + + public void removeRangeEndingAtSplitPoint(Range range) { + Integer newCount = endpointRangeToCountMap.computeIfPresent(range.getValue(), (key, count) -> { + if (count > 1) { + return count - 1; + } + return null; + }); + if (null == newCount) { + rangesEndingAtSplitPointSet.remove(range); + } + } + + public boolean containsRangeStarting(Range range) { + return rangesStartingAtSplitPointSet.contains(range); + } + + public boolean containsRangeEnding(Range range) { + return rangesEndingAtSplitPointSet.contains(range); + } + + public Iterator getValuesStartingFromSplitPointIterator() { + return rangesStartingAtSplitPointSet.stream() + .map(Range::getValue) + .iterator(); + } + + public boolean isEmpty() { + return rangesStartingAtSplitPointSet.isEmpty() && rangesEndingAtSplitPointSet.isEmpty(); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + RangeSplitPoint that = (RangeSplitPoint) o; + return splitPoint.equals(that.splitPoint); + } + + public boolean isBefore(RangeSplitPoint other) { + return compareTo(other) < 0; + } + + public boolean isAfter(RangeSplitPoint other) { + return compareTo(other) > 0; + } + + @Override + public int hashCode() { + return Objects.hash(splitPoint); + } + + @Override + public int compareTo(RangeSplitPoint other) { + return splitPoint.compareTo(other.splitPoint); + } + + @Override + public String toString() { + return splitPoint.toString(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java index 42b32ce453..a08098cd3c 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/AbstractConstraintCollectorsTest.java @@ -4,7 +4,7 @@ import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; -import ai.timefold.solver.core.impl.score.stream.collector.connected_ranges.IntervalTree; +import ai.timefold.solver.core.impl.score.stream.collector.connected_ranges.ConnectedRangeTracker; import ai.timefold.solver.core.impl.score.stream.collector.consecutive.ConsecutiveSetTree; import org.junit.jupiter.api.Test; @@ -124,8 +124,8 @@ protected static SequenceChain buildSequenceChain(Integer... d protected ConnectedRangeChain buildConsecutiveUsage(Interval... data) { return Arrays.stream(data).collect( - () -> new IntervalTree<>(Interval::start, Interval::end, (a, b) -> b - a), - (tree, datum) -> tree.add(tree.getInterval(datum)), + () -> new ConnectedRangeTracker<>(Interval::start, Interval::end, (a, b) -> b - a), + (tree, datum) -> tree.add(tree.getRange(datum)), (a, b) -> { throw new UnsupportedOperationException(); }).getConnectedRangeChain(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeTrackerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeTrackerTest.java new file mode 100644 index 0000000000..8c9ebd8a54 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeTrackerTest.java @@ -0,0 +1,468 @@ +package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import ai.timefold.solver.core.api.score.stream.common.ConnectedRange; +import ai.timefold.solver.core.api.score.stream.common.RangeGap; + +import org.junit.jupiter.api.Test; + +class ConnectedRangeTrackerTest { + private static class TestRange { + int start; + int end; + + public TestRange(int start, int end) { + this.start = start; + this.end = end; + } + + public int getStart() { + return start; + } + + public int getEnd() { + return end; + } + + public void setStart(int start) { + this.start = start; + } + + public void setEnd(int end) { + this.end = end; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + TestRange range = (TestRange) o; + return start == range.start && end == range.end; + } + + @Override + public int hashCode() { + return Objects.hash(start, end); + } + + @Override + public String toString() { + return "(" + start + ", " + end + ")"; + } + } + + private ConnectedRangeTracker getIntegerConnectedRangeTracker() { + return new ConnectedRangeTracker<>(TestRange::getStart, TestRange::getEnd, (a, b) -> b - a); + } + + @Test + void testNonConsecutiveRanges() { + ConnectedRangeTracker tree = getIntegerConnectedRangeTracker(); + Range a = tree.getRange(new TestRange(0, 2)); + Range b = tree.getRange(new TestRange(3, 4)); + Range c = tree.getRange(new TestRange(5, 7)); + tree.add(a); + tree.add(b); + tree.add(c); + + var connectedRangeList = + new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); + assertThat(connectedRangeList).hasSize(3); + + assertThat(connectedRangeList.get(0)).containsExactly(new TestRange(0, 2)); + assertThat(connectedRangeList.get(0).hasOverlap()).isFalse(); + assertThat(connectedRangeList.get(0).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRangeList.get(0).getMaximumOverlap()).isEqualTo(1); + + assertThat(connectedRangeList.get(1)).containsExactly(new TestRange(3, 4)); + assertThat(connectedRangeList.get(1).hasOverlap()).isFalse(); + assertThat(connectedRangeList.get(1).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRangeList.get(1).getMaximumOverlap()).isEqualTo(1); + + assertThat(connectedRangeList.get(2)).containsExactly(new TestRange(5, 7)); + assertThat(connectedRangeList.get(2).hasOverlap()).isFalse(); + assertThat(connectedRangeList.get(2).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRangeList.get(2).getMaximumOverlap()).isEqualTo(1); + + verifyGaps(tree); + } + + @Test + void testConsecutiveRanges() { + ConnectedRangeTracker tree = getIntegerConnectedRangeTracker(); + Range a = tree.getRange(new TestRange(0, 2)); + Range b = tree.getRange(new TestRange(2, 4)); + Range c = tree.getRange(new TestRange(4, 7)); + tree.add(a); + tree.add(b); + tree.add(c); + + var connectedRangeList = + new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); + assertThat(connectedRangeList).hasSize(1); + + assertThat(connectedRangeList.get(0)).containsExactly(new TestRange(0, 2), new TestRange(2, 4), new TestRange(4, 7)); + assertThat(connectedRangeList.get(0).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRangeList.get(0).getMaximumOverlap()).isEqualTo(1); + verifyGaps(tree); + } + + @Test + void testDuplicateRanges() { + ConnectedRangeTracker tree = getIntegerConnectedRangeTracker(); + Range a = tree.getRange(new TestRange(0, 2)); + Range b = tree.getRange(new TestRange(4, 7)); + tree.add(a); + tree.add(a); + tree.add(b); + + var connectedRangeList = + new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); + assertThat(connectedRangeList).hasSize(2); + + assertThat(connectedRangeList.get(0)).containsExactly(a.getValue(), a.getValue()); + assertThat(connectedRangeList.get(0).getMinimumOverlap()).isEqualTo(2); + assertThat(connectedRangeList.get(0).getMaximumOverlap()).isEqualTo(2); + assertThat(connectedRangeList.get(1)).containsExactly(b.getValue()); + assertThat(connectedRangeList.get(1).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRangeList.get(1).getMaximumOverlap()).isEqualTo(1); + verifyGaps(tree); + } + + @Test + void testRangeRemoval() { + ConnectedRangeTracker tree = getIntegerConnectedRangeTracker(); + TestRange removedRange = new TestRange(2, 4); + Range a = tree.getRange(new TestRange(0, 2)); + Range b = tree.getRange(removedRange); + Range c = tree.getRange(new TestRange(4, 7)); + tree.add(a); + tree.add(b); + tree.add(c); + + // Imitate changing planning variables + removedRange.setStart(10); + removedRange.setEnd(12); + + tree.remove(b); + + var connectedRangeList = + new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); + assertThat(connectedRangeList).hasSize(2); + + assertThat(connectedRangeList.get(0)).containsExactly(new TestRange(0, 2)); + assertThat(connectedRangeList.get(1)).containsExactly(new TestRange(4, 7)); + verifyGaps(tree); + } + + @Test + void testRangeAddUpdatingOldGap() { + ConnectedRangeTracker tree = getIntegerConnectedRangeTracker(); + TestRange beforeAll = new TestRange(1, 2); + TestRange newStart = new TestRange(3, 8); + TestRange oldStart = new TestRange(4, 5); + TestRange betweenOldAndNewStart = new TestRange(6, 7); + TestRange afterAll = new TestRange(9, 10); + + tree.add(tree.getRange(beforeAll)); + verifyGaps(tree); + + tree.add(tree.getRange(afterAll)); + verifyGaps(tree); + + tree.add(tree.getRange(oldStart)); + verifyGaps(tree); + + tree.add(tree.getRange(betweenOldAndNewStart)); + verifyGaps(tree); + + tree.add(tree.getRange(newStart)); + verifyGaps(tree); + } + + @Test + void testOverlappingRange() { + ConnectedRangeTracker tree = getIntegerConnectedRangeTracker(); + Range a = tree.getRange(new TestRange(0, 2)); + TestRange removedTestRange1 = new TestRange(1, 3); + Range removedRange1 = tree.getRange(removedTestRange1); + Range c = tree.getRange(new TestRange(2, 4)); + + Range d = tree.getRange(new TestRange(5, 6)); + + Range e = tree.getRange(new TestRange(7, 9)); + TestRange removedTestRange2 = new TestRange(7, 9); + Range removedRange2 = tree.getRange(removedTestRange2); + + tree.add(a); + tree.add(removedRange1); + tree.add(c); + tree.add(d); + tree.add(e); + tree.add(removedRange2); + + var connectedRanges = + new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); + assertThat(connectedRanges).hasSize(3); + + assertThat(connectedRanges.get(0)).containsExactly(a.getValue(), removedTestRange1, c.getValue()); + assertThat(connectedRanges.get(0).hasOverlap()).isTrue(); + assertThat(connectedRanges.get(0).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRanges.get(0).getMaximumOverlap()).isEqualTo(2); + + assertThat(connectedRanges.get(1)).containsExactly(d.getValue()); + assertThat(connectedRanges.get(1).hasOverlap()).isFalse(); + assertThat(connectedRanges.get(1).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRanges.get(1).getMaximumOverlap()).isEqualTo(1); + + assertThat(connectedRanges.get(2)).containsExactly(e.getValue(), removedTestRange2); + assertThat(connectedRanges.get(2).hasOverlap()).isTrue(); + assertThat(connectedRanges.get(2).getMinimumOverlap()).isEqualTo(2); + assertThat(connectedRanges.get(2).getMaximumOverlap()).isEqualTo(2); + + verifyGaps(tree); + + // Simulate changing planning variables + removedTestRange1.setStart(0); + removedTestRange1.setEnd(10); + + tree.remove(removedRange1); + + connectedRanges = new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); + assertThat(connectedRanges).hasSize(3); + + assertThat(connectedRanges.get(0)).containsExactly(a.getValue(), c.getValue()); + assertThat(connectedRanges.get(0).hasOverlap()).isFalse(); + assertThat(connectedRanges.get(0).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRanges.get(0).getMaximumOverlap()).isEqualTo(1); + + assertThat(connectedRanges.get(1)).containsExactly(d.getValue()); + assertThat(connectedRanges.get(1).hasOverlap()).isFalse(); + assertThat(connectedRanges.get(1).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRanges.get(1).getMaximumOverlap()).isEqualTo(1); + + assertThat(connectedRanges.get(2)).containsExactly(e.getValue(), removedTestRange2); + assertThat(connectedRanges.get(2).hasOverlap()).isTrue(); + assertThat(connectedRanges.get(2).getMinimumOverlap()).isEqualTo(2); + assertThat(connectedRanges.get(2).getMaximumOverlap()).isEqualTo(2); + + verifyGaps(tree); + + // Simulate changing planning variables + removedTestRange2.setStart(2); + removedTestRange2.setEnd(4); + + tree.remove(removedRange2); + connectedRanges = new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); + assertThat(connectedRanges).hasSize(3); + + assertThat(connectedRanges.get(0)).containsExactly(a.getValue(), c.getValue()); + assertThat(connectedRanges.get(0).hasOverlap()).isFalse(); + assertThat(connectedRanges.get(0).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRanges.get(0).getMaximumOverlap()).isEqualTo(1); + + assertThat(connectedRanges.get(1)).containsExactly(d.getValue()); + assertThat(connectedRanges.get(1).hasOverlap()).isFalse(); + assertThat(connectedRanges.get(1).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRanges.get(1).getMaximumOverlap()).isEqualTo(1); + + assertThat(connectedRanges.get(2)).containsExactly(e.getValue()); + assertThat(connectedRanges.get(2).hasOverlap()).isFalse(); + assertThat(connectedRanges.get(2).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRanges.get(2).getMaximumOverlap()).isEqualTo(1); + + verifyGaps(tree); + Range g = tree.getRange(new TestRange(6, 7)); + tree.add(g); + connectedRanges = new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); + assertThat(connectedRanges).hasSize(2); + + assertThat(connectedRanges.get(0)).containsExactly(a.getValue(), c.getValue()); + assertThat(connectedRanges.get(0).hasOverlap()).isFalse(); + assertThat(connectedRanges.get(0).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRanges.get(0).getMaximumOverlap()).isEqualTo(1); + + assertThat(connectedRanges.get(1)).containsExactly(d.getValue(), g.getValue(), e.getValue()); + assertThat(connectedRanges.get(1).hasOverlap()).isFalse(); + assertThat(connectedRanges.get(1).getMinimumOverlap()).isEqualTo(1); + assertThat(connectedRanges.get(1).getMaximumOverlap()).isEqualTo(1); + } + + void verifyGaps(ConnectedRangeTracker tree) { + var connectedRangeList = + new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); + var gapList = + new IterableList<>(tree.getConnectedRangeChain().getGaps()); + + if (connectedRangeList.size() == 0) { + return; + } + assertThat(gapList).hasSize(connectedRangeList.size() - 1); + for (int i = 0; i < connectedRangeList.size() - 1; i++) { + assertThat(gapList.get(i).getPreviousRangeEnd()).isEqualTo(connectedRangeList.get(i).getEnd()); + assertThat(gapList.get(i).getNextRangeStart()).isEqualTo(connectedRangeList.get(i + 1).getStart()); + assertThat(gapList.get(i).getLength()) + .isEqualTo(connectedRangeList.get(i + 1).getStart() - connectedRangeList.get(i).getEnd()); + } + } + + private static int rangeGapCompare(RangeGap a, + RangeGap b) { + if (a == b) { + return 0; + } + if (a == null || b == null) { + return (a == null) ? -1 : 1; + } + boolean out = Objects.equals(a.getPreviousRangeEnd(), b.getPreviousRangeEnd()) && + Objects.equals(a.getNextRangeStart(), b.getNextRangeStart()) && + Objects.equals(a.getLength(), b.getLength()); + + if (out) { + return 0; + } + return a.hashCode() - b.hashCode(); + } + + private static int rangeClusterCompare(ConnectedRange a, + ConnectedRange b) { + if (a == b) { + return 0; + } + if (a == null || b == null) { + return (a == null) ? -1 : 1; + } + + if (!(a instanceof ConnectedRangeImpl) || !(b instanceof ConnectedRangeImpl)) { + throw new IllegalArgumentException("Expected (" + a + ") and (" + b + ") to both be ConnectedRangeImpl"); + } + + var first = (ConnectedRangeImpl) a; + var second = (ConnectedRangeImpl) b; + + boolean out = first.getStartSplitPoint().compareTo(second.getStartSplitPoint()) == 0 && + first.getEndSplitPoint().compareTo(second.getEndSplitPoint()) == 0 && + first.getMinimumOverlap() == second.getMinimumOverlap() && + first.getMaximumOverlap() == second.getMaximumOverlap(); + if (out) { + return 0; + } + return first.hashCode() - second.hashCode(); + } + + // Compare the mutable version with the recompute version + @Test + void testRandomRanges() { + Random random = new Random(1); + + for (int i = 0; i < 100; i++) { + Map> rangeToInstanceMap = new HashMap<>(); + TreeSet> splitPoints = new TreeSet<>(); + ConnectedRangeTracker tree = + new ConnectedRangeTracker<>(TestRange::getStart, TestRange::getEnd, (a, b) -> b - a); + for (int j = 0; j < 100; j++) { + // Create a random range + String old = formatConnectedRangeTracker(tree); + int from = random.nextInt(5); + int to = from + random.nextInt(5); + TestRange data = new TestRange(from, to); + Range range = rangeToInstanceMap.computeIfAbsent(data, tree::getRange); + Range treeRange = + new Range<>(data, TestRange::getStart, TestRange::getEnd); + splitPoints.add(treeRange.getStartSplitPoint()); + splitPoints.add(treeRange.getEndSplitPoint()); + + // Get the split points from the set (since those split points have collections) + RangeSplitPoint startSplitPoint = + splitPoints.floor(treeRange.getStartSplitPoint()); + RangeSplitPoint endSplitPoint = splitPoints.floor(treeRange.getEndSplitPoint()); + + // Create the collections if they do not exist + if (startSplitPoint.startpointRangeToCountMap == null) { + startSplitPoint.createCollections(); + } + if (endSplitPoint.endpointRangeToCountMap == null) { + endSplitPoint.createCollections(); + } + + // Either add or remove the range + String op; + if (startSplitPoint.containsRangeStarting(treeRange) && random.nextBoolean()) { + op = "Remove"; + startSplitPoint.removeRangeStartingAtSplitPoint(treeRange); + endSplitPoint.removeRangeEndingAtSplitPoint(treeRange); + if (startSplitPoint.isEmpty()) { + splitPoints.remove(startSplitPoint); + } + if (endSplitPoint.isEmpty()) { + splitPoints.remove(endSplitPoint); + } + tree.remove(range); + } else { + op = "Add"; + startSplitPoint.addRangeStartingAtSplitPoint(treeRange); + endSplitPoint.addRangeEndingAtSplitPoint(treeRange); + tree.add(range); + } + + // Recompute all connected ranges + RangeSplitPoint previous = null; + RangeSplitPoint current = splitPoints.isEmpty() ? null : splitPoints.first(); + List> rangeClusterList = new ArrayList<>(); + List> gapList = new ArrayList<>(); + while (current != null) { + rangeClusterList + .add(ConnectedRangeImpl.getConnectedRangeStartingAt(splitPoints, (a, b) -> a - b, current)); + if (previous != null) { + ConnectedRangeImpl before = + rangeClusterList.get(rangeClusterList.size() - 2); + ConnectedRangeImpl after = + rangeClusterList.get(rangeClusterList.size() - 1); + gapList.add(new RangeGapImpl<>(before, after, after.getStart() - before.getEnd())); + } + previous = current; + current = splitPoints.higher(rangeClusterList.get(rangeClusterList.size() - 1).getEndSplitPoint()); + } + + // Verify the mutable version matches the recompute version + verifyGaps(tree); + assertThat(tree.getConnectedRangeChain().getConnectedRanges()) + .as(op + " range " + range + " to " + old) + .usingElementComparator(ConnectedRangeTrackerTest::rangeClusterCompare) + .containsExactlyElementsOf(rangeClusterList); + assertThat(tree.getConnectedRangeChain().getGaps()) + .as(op + " range " + range + " to " + old) + .usingElementComparator(ConnectedRangeTrackerTest::rangeGapCompare) + .containsExactlyElementsOf(gapList); + } + } + } + + private String formatConnectedRangeTracker(ConnectedRangeTracker rangeTree) { + List> listOfConnectedRanges = new ArrayList<>(); + for (ConnectedRange cluster : rangeTree.getConnectedRangeChain() + .getConnectedRanges()) { + List rangesInCluster = new ArrayList<>(); + for (TestRange range : cluster) { + rangesInCluster.add(range); + } + listOfConnectedRanges.add(rangesInCluster); + } + return listOfConnectedRanges.stream() + .map(cluster -> cluster.stream().map(TestRange::toString).collect(Collectors.joining(",", "[", "]"))) + .collect(Collectors.joining(";", "{", "}")); + } + +} diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTreeTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTreeTest.java deleted file mode 100644 index 540f7ca2d1..0000000000 --- a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/IntervalTreeTest.java +++ /dev/null @@ -1,466 +0,0 @@ -package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Random; -import java.util.TreeSet; -import java.util.stream.Collectors; - -import ai.timefold.solver.core.api.score.stream.common.ConnectedRange; -import ai.timefold.solver.core.api.score.stream.common.RangeGap; - -import org.junit.jupiter.api.Test; - -class IntervalTreeTest { - private static class TestInterval { - int start; - int end; - - public TestInterval(int start, int end) { - this.start = start; - this.end = end; - } - - public int getStart() { - return start; - } - - public int getEnd() { - return end; - } - - public void setStart(int start) { - this.start = start; - } - - public void setEnd(int end) { - this.end = end; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - TestInterval interval = (TestInterval) o; - return start == interval.start && end == interval.end; - } - - @Override - public int hashCode() { - return Objects.hash(start, end); - } - - @Override - public String toString() { - return "(" + start + ", " + end + ")"; - } - } - - private IntervalTree getIntegerIntervalTree() { - return new IntervalTree<>(TestInterval::getStart, TestInterval::getEnd, (a, b) -> b - a); - } - - @Test - void testNonConsecutiveIntervals() { - IntervalTree tree = getIntegerIntervalTree(); - Interval a = tree.getInterval(new TestInterval(0, 2)); - Interval b = tree.getInterval(new TestInterval(3, 4)); - Interval c = tree.getInterval(new TestInterval(5, 7)); - tree.add(a); - tree.add(b); - tree.add(c); - - var clusterList = - new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); - assertThat(clusterList).hasSize(3); - - assertThat(clusterList.get(0)).containsExactly(new TestInterval(0, 2)); - assertThat(clusterList.get(0).hasOverlap()).isFalse(); - assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(1); - - assertThat(clusterList.get(1)).containsExactly(new TestInterval(3, 4)); - assertThat(clusterList.get(1).hasOverlap()).isFalse(); - assertThat(clusterList.get(1).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(1).getMaximumOverlap()).isEqualTo(1); - - assertThat(clusterList.get(2)).containsExactly(new TestInterval(5, 7)); - assertThat(clusterList.get(2).hasOverlap()).isFalse(); - assertThat(clusterList.get(2).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(2).getMaximumOverlap()).isEqualTo(1); - - verifyBreaks(tree); - } - - @Test - void testConsecutiveIntervals() { - IntervalTree tree = getIntegerIntervalTree(); - Interval a = tree.getInterval(new TestInterval(0, 2)); - Interval b = tree.getInterval(new TestInterval(2, 4)); - Interval c = tree.getInterval(new TestInterval(4, 7)); - tree.add(a); - tree.add(b); - tree.add(c); - - var clusterList = - new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); - assertThat(clusterList).hasSize(1); - - assertThat(clusterList.get(0)).containsExactly(new TestInterval(0, 2), new TestInterval(2, 4), new TestInterval(4, 7)); - assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(1); - verifyBreaks(tree); - } - - @Test - void testDuplicateIntervals() { - IntervalTree tree = getIntegerIntervalTree(); - Interval a = tree.getInterval(new TestInterval(0, 2)); - Interval b = tree.getInterval(new TestInterval(4, 7)); - tree.add(a); - tree.add(a); - tree.add(b); - - var clusterList = - new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); - assertThat(clusterList).hasSize(2); - - assertThat(clusterList.get(0)).containsExactly(a.getValue(), a.getValue()); - assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(2); - assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(2); - assertThat(clusterList.get(1)).containsExactly(b.getValue()); - assertThat(clusterList.get(1).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(1).getMaximumOverlap()).isEqualTo(1); - verifyBreaks(tree); - } - - @Test - void testIntervalRemoval() { - IntervalTree tree = getIntegerIntervalTree(); - TestInterval removedInterval = new TestInterval(2, 4); - Interval a = tree.getInterval(new TestInterval(0, 2)); - Interval b = tree.getInterval(removedInterval); - Interval c = tree.getInterval(new TestInterval(4, 7)); - tree.add(a); - tree.add(b); - tree.add(c); - - // Imitate changing planning variables - removedInterval.setStart(10); - removedInterval.setEnd(12); - - tree.remove(b); - - var clusterList = - new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); - assertThat(clusterList).hasSize(2); - - assertThat(clusterList.get(0)).containsExactly(new TestInterval(0, 2)); - assertThat(clusterList.get(1)).containsExactly(new TestInterval(4, 7)); - verifyBreaks(tree); - } - - @Test - void testIntervalAddUpdatingOldBreak() { - IntervalTree tree = getIntegerIntervalTree(); - TestInterval beforeAll = new TestInterval(1, 2); - TestInterval newStart = new TestInterval(3, 8); - TestInterval oldStart = new TestInterval(4, 5); - TestInterval betweenOldAndNewStart = new TestInterval(6, 7); - TestInterval afterAll = new TestInterval(9, 10); - - tree.add(tree.getInterval(beforeAll)); - verifyBreaks(tree); - - tree.add(tree.getInterval(afterAll)); - verifyBreaks(tree); - - tree.add(tree.getInterval(oldStart)); - verifyBreaks(tree); - - tree.add(tree.getInterval(betweenOldAndNewStart)); - verifyBreaks(tree); - - tree.add(tree.getInterval(newStart)); - verifyBreaks(tree); - } - - @Test - void testOverlappingInterval() { - IntervalTree tree = getIntegerIntervalTree(); - Interval a = tree.getInterval(new TestInterval(0, 2)); - TestInterval removedTestInterval1 = new TestInterval(1, 3); - Interval removedInterval1 = tree.getInterval(removedTestInterval1); - Interval c = tree.getInterval(new TestInterval(2, 4)); - - Interval d = tree.getInterval(new TestInterval(5, 6)); - - Interval e = tree.getInterval(new TestInterval(7, 9)); - TestInterval removedTestInterval2 = new TestInterval(7, 9); - Interval removedInterval2 = tree.getInterval(removedTestInterval2); - - tree.add(a); - tree.add(removedInterval1); - tree.add(c); - tree.add(d); - tree.add(e); - tree.add(removedInterval2); - - var clusterList = - new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); - assertThat(clusterList).hasSize(3); - - assertThat(clusterList.get(0)).containsExactly(a.getValue(), removedTestInterval1, c.getValue()); - assertThat(clusterList.get(0).hasOverlap()).isTrue(); - assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(2); - - assertThat(clusterList.get(1)).containsExactly(d.getValue()); - assertThat(clusterList.get(1).hasOverlap()).isFalse(); - assertThat(clusterList.get(1).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(1).getMaximumOverlap()).isEqualTo(1); - - assertThat(clusterList.get(2)).containsExactly(e.getValue(), removedTestInterval2); - assertThat(clusterList.get(2).hasOverlap()).isTrue(); - assertThat(clusterList.get(2).getMinimumOverlap()).isEqualTo(2); - assertThat(clusterList.get(2).getMaximumOverlap()).isEqualTo(2); - - verifyBreaks(tree); - - // Simulate changing planning variables - removedTestInterval1.setStart(0); - removedTestInterval1.setEnd(10); - - tree.remove(removedInterval1); - - clusterList = new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); - assertThat(clusterList).hasSize(3); - - assertThat(clusterList.get(0)).containsExactly(a.getValue(), c.getValue()); - assertThat(clusterList.get(0).hasOverlap()).isFalse(); - assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(1); - - assertThat(clusterList.get(1)).containsExactly(d.getValue()); - assertThat(clusterList.get(1).hasOverlap()).isFalse(); - assertThat(clusterList.get(1).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(1).getMaximumOverlap()).isEqualTo(1); - - assertThat(clusterList.get(2)).containsExactly(e.getValue(), removedTestInterval2); - assertThat(clusterList.get(2).hasOverlap()).isTrue(); - assertThat(clusterList.get(2).getMinimumOverlap()).isEqualTo(2); - assertThat(clusterList.get(2).getMaximumOverlap()).isEqualTo(2); - - verifyBreaks(tree); - - // Simulate changing planning variables - removedTestInterval2.setStart(2); - removedTestInterval2.setEnd(4); - - tree.remove(removedInterval2); - clusterList = new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); - assertThat(clusterList).hasSize(3); - - assertThat(clusterList.get(0)).containsExactly(a.getValue(), c.getValue()); - assertThat(clusterList.get(0).hasOverlap()).isFalse(); - assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(1); - - assertThat(clusterList.get(1)).containsExactly(d.getValue()); - assertThat(clusterList.get(1).hasOverlap()).isFalse(); - assertThat(clusterList.get(1).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(1).getMaximumOverlap()).isEqualTo(1); - - assertThat(clusterList.get(2)).containsExactly(e.getValue()); - assertThat(clusterList.get(2).hasOverlap()).isFalse(); - assertThat(clusterList.get(2).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(2).getMaximumOverlap()).isEqualTo(1); - - verifyBreaks(tree); - Interval g = tree.getInterval(new TestInterval(6, 7)); - tree.add(g); - clusterList = new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); - assertThat(clusterList).hasSize(2); - - assertThat(clusterList.get(0)).containsExactly(a.getValue(), c.getValue()); - assertThat(clusterList.get(0).hasOverlap()).isFalse(); - assertThat(clusterList.get(0).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(0).getMaximumOverlap()).isEqualTo(1); - - assertThat(clusterList.get(1)).containsExactly(d.getValue(), g.getValue(), e.getValue()); - assertThat(clusterList.get(1).hasOverlap()).isFalse(); - assertThat(clusterList.get(1).getMinimumOverlap()).isEqualTo(1); - assertThat(clusterList.get(1).getMaximumOverlap()).isEqualTo(1); - } - - void verifyBreaks(IntervalTree tree) { - var clusterList = - new IterableList<>(tree.getConnectedRangeChain().getConnectedRanges()); - var breakList = - new IterableList<>(tree.getConnectedRangeChain().getGaps()); - - if (clusterList.size() == 0) { - return; - } - assertThat(breakList).hasSize(clusterList.size() - 1); - for (int i = 0; i < clusterList.size() - 1; i++) { - assertThat(breakList.get(i).getPreviousRangeEnd()).isEqualTo(clusterList.get(i).getEnd()); - assertThat(breakList.get(i).getNextRangeStart()).isEqualTo(clusterList.get(i + 1).getStart()); - assertThat(breakList.get(i).getLength()).isEqualTo(clusterList.get(i + 1).getStart() - clusterList.get(i).getEnd()); - } - } - - private static int intervalBreakCompare(RangeGap a, - RangeGap b) { - if (a == b) { - return 0; - } - if (a == null || b == null) { - return (a == null) ? -1 : 1; - } - boolean out = Objects.equals(a.getPreviousRangeEnd(), b.getPreviousRangeEnd()) && - Objects.equals(a.getNextRangeStart(), b.getNextRangeStart()) && - Objects.equals(a.getLength(), b.getLength()); - - if (out) { - return 0; - } - return a.hashCode() - b.hashCode(); - } - - private static int intervalClusterCompare(ConnectedRange a, - ConnectedRange b) { - if (a == b) { - return 0; - } - if (a == null || b == null) { - return (a == null) ? -1 : 1; - } - - if (!(a instanceof ConnectedRangeImpl) || !(b instanceof ConnectedRangeImpl)) { - throw new IllegalArgumentException("Expected (" + a + ") and (" + b + ") to both be IntervalClusterImpl"); - } - - var first = (ConnectedRangeImpl) a; - var second = (ConnectedRangeImpl) b; - - boolean out = first.getStartSplitPoint().compareTo(second.getStartSplitPoint()) == 0 && - first.getEndSplitPoint().compareTo(second.getEndSplitPoint()) == 0 && - first.getMinimumOverlap() == second.getMinimumOverlap() && - first.getMaximumOverlap() == second.getMaximumOverlap(); - if (out) { - return 0; - } - return first.hashCode() - second.hashCode(); - } - - // Compare the mutable version with the recompute version - @Test - void testRandomIntervals() { - Random random = new Random(1); - - for (int i = 0; i < 100; i++) { - Map> intervalToInstanceMap = new HashMap<>(); - TreeSet> splitPoints = new TreeSet<>(); - IntervalTree tree = - new IntervalTree<>(TestInterval::getStart, TestInterval::getEnd, (a, b) -> b - a); - for (int j = 0; j < 100; j++) { - // Create a random interval - String old = formatIntervalTree(tree); - int from = random.nextInt(5); - int to = from + random.nextInt(5); - TestInterval data = new TestInterval(from, to); - Interval interval = intervalToInstanceMap.computeIfAbsent(data, tree::getInterval); - Interval treeInterval = - new Interval<>(data, TestInterval::getStart, TestInterval::getEnd); - splitPoints.add(treeInterval.getStartSplitPoint()); - splitPoints.add(treeInterval.getEndSplitPoint()); - - // Get the split points from the set (since those split points have collections) - IntervalSplitPoint startSplitPoint = - splitPoints.floor(treeInterval.getStartSplitPoint()); - IntervalSplitPoint endSplitPoint = splitPoints.floor(treeInterval.getEndSplitPoint()); - - // Create the collections if they do not exist - if (startSplitPoint.startIntervalToCountMap == null) { - startSplitPoint.createCollections(); - } - if (endSplitPoint.endIntervalToCountMap == null) { - endSplitPoint.createCollections(); - } - - // Either add or remove the interval - String op; - if (startSplitPoint.containsIntervalStarting(treeInterval) && random.nextBoolean()) { - op = "Remove"; - startSplitPoint.removeIntervalStartingAtSplitPoint(treeInterval); - endSplitPoint.removeIntervalEndingAtSplitPoint(treeInterval); - if (startSplitPoint.isEmpty()) { - splitPoints.remove(startSplitPoint); - } - if (endSplitPoint.isEmpty()) { - splitPoints.remove(endSplitPoint); - } - tree.remove(interval); - } else { - op = "Add"; - startSplitPoint.addIntervalStartingAtSplitPoint(treeInterval); - endSplitPoint.addIntervalEndingAtSplitPoint(treeInterval); - tree.add(interval); - } - - // Recompute all interval clusters - IntervalSplitPoint previous = null; - IntervalSplitPoint current = splitPoints.isEmpty() ? null : splitPoints.first(); - List> intervalClusterList = new ArrayList<>(); - List> breakList = new ArrayList<>(); - while (current != null) { - intervalClusterList.add(new ConnectedRangeImpl<>(splitPoints, (a, b) -> a - b, current)); - if (previous != null) { - ConnectedRangeImpl before = - intervalClusterList.get(intervalClusterList.size() - 2); - ConnectedRangeImpl after = - intervalClusterList.get(intervalClusterList.size() - 1); - breakList.add(new RangeGapImpl<>(before, after, after.getStart() - before.getEnd())); - } - previous = current; - current = splitPoints.higher(intervalClusterList.get(intervalClusterList.size() - 1).getEndSplitPoint()); - } - - // Verify the mutable version matches the recompute version - verifyBreaks(tree); - assertThat(tree.getConnectedRangeChain().getConnectedRanges()) - .as(op + " interval " + interval + " to " + old) - .usingElementComparator(IntervalTreeTest::intervalClusterCompare) - .containsExactlyElementsOf(intervalClusterList); - assertThat(tree.getConnectedRangeChain().getGaps()) - .as(op + " interval " + interval + " to " + old) - .usingElementComparator(IntervalTreeTest::intervalBreakCompare) - .containsExactlyElementsOf(breakList); - } - } - } - - private String formatIntervalTree(IntervalTree intervalTree) { - List> listOfIntervalClusters = new ArrayList<>(); - for (ConnectedRange cluster : intervalTree.getConnectedRangeChain() - .getConnectedRanges()) { - List intervalsInCluster = new ArrayList<>(); - for (TestInterval interval : cluster) { - intervalsInCluster.add(interval); - } - listOfIntervalClusters.add(intervalsInCluster); - } - return listOfIntervalClusters.stream() - .map(cluster -> cluster.stream().map(TestInterval::toString).collect(Collectors.joining(",", "[", "]"))) - .collect(Collectors.joining(";", "{", "}")); - } - -} From ea60b9d483447f4a560b4bb86158ef0f7920a617 Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Wed, 24 Apr 2024 12:21:20 -0400 Subject: [PATCH 9/9] chore: Rename missed methods and variables --- .../ConnectedRangeChainImpl.java | 16 +++++------ .../connected_ranges/RangeGapImpl.java | 28 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java index 0fb00196f2..bb55f8c7b3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/ConnectedRangeChainImpl.java @@ -76,7 +76,7 @@ void addRange(Range range) { firstIntersectedConnectedRange); var nextGap = startSplitPointToNextGap.get(firstIntersectedConnectedRange.getStartSplitPoint()); if (nextGap != null) { - nextGap.setPreviousCluster(firstIntersectedConnectedRange); + nextGap.setPreviousConnectedRange(firstIntersectedConnectedRange); nextGap.setLength(differenceFunction.apply(nextGap.getPreviousRangeEnd(), nextGap.getNextRangeStart())); } @@ -149,7 +149,7 @@ private void removeSpannedGapsAndUpdateIntersectedGaps(Range ran // ----------- //---- ------ ----- var finalGap = intersectedRangeGapMap.lastEntry().getValue(); - finalGap.setPreviousCluster(connectedRange); + finalGap.setPreviousConnectedRange(connectedRange); finalGap.setLength( differenceFunction.apply(finalGap.getPreviousRangeEnd(), finalGap.getNextRangeStart())); @@ -163,7 +163,7 @@ private void removeSpannedGapsAndUpdateIntersectedGaps(Range ran //---- ----- ----- var previousGapEntry = intersectedRangeGapMap.firstEntry(); var previousGap = previousGapEntry.getValue(); - previousGap.setNextCluster(connectedRange); + previousGap.setNextConnectedRange(connectedRange); previousGap.setLength( differenceFunction.apply(previousGap.getPreviousRangeEnd(), connectedRange.getStart())); intersectedRangeGapMap.clear(); @@ -181,7 +181,7 @@ private void removeSpannedGapsAndUpdateIntersectedGaps(Range ran var previousGapEntry = intersectedRangeGapMap.firstEntry(); var previousGap = previousGapEntry.getValue(); - previousGap.setNextCluster(connectedRange); + previousGap.setNextConnectedRange(connectedRange); previousGap.setLength( differenceFunction.apply(previousGap.getPreviousRangeEnd(), connectedRange.getStart())); @@ -211,7 +211,7 @@ void removeRange(Range range) { while (iterator.hasNext()) { var newConnectedRange = iterator.next(); if (previousGap != null) { - previousGap.setNextCluster(newConnectedRange); + previousGap.setNextConnectedRange(newConnectedRange); previousGap.setLength(differenceFunction.apply(previousGap.getPreviousConnectedRange().getEnd(), newConnectedRange.getStart())); startSplitPointToNextGap @@ -224,15 +224,15 @@ void removeRange(Range range) { } if (nextConnectedRangeEntry != null && previousGap != null) { - previousGap.setNextCluster(nextConnectedRangeEntry.getValue()); + previousGap.setNextConnectedRange(nextConnectedRangeEntry.getValue()); previousGap.setLength(differenceFunction.apply(previousConnectedRange.getEnd(), nextConnectedRangeEntry.getValue().getStart())); startSplitPointToNextGap.put(previousConnectedRange.getStartSplitPoint(), previousGap); } else if (previousGapEntry != null && previousGap == previousGapEntry.getValue()) { - // i.e. range was the last range in the cluster, + // i.e. range was the last range in the connected range, // (previousGap == previousGapEntry.getValue()), - // and there is no range cluster after it + // and there is no connected range after it // (previousGap != null as previousGapEntry != null, // so it must be the case nextConnectedRangeEntry == null) startSplitPointToNextGap.remove(previousGapEntry.getKey()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java index fa996b4408..efae2257b5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeGapImpl.java @@ -5,33 +5,33 @@ final class RangeGapImpl, Difference_ extends Comparable> implements RangeGap { - private ConnectedRange previousCluster; - private ConnectedRange nextCluster; + private ConnectedRange previousConnectedRange; + private ConnectedRange nextConnectedRange; private Difference_ length; - RangeGapImpl(ConnectedRange previousCluster, - ConnectedRange nextCluster, Difference_ length) { - this.previousCluster = previousCluster; - this.nextCluster = nextCluster; + RangeGapImpl(ConnectedRange previousConnectedRange, + ConnectedRange nextConnectedRange, Difference_ length) { + this.previousConnectedRange = previousConnectedRange; + this.nextConnectedRange = nextConnectedRange; this.length = length; } ConnectedRange getPreviousConnectedRange() { - return previousCluster; + return previousConnectedRange; } ConnectedRange getNextConnectedRange() { - return nextCluster; + return nextConnectedRange; } @Override public Point_ getPreviousRangeEnd() { - return previousCluster.getEnd(); + return previousConnectedRange.getEnd(); } @Override public Point_ getNextRangeStart() { - return nextCluster.getStart(); + return nextConnectedRange.getStart(); } @Override @@ -39,12 +39,12 @@ public Difference_ getLength() { return length; } - void setPreviousCluster(ConnectedRange previousCluster) { - this.previousCluster = previousCluster; + void setPreviousConnectedRange(ConnectedRange previousConnectedRange) { + this.previousConnectedRange = previousConnectedRange; } - void setNextCluster(ConnectedRange nextCluster) { - this.nextCluster = nextCluster; + void setNextConnectedRange(ConnectedRange nextConnectedRange) { + this.nextConnectedRange = nextConnectedRange; } void setLength(Difference_ length) {