From 274dadd231e057954683704214d1708c59bd0858 Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Tue, 27 Aug 2024 11:11:52 -0400 Subject: [PATCH] fix: Use inserted range when retracting range in `toConnectedRanges` (#1056) The `ConnectedRangesCalculator` incorrectly assumes the range for each value is constant. The fix is to save the range on insert, and then pass the saved range on retract. The other calculators do not have this problem, because either: - Their input should be immutable (ex: number) - They used a map in their internal data structure that saved the original value Fixes #953 --- .../collector/ConnectedRangesCalculator.java | 14 +++-- .../collector/IntDistinctCountCalculator.java | 11 ++-- .../LongDistinctCountCalculator.java | 11 ++-- .../stream/collector/ObjectCalculator.java | 6 +-- .../collector/ReferenceAverageCalculator.java | 9 ++-- .../collector/ReferenceSumCalculator.java | 9 ++-- .../stream/collector/SequenceCalculator.java | 5 +- .../bi/AverageReferenceBiCollector.java | 2 +- .../ConnectedRangesBiConstraintCollector.java | 3 +- ...ecutiveSequencesBiConstraintCollector.java | 2 +- .../bi/CountDistinctIntBiCollector.java | 2 +- .../bi/CountDistinctLongBiCollector.java | 2 +- .../bi/ObjectCalculatorBiCollector.java | 10 ++-- .../collector/bi/SumReferenceBiCollector.java | 2 +- .../connected_ranges/RangeGapImpl.java | 17 ++++++ .../quad/AverageReferenceQuadCollector.java | 3 +- ...onnectedRangesQuadConstraintCollector.java | 3 +- ...utiveSequencesQuadConstraintCollector.java | 2 +- .../quad/CountDistinctIntQuadCollector.java | 2 +- .../quad/CountDistinctLongQuadCollector.java | 2 +- .../quad/ObjectCalculatorQuadCollector.java | 10 ++-- .../quad/SumReferenceQuadCollector.java | 2 +- .../tri/AverageReferenceTriCollector.java | 3 +- ...ConnectedRangesTriConstraintCollector.java | 3 +- ...cutiveSequencesTriConstraintCollector.java | 2 +- .../tri/CountDistinctIntTriCollector.java | 2 +- .../tri/CountDistinctLongTriCollector.java | 2 +- .../tri/ObjectCalculatorTriCollector.java | 10 ++-- .../tri/SumReferenceTriCollector.java | 2 +- .../uni/AverageReferenceUniCollector.java | 2 +- ...ConnectedRangesUniConstraintCollector.java | 3 +- ...cutiveSequencesUniConstraintCollector.java | 2 +- .../uni/CountDistinctIntUniCollector.java | 2 +- .../uni/CountDistinctLongUniCollector.java | 2 +- .../uni/ObjectCalculatorUniCollector.java | 10 ++-- .../uni/SumReferenceUniCollector.java | 2 +- .../AbstractConstraintCollectorsTest.java | 52 +++++++++++++++++++ .../bi/InnerBiConstraintCollectorsTest.java | 45 ++++++++++++++-- .../InnerQuadConstraintCollectorsTest.java | 47 ++++++++++++++--- .../tri/InnerTriConstraintCollectorsTest.java | 47 ++++++++++++++--- .../uni/InnerUniConstraintCollectorsTest.java | 45 ++++++++++++++-- 41 files changed, 318 insertions(+), 94 deletions(-) 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 c49e11f76f..9395893bbf 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 @@ -5,9 +5,11 @@ import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.impl.score.stream.collector.connected_ranges.ConnectedRangeTracker; +import ai.timefold.solver.core.impl.score.stream.collector.connected_ranges.Range; public final class ConnectedRangesCalculator, Difference_ extends Comparable> - implements ObjectCalculator> { + implements + ObjectCalculator, Range> { private final ConnectedRangeTracker context; @@ -21,13 +23,15 @@ public ConnectedRangesCalculator(Function s } @Override - public void insert(Interval_ result) { - context.add(context.getRange(result)); + public Range insert(Interval_ result) { + final var saved = context.getRange(result); + context.add(saved); + return saved; } @Override - public void retract(Interval_ result) { - context.remove(context.getRange(result)); + public void retract(Range range) { + context.remove(range); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/IntDistinctCountCalculator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/IntDistinctCountCalculator.java index ea6866127d..0552e7dce1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/IntDistinctCountCalculator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/IntDistinctCountCalculator.java @@ -5,18 +5,19 @@ import ai.timefold.solver.core.impl.util.MutableInt; -public final class IntDistinctCountCalculator implements ObjectCalculator { +public final class IntDistinctCountCalculator implements ObjectCalculator { private final Map countMap = new HashMap<>(); @Override - public void insert(Input_ input) { + public Input_ insert(Input_ input) { countMap.computeIfAbsent(input, ignored -> new MutableInt()).increment(); + return input; } @Override - public void retract(Input_ input) { - if (countMap.get(input).decrement() == 0) { - countMap.remove(input); + public void retract(Input_ mapped) { + if (countMap.get(mapped).decrement() == 0) { + countMap.remove(mapped); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/LongDistinctCountCalculator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/LongDistinctCountCalculator.java index e7f9332424..0a5eb0706f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/LongDistinctCountCalculator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/LongDistinctCountCalculator.java @@ -5,18 +5,19 @@ import ai.timefold.solver.core.impl.util.MutableInt; -public final class LongDistinctCountCalculator implements ObjectCalculator { +public final class LongDistinctCountCalculator implements ObjectCalculator { private final Map countMap = new HashMap<>(); @Override - public void insert(Input_ input) { + public Input_ insert(Input_ input) { countMap.computeIfAbsent(input, ignored -> new MutableInt()).increment(); + return input; } @Override - public void retract(Input_ input) { - if (countMap.get(input).decrement() == 0) { - countMap.remove(input); + public void retract(Input_ mapped) { + if (countMap.get(mapped).decrement() == 0) { + countMap.remove(mapped); } } 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 d3007f2c25..ae1927285f 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,11 +1,11 @@ package ai.timefold.solver.core.impl.score.stream.collector; -public sealed interface ObjectCalculator +public sealed interface ObjectCalculator permits ConnectedRangesCalculator, IntDistinctCountCalculator, LongDistinctCountCalculator, ReferenceAverageCalculator, ReferenceSumCalculator, SequenceCalculator { - void insert(Input_ input); + Mapped_ insert(Input_ input); - void retract(Input_ input); + void retract(Mapped_ mapped); Output_ result(); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ReferenceAverageCalculator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ReferenceAverageCalculator.java index 1cbf2cd162..ce4963b1c8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ReferenceAverageCalculator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ReferenceAverageCalculator.java @@ -8,7 +8,7 @@ import java.util.function.BinaryOperator; import java.util.function.Supplier; -public final class ReferenceAverageCalculator implements ObjectCalculator { +public final class ReferenceAverageCalculator implements ObjectCalculator { int count = 0; Input_ sum; final BinaryOperator adder; @@ -51,15 +51,16 @@ public static Supplier> duration( } @Override - public void insert(Input_ input) { + public Input_ insert(Input_ input) { count++; sum = adder.apply(sum, input); + return input; } @Override - public void retract(Input_ input) { + public void retract(Input_ mapped) { count--; - sum = subtractor.apply(sum, input); + sum = subtractor.apply(sum, mapped); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ReferenceSumCalculator.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ReferenceSumCalculator.java index 95aeedef7b..dc361f153b 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ReferenceSumCalculator.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/ReferenceSumCalculator.java @@ -2,7 +2,7 @@ import java.util.function.BinaryOperator; -public final class ReferenceSumCalculator implements ObjectCalculator { +public final class ReferenceSumCalculator implements ObjectCalculator { private Result_ current; private final BinaryOperator adder; private final BinaryOperator subtractor; @@ -14,13 +14,14 @@ public ReferenceSumCalculator(Result_ current, BinaryOperator adder, Bi } @Override - public void insert(Result_ input) { + public Result_ insert(Result_ input) { current = adder.apply(current, input); + return input; } @Override - public void retract(Result_ input) { - current = subtractor.apply(current, input); + public void retract(Result_ mapped) { + current = subtractor.apply(current, mapped); } @Override 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 54db9dc5fb..2463fe4166 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 @@ -7,7 +7,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.consecutive.ConsecutiveSetTree; public final class SequenceCalculator - implements ObjectCalculator> { + implements ObjectCalculator, Result_> { private final ConsecutiveSetTree context = new ConsecutiveSetTree<>( (Integer a, Integer b) -> b - a, @@ -20,9 +20,10 @@ public SequenceCalculator(ToIntFunction indexMap) { } @Override - public void insert(Result_ result) { + public Result_ insert(Result_ result) { var value = indexMap.applyAsInt(result); context.add(result, value); + return result; } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/AverageReferenceBiCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/AverageReferenceBiCollector.java index 8ffb3ad19b..e11cb7a9ce 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/AverageReferenceBiCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/AverageReferenceBiCollector.java @@ -7,7 +7,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.ReferenceAverageCalculator; final class AverageReferenceBiCollector - extends ObjectCalculatorBiCollector> { + extends ObjectCalculatorBiCollector> { private final Supplier> calculatorSupplier; AverageReferenceBiCollector(BiFunction mapper, diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConnectedRangesBiConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConnectedRangesBiConstraintCollector.java index 5ec943825a..cd7efd836c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConnectedRangesBiConstraintCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConnectedRangesBiConstraintCollector.java @@ -7,10 +7,11 @@ import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.impl.score.stream.collector.ConnectedRangesCalculator; +import ai.timefold.solver.core.impl.score.stream.collector.connected_ranges.Range; final class ConnectedRangesBiConstraintCollector, Difference_ extends Comparable> extends - ObjectCalculatorBiCollector, ConnectedRangesCalculator> { + ObjectCalculatorBiCollector, Range, ConnectedRangesCalculator> { private final Function startMap; private final Function endMap; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConsecutiveSequencesBiConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConsecutiveSequencesBiConstraintCollector.java index 775db3c578..4831d7bca4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConsecutiveSequencesBiConstraintCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/ConsecutiveSequencesBiConstraintCollector.java @@ -10,7 +10,7 @@ final class ConsecutiveSequencesBiConstraintCollector extends - ObjectCalculatorBiCollector, SequenceCalculator> { + ObjectCalculatorBiCollector, Result_, SequenceCalculator> { private final ToIntFunction indexMap; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/CountDistinctIntBiCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/CountDistinctIntBiCollector.java index 68c340fc7d..8775aa525a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/CountDistinctIntBiCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/CountDistinctIntBiCollector.java @@ -6,7 +6,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.IntDistinctCountCalculator; final class CountDistinctIntBiCollector - extends ObjectCalculatorBiCollector> { + extends ObjectCalculatorBiCollector> { CountDistinctIntBiCollector(BiFunction mapper) { super(mapper); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/CountDistinctLongBiCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/CountDistinctLongBiCollector.java index 20adc32787..4c83171952 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/CountDistinctLongBiCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/CountDistinctLongBiCollector.java @@ -6,7 +6,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.LongDistinctCountCalculator; final class CountDistinctLongBiCollector - extends ObjectCalculatorBiCollector> { + extends ObjectCalculatorBiCollector> { CountDistinctLongBiCollector(BiFunction mapper) { super(mapper); } 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 2e197c5138..418ac70bea 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 @@ -8,7 +8,7 @@ import ai.timefold.solver.core.api.score.stream.bi.BiConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.ObjectCalculator; -abstract sealed class ObjectCalculatorBiCollector> +abstract sealed class ObjectCalculatorBiCollector> implements BiConstraintCollector permits AverageReferenceBiCollector, ConnectedRangesBiConstraintCollector, ConsecutiveSequencesBiConstraintCollector, CountDistinctIntBiCollector, CountDistinctLongBiCollector, SumReferenceBiCollector { @@ -21,9 +21,9 @@ public ObjectCalculatorBiCollector(BiFunction accumulator() { return (calculator, a, b) -> { - final Input_ mapped = mapper.apply(a, b); - calculator.insert(mapped); - return () -> calculator.retract(mapped); + final var mapped = mapper.apply(a, b); + final var saved = calculator.insert(mapped); + return () -> calculator.retract(saved); }; } @@ -38,7 +38,7 @@ public boolean equals(Object object) { return true; if (object == null || getClass() != object.getClass()) return false; - var that = (ObjectCalculatorBiCollector) object; + var that = (ObjectCalculatorBiCollector) object; return Objects.equals(mapper, that.mapper); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/SumReferenceBiCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/SumReferenceBiCollector.java index 2f8a7e9717..25e499ea66 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/SumReferenceBiCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/bi/SumReferenceBiCollector.java @@ -8,7 +8,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.ReferenceSumCalculator; final class SumReferenceBiCollector - extends ObjectCalculatorBiCollector> { + extends ObjectCalculatorBiCollector> { private final Result_ zero; private final BinaryOperator adder; private final BinaryOperator subtractor; 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 efae2257b5..8edd9d1d7e 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 @@ -1,5 +1,7 @@ package ai.timefold.solver.core.impl.score.stream.collector.connected_ranges; +import java.util.Objects; + import ai.timefold.solver.core.api.score.stream.common.ConnectedRange; import ai.timefold.solver.core.api.score.stream.common.RangeGap; @@ -51,6 +53,21 @@ void setLength(Difference_ length) { this.length = length; } + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof RangeGapImpl rangeGap)) + return false; + return Objects.equals(getPreviousRangeEnd(), rangeGap.getPreviousRangeEnd()) && + Objects.equals(getNextRangeStart(), rangeGap.getNextRangeStart()); + } + + @Override + public int hashCode() { + return Objects.hash(getPreviousRangeEnd(), getNextRangeStart()); + } + @Override public String toString() { return "RangeGap{" + diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/AverageReferenceQuadCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/AverageReferenceQuadCollector.java index c4e3c7fabc..c1e8998dde 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/AverageReferenceQuadCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/AverageReferenceQuadCollector.java @@ -7,7 +7,8 @@ import ai.timefold.solver.core.impl.score.stream.collector.ReferenceAverageCalculator; final class AverageReferenceQuadCollector - extends ObjectCalculatorQuadCollector> { + extends + ObjectCalculatorQuadCollector> { private final Supplier> calculatorSupplier; AverageReferenceQuadCollector(QuadFunction mapper, diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConnectedRangesQuadConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConnectedRangesQuadConstraintCollector.java index 4902c48047..cf241a37bf 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConnectedRangesQuadConstraintCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConnectedRangesQuadConstraintCollector.java @@ -8,10 +8,11 @@ import ai.timefold.solver.core.api.function.QuadFunction; import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.impl.score.stream.collector.ConnectedRangesCalculator; +import ai.timefold.solver.core.impl.score.stream.collector.connected_ranges.Range; final class ConnectedRangesQuadConstraintCollector, Difference_ extends Comparable> extends - ObjectCalculatorQuadCollector, ConnectedRangesCalculator> { + ObjectCalculatorQuadCollector, Range, ConnectedRangesCalculator> { private final Function startMap; private final Function endMap; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConsecutiveSequencesQuadConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConsecutiveSequencesQuadConstraintCollector.java index 808b521783..48378a9817 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConsecutiveSequencesQuadConstraintCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/ConsecutiveSequencesQuadConstraintCollector.java @@ -10,7 +10,7 @@ final class ConsecutiveSequencesQuadConstraintCollector extends - ObjectCalculatorQuadCollector, SequenceCalculator> { + ObjectCalculatorQuadCollector, Result_, SequenceCalculator> { private final ToIntFunction indexMap; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/CountDistinctIntQuadCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/CountDistinctIntQuadCollector.java index e5c1907eba..cb16d5bb31 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/CountDistinctIntQuadCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/CountDistinctIntQuadCollector.java @@ -6,7 +6,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.IntDistinctCountCalculator; final class CountDistinctIntQuadCollector - extends ObjectCalculatorQuadCollector> { + extends ObjectCalculatorQuadCollector> { CountDistinctIntQuadCollector(QuadFunction mapper) { super(mapper); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/CountDistinctLongQuadCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/CountDistinctLongQuadCollector.java index 16f1cb7d3b..f1dcc17573 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/CountDistinctLongQuadCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/CountDistinctLongQuadCollector.java @@ -6,7 +6,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.LongDistinctCountCalculator; final class CountDistinctLongQuadCollector - extends ObjectCalculatorQuadCollector> { + extends ObjectCalculatorQuadCollector> { CountDistinctLongQuadCollector(QuadFunction mapper) { super(mapper); } 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 ee508a4a8f..78c300be1a 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 @@ -8,7 +8,7 @@ import ai.timefold.solver.core.api.score.stream.quad.QuadConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.ObjectCalculator; -abstract sealed class ObjectCalculatorQuadCollector> +abstract sealed class ObjectCalculatorQuadCollector> implements QuadConstraintCollector permits AverageReferenceQuadCollector, ConnectedRangesQuadConstraintCollector, ConsecutiveSequencesQuadConstraintCollector, CountDistinctIntQuadCollector, CountDistinctLongQuadCollector, @@ -23,9 +23,9 @@ public ObjectCalculatorQuadCollector(QuadFunction accumulator() { return (calculator, a, b, c, d) -> { - final Input_ mapped = mapper.apply(a, b, c, d); - calculator.insert(mapped); - return () -> calculator.retract(mapped); + final var mapped = mapper.apply(a, b, c, d); + final var saved = calculator.insert(mapped); + return () -> calculator.retract(saved); }; } @@ -40,7 +40,7 @@ public boolean equals(Object object) { return true; if (object == null || getClass() != object.getClass()) return false; - var that = (ObjectCalculatorQuadCollector) object; + var that = (ObjectCalculatorQuadCollector) object; return Objects.equals(mapper, that.mapper); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/SumReferenceQuadCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/SumReferenceQuadCollector.java index 03fb2c3d11..da49633c8e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/SumReferenceQuadCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/quad/SumReferenceQuadCollector.java @@ -8,7 +8,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.ReferenceSumCalculator; final class SumReferenceQuadCollector - extends ObjectCalculatorQuadCollector> { + extends ObjectCalculatorQuadCollector> { private final Result_ zero; private final BinaryOperator adder; private final BinaryOperator subtractor; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/AverageReferenceTriCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/AverageReferenceTriCollector.java index 15a3438bcc..7c16f7cbf0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/AverageReferenceTriCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/AverageReferenceTriCollector.java @@ -7,7 +7,8 @@ import ai.timefold.solver.core.impl.score.stream.collector.ReferenceAverageCalculator; final class AverageReferenceTriCollector - extends ObjectCalculatorTriCollector> { + extends + ObjectCalculatorTriCollector> { private final Supplier> calculatorSupplier; AverageReferenceTriCollector(TriFunction mapper, diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConnectedRangesTriConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConnectedRangesTriConstraintCollector.java index 17851a680e..17a5299dda 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConnectedRangesTriConstraintCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConnectedRangesTriConstraintCollector.java @@ -8,10 +8,11 @@ import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.impl.score.stream.collector.ConnectedRangesCalculator; +import ai.timefold.solver.core.impl.score.stream.collector.connected_ranges.Range; final class ConnectedRangesTriConstraintCollector, Difference_ extends Comparable> extends - ObjectCalculatorTriCollector, ConnectedRangesCalculator> { + ObjectCalculatorTriCollector, Range, ConnectedRangesCalculator> { private final Function startMap; private final Function endMap; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConsecutiveSequencesTriConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConsecutiveSequencesTriConstraintCollector.java index eabbb1b384..b1253b0f82 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConsecutiveSequencesTriConstraintCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/ConsecutiveSequencesTriConstraintCollector.java @@ -10,7 +10,7 @@ final class ConsecutiveSequencesTriConstraintCollector extends - ObjectCalculatorTriCollector, SequenceCalculator> { + ObjectCalculatorTriCollector, Result_, SequenceCalculator> { private final ToIntFunction indexMap; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/CountDistinctIntTriCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/CountDistinctIntTriCollector.java index 03a739e02b..e798f38e4d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/CountDistinctIntTriCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/CountDistinctIntTriCollector.java @@ -6,7 +6,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.IntDistinctCountCalculator; final class CountDistinctIntTriCollector - extends ObjectCalculatorTriCollector> { + extends ObjectCalculatorTriCollector> { CountDistinctIntTriCollector(TriFunction mapper) { super(mapper); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/CountDistinctLongTriCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/CountDistinctLongTriCollector.java index 49a57d379e..69e07017a7 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/CountDistinctLongTriCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/CountDistinctLongTriCollector.java @@ -6,7 +6,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.LongDistinctCountCalculator; final class CountDistinctLongTriCollector - extends ObjectCalculatorTriCollector> { + extends ObjectCalculatorTriCollector> { CountDistinctLongTriCollector(TriFunction mapper) { super(mapper); } 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 d57b95b9f5..44484d0cb0 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 @@ -8,7 +8,7 @@ import ai.timefold.solver.core.api.score.stream.tri.TriConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.ObjectCalculator; -abstract sealed class ObjectCalculatorTriCollector> +abstract sealed class ObjectCalculatorTriCollector> implements TriConstraintCollector permits AverageReferenceTriCollector, ConnectedRangesTriConstraintCollector, ConsecutiveSequencesTriConstraintCollector, CountDistinctIntTriCollector, CountDistinctLongTriCollector, SumReferenceTriCollector { @@ -21,9 +21,9 @@ public ObjectCalculatorTriCollector(TriFunction accumulator() { return (calculator, a, b, c) -> { - final Input_ mapped = mapper.apply(a, b, c); - calculator.insert(mapped); - return () -> calculator.retract(mapped); + final var mapped = mapper.apply(a, b, c); + final var saved = calculator.insert(mapped); + return () -> calculator.retract(saved); }; } @@ -38,7 +38,7 @@ public boolean equals(Object object) { return true; if (object == null || getClass() != object.getClass()) return false; - var that = (ObjectCalculatorTriCollector) object; + var that = (ObjectCalculatorTriCollector) object; return Objects.equals(mapper, that.mapper); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/SumReferenceTriCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/SumReferenceTriCollector.java index 056fd4c4b1..4aa7da1937 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/SumReferenceTriCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/tri/SumReferenceTriCollector.java @@ -8,7 +8,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.ReferenceSumCalculator; final class SumReferenceTriCollector - extends ObjectCalculatorTriCollector> { + extends ObjectCalculatorTriCollector> { private final Result_ zero; private final BinaryOperator adder; private final BinaryOperator subtractor; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/AverageReferenceUniCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/AverageReferenceUniCollector.java index cd43e197a2..329d727ba0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/AverageReferenceUniCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/AverageReferenceUniCollector.java @@ -7,7 +7,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.ReferenceAverageCalculator; final class AverageReferenceUniCollector - extends ObjectCalculatorUniCollector> { + extends ObjectCalculatorUniCollector> { private final Supplier> calculatorSupplier; AverageReferenceUniCollector(Function mapper, diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConnectedRangesUniConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConnectedRangesUniConstraintCollector.java index 20557c211b..0eb267265a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConnectedRangesUniConstraintCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConnectedRangesUniConstraintCollector.java @@ -7,10 +7,11 @@ import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.impl.score.stream.collector.ConnectedRangesCalculator; +import ai.timefold.solver.core.impl.score.stream.collector.connected_ranges.Range; final class ConnectedRangesUniConstraintCollector, Difference_ extends Comparable> extends - ObjectCalculatorUniCollector, ConnectedRangesCalculator> { + ObjectCalculatorUniCollector, Range, ConnectedRangesCalculator> { private final Function startMap; private final Function endMap; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConsecutiveSequencesUniConstraintCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConsecutiveSequencesUniConstraintCollector.java index 45f641de5b..7887740990 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConsecutiveSequencesUniConstraintCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/ConsecutiveSequencesUniConstraintCollector.java @@ -9,7 +9,7 @@ import ai.timefold.solver.core.impl.util.ConstantLambdaUtils; final class ConsecutiveSequencesUniConstraintCollector - extends ObjectCalculatorUniCollector, SequenceCalculator> { + extends ObjectCalculatorUniCollector, A, SequenceCalculator> { private final ToIntFunction indexMap; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/CountDistinctIntUniCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/CountDistinctIntUniCollector.java index 3965ed854e..dff9559f03 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/CountDistinctIntUniCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/CountDistinctIntUniCollector.java @@ -6,7 +6,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.IntDistinctCountCalculator; final class CountDistinctIntUniCollector - extends ObjectCalculatorUniCollector> { + extends ObjectCalculatorUniCollector> { CountDistinctIntUniCollector(Function mapper) { super(mapper); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/CountDistinctLongUniCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/CountDistinctLongUniCollector.java index 6c482fac9c..5c9a2c0cac 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/CountDistinctLongUniCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/CountDistinctLongUniCollector.java @@ -6,7 +6,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.LongDistinctCountCalculator; final class CountDistinctLongUniCollector - extends ObjectCalculatorUniCollector> { + extends ObjectCalculatorUniCollector> { CountDistinctLongUniCollector(Function mapper) { super(mapper); } 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 a7f6c56811..746a303da2 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 @@ -7,7 +7,7 @@ import ai.timefold.solver.core.api.score.stream.uni.UniConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.ObjectCalculator; -abstract sealed class ObjectCalculatorUniCollector> +abstract sealed class ObjectCalculatorUniCollector> implements UniConstraintCollector permits AverageReferenceUniCollector, ConnectedRangesUniConstraintCollector, ConsecutiveSequencesUniConstraintCollector, CountDistinctIntUniCollector, CountDistinctLongUniCollector, SumReferenceUniCollector { @@ -21,9 +21,9 @@ public ObjectCalculatorUniCollector(Function mapper @Override public BiFunction accumulator() { return (calculator, a) -> { - final Input_ mapped = mapper.apply(a); - calculator.insert(mapped); - return () -> calculator.retract(mapped); + final var mapped = mapper.apply(a); + final var saved = calculator.insert(mapped); + return () -> calculator.retract(saved); }; } @@ -38,7 +38,7 @@ public boolean equals(Object object) { return true; if (object == null || getClass() != object.getClass()) return false; - var that = (ObjectCalculatorUniCollector) object; + var that = (ObjectCalculatorUniCollector) object; return Objects.equals(mapper, that.mapper); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/SumReferenceUniCollector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/SumReferenceUniCollector.java index 68c2f16432..b5fb706639 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/SumReferenceUniCollector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/uni/SumReferenceUniCollector.java @@ -8,7 +8,7 @@ import ai.timefold.solver.core.impl.score.stream.collector.ReferenceSumCalculator; final class SumReferenceUniCollector - extends ObjectCalculatorUniCollector> { + extends ObjectCalculatorUniCollector> { private final Result_ zero; private final BinaryOperator adder; private final BinaryOperator subtractor; 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 40ca538877..535efdeb38 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 @@ -1,6 +1,7 @@ package ai.timefold.solver.core.impl.score.stream.collector; import java.util.Arrays; +import java.util.Objects; import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.SequenceChain; @@ -110,6 +111,9 @@ public abstract class AbstractConstraintCollectorsTest { @Test public abstract void consecutiveUsage(); + @Test + public abstract void consecutiveUsageDynamic(); + @Test public abstract void loadBalance(); @@ -134,8 +138,56 @@ protected ConnectedRangeChain buildConsecutiveUsage( }).getConnectedRangeChain(); } + protected ConnectedRangeChain buildDynamicConsecutiveUsage(DynamicInterval... data) { + return Arrays.stream(data).collect( + () -> new ConnectedRangeTracker<>(DynamicInterval::getStart, DynamicInterval::getEnd, (a, b) -> b - a), + (tree, datum) -> tree.add(tree.getRange(datum)), + (a, b) -> { + throw new UnsupportedOperationException(); + }).getConnectedRangeChain(); + } + public record Interval(int start, int end) { } + public static final class DynamicInterval { + int start; + + public DynamicInterval(int start) { + this.start = start; + } + + public int getStart() { + return start; + } + + public int getEnd() { + return start + 10; + } + + public void setStart(int start) { + this.start = start; + } + + @Override + public String toString() { + return "DynamicInterval(%d, %d)".formatted(getStart(), getEnd()); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof DynamicInterval that)) + return false; + return start == that.start; + } + + @Override + public int hashCode() { + return Objects.hashCode(start); + } + } + } 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 dd780c1cd5..acc5279c68 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,6 @@ 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.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.LoadBalance; import ai.timefold.solver.core.impl.score.stream.collector.AbstractConstraintCollectorsTest; import ai.timefold.solver.core.impl.util.Pair; @@ -1071,19 +1070,19 @@ public void toConsecutiveSequences() { @Override @Test public void consecutiveUsage() { - BiConstraintCollector> collector = + var collector = ConstraintCollectors.toConnectedRanges(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); + var 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); + var 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); + var 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(); @@ -1096,6 +1095,42 @@ public void consecutiveUsage() { assertResult(collector, container, buildConsecutiveUsage()); } + @Override + @Test + public void consecutiveUsageDynamic() { + var dynamicCollector = + ConstraintCollectors.toConnectedRanges((DynamicInterval a, Object b) -> a, + DynamicInterval::getStart, + DynamicInterval::getEnd, (a, b) -> b - a); + + var first = new DynamicInterval(0); + var second = new DynamicInterval(10); + var third = new DynamicInterval(20); + var container = dynamicCollector.supplier().get(); + + // Add first value, sequence is [[(0, 10)]] + var firstRetractor = accumulate(dynamicCollector, container, first, null); + assertResult(dynamicCollector, container, buildDynamicConsecutiveUsage(new DynamicInterval(0))); + + // Add third value, sequence is [[(0, 10)], [(20, 30)]] + accumulate(dynamicCollector, container, third, null); + assertResult(dynamicCollector, container, + buildDynamicConsecutiveUsage(new DynamicInterval(0), new DynamicInterval(20))); + + // Add second value, sequence is [[(0, 10), (10, 20), (20, 30)]] + accumulate(dynamicCollector, container, second, null); + assertResult(dynamicCollector, container, + buildDynamicConsecutiveUsage(new DynamicInterval(0), new DynamicInterval(10), new DynamicInterval(20))); + + // Change first value, retract it, then re-add it + first.setStart(-5); + firstRetractor.run(); + accumulate(dynamicCollector, container, first, null); + + assertResult(dynamicCollector, container, + buildDynamicConsecutiveUsage(new DynamicInterval(-5), new DynamicInterval(10), new DynamicInterval(20))); + } + @Override @Test public void loadBalance() { 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 4b8abe8529..eaee6f4c34 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,6 @@ import java.util.SortedSet; import ai.timefold.solver.core.api.score.stream.ConstraintCollectors; -import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.LoadBalance; import ai.timefold.solver.core.api.score.stream.quad.QuadConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.AbstractConstraintCollectorsTest; @@ -1124,19 +1123,19 @@ public void toConsecutiveSequences() { @Override @Test public void consecutiveUsage() { - QuadConstraintCollector> collector = - ConstraintCollectors.toConnectedRanges((a, b, c, d) -> new Interval(a, b), + var collector = + ConstraintCollectors.toConnectedRanges((Integer a, Integer b, Object c, Object 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); + var 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); + var 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); + var 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(); @@ -1149,6 +1148,42 @@ public void consecutiveUsage() { assertResult(collector, container, buildConsecutiveUsage()); } + @Override + @Test + public void consecutiveUsageDynamic() { + var dynamicCollector = + ConstraintCollectors.toConnectedRanges((DynamicInterval a, Object b, Object c, Object d) -> a, + DynamicInterval::getStart, + DynamicInterval::getEnd, (a, b) -> b - a); + + var first = new DynamicInterval(0); + var second = new DynamicInterval(10); + var third = new DynamicInterval(20); + var container = dynamicCollector.supplier().get(); + + // Add first value, sequence is [[(0, 10)]] + var firstRetractor = accumulate(dynamicCollector, container, first, null, null, null); + assertResult(dynamicCollector, container, buildDynamicConsecutiveUsage(new DynamicInterval(0))); + + // Add third value, sequence is [[(0, 10)], [(20, 30)]] + accumulate(dynamicCollector, container, third, null, null, null); + assertResult(dynamicCollector, container, + buildDynamicConsecutiveUsage(new DynamicInterval(0), new DynamicInterval(20))); + + // Add second value, sequence is [[(0, 10), (10, 20), (20, 30)]] + accumulate(dynamicCollector, container, second, null, null, null); + assertResult(dynamicCollector, container, + buildDynamicConsecutiveUsage(new DynamicInterval(0), new DynamicInterval(10), new DynamicInterval(20))); + + // Change first value, retract it, then re-add it + first.setStart(-5); + firstRetractor.run(); + accumulate(dynamicCollector, container, first, null, null, null); + + assertResult(dynamicCollector, container, + buildDynamicConsecutiveUsage(new DynamicInterval(-5), new DynamicInterval(10), new DynamicInterval(20))); + } + @Override @Test public void loadBalance() { 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 e31ba9e85a..26154932d1 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,6 @@ import java.util.SortedSet; import ai.timefold.solver.core.api.score.stream.ConstraintCollectors; -import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.LoadBalance; import ai.timefold.solver.core.api.score.stream.tri.TriConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.AbstractConstraintCollectorsTest; @@ -1076,19 +1075,19 @@ public void toConsecutiveSequences() { @Override @Test public void consecutiveUsage() { - TriConstraintCollector> collector = - ConstraintCollectors.toConnectedRanges((a, b, c) -> new Interval(a, b), + var collector = + ConstraintCollectors.toConnectedRanges((Integer a, Integer b, Object 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); + var 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); + var 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); + var 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(); @@ -1101,6 +1100,42 @@ public void consecutiveUsage() { assertResult(collector, container, buildConsecutiveUsage()); } + @Override + @Test + public void consecutiveUsageDynamic() { + var dynamicCollector = + ConstraintCollectors.toConnectedRanges((DynamicInterval a, Object b, Object c) -> a, + DynamicInterval::getStart, + DynamicInterval::getEnd, (a, b) -> b - a); + + var first = new DynamicInterval(0); + var second = new DynamicInterval(10); + var third = new DynamicInterval(20); + var container = dynamicCollector.supplier().get(); + + // Add first value, sequence is [[(0, 10)]] + var firstRetractor = accumulate(dynamicCollector, container, first, null, null); + assertResult(dynamicCollector, container, buildDynamicConsecutiveUsage(new DynamicInterval(0))); + + // Add third value, sequence is [[(0, 10)], [(20, 30)]] + accumulate(dynamicCollector, container, third, null, null); + assertResult(dynamicCollector, container, + buildDynamicConsecutiveUsage(new DynamicInterval(0), new DynamicInterval(20))); + + // Add second value, sequence is [[(0, 10), (10, 20), (20, 30)]] + accumulate(dynamicCollector, container, second, null, null); + assertResult(dynamicCollector, container, + buildDynamicConsecutiveUsage(new DynamicInterval(0), new DynamicInterval(10), new DynamicInterval(20))); + + // Change first value, retract it, then re-add it + first.setStart(-5); + firstRetractor.run(); + accumulate(dynamicCollector, container, first, null, null); + + assertResult(dynamicCollector, container, + buildDynamicConsecutiveUsage(new DynamicInterval(-5), new DynamicInterval(10), new DynamicInterval(20))); + } + @Override @Test public void loadBalance() { 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 22cc919a32..e83ec18d00 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 @@ -33,7 +33,6 @@ 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.ConnectedRangeChain; import ai.timefold.solver.core.api.score.stream.common.LoadBalance; import ai.timefold.solver.core.api.score.stream.uni.UniConstraintCollector; import ai.timefold.solver.core.impl.score.stream.collector.AbstractConstraintCollectorsTest; @@ -1001,19 +1000,19 @@ public void toConsecutiveSequences() { @Override @Test public void consecutiveUsage() { - UniConstraintCollector> collector = + var collector = ConstraintCollectors.toConnectedRanges( 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)); + var 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)); + var 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)); + var 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(); @@ -1026,6 +1025,42 @@ public void consecutiveUsage() { assertResult(collector, container, buildConsecutiveUsage()); } + @Override + @Test + public void consecutiveUsageDynamic() { + var dynamicCollector = + ConstraintCollectors.toConnectedRanges( + DynamicInterval::getStart, + DynamicInterval::getEnd, (a, b) -> b - a); + + var first = new DynamicInterval(0); + var second = new DynamicInterval(10); + var third = new DynamicInterval(20); + var container = dynamicCollector.supplier().get(); + + // Add first value, sequence is [[(0, 10)]] + var firstRetractor = accumulate(dynamicCollector, container, first); + assertResult(dynamicCollector, container, buildDynamicConsecutiveUsage(new DynamicInterval(0))); + + // Add third value, sequence is [[(0, 10)], [(20, 30)]] + accumulate(dynamicCollector, container, third); + assertResult(dynamicCollector, container, + buildDynamicConsecutiveUsage(new DynamicInterval(0), new DynamicInterval(20))); + + // Add second value, sequence is [[(0, 10), (10, 20), (20, 30)]] + accumulate(dynamicCollector, container, second); + assertResult(dynamicCollector, container, + buildDynamicConsecutiveUsage(new DynamicInterval(0), new DynamicInterval(10), new DynamicInterval(20))); + + // Change first value, retract it, then re-add it + first.setStart(-5); + firstRetractor.run(); + accumulate(dynamicCollector, container, first); + + assertResult(dynamicCollector, container, + buildDynamicConsecutiveUsage(new DynamicInterval(-5), new DynamicInterval(10), new DynamicInterval(20))); + } + @Override @Test public void loadBalance() {