From 925e30f1dedb7e1c4277ca27d6da3386f0c0ea97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Tue, 21 Jan 2025 14:20:57 +0100 Subject: [PATCH 1/3] perf: improve performance of Bavet indexing --- .../bi/BavetFilterBiConstraintStream.java | 5 +- .../bavet/bi/ConditionalBiTupleLifecycle.java | 22 - .../bavet/common/AbstractConcatNode.java | 6 +- .../bavet/common/AbstractGroupNode.java | 37 +- .../bavet/common/AbstractIfExistsNode.java | 5 +- .../common/AbstractIndexedIfExistsNode.java | 18 +- .../bavet/common/AbstractIndexedJoinNode.java | 18 +- .../stream/bavet/common/AbstractJoinNode.java | 5 +- .../bavet/common/AbstractTwoInputNode.java | 11 + .../common/AbstractUnindexedIfExistsNode.java | 3 +- .../bavet/common/DynamicPropagationQueue.java | 99 ++- .../impl/score/stream/bavet/common/Group.java | 55 +- .../stream/bavet/common/NodeBuildHelper.java | 38 +- .../bavet/common/StaticPropagationQueue.java | 60 +- .../bavet/common/index/BiKeyFunction.java | 110 ++++ .../bavet/common/index/BiKeysExtractor.java | 8 + .../bavet/common/index/BiMappingFunction.java | 17 + .../bavet/common/index/ComparisonIndexer.java | 8 +- .../bavet/common/index/EqualsIndexer.java | 18 +- .../stream/bavet/common/index/IndexKeys.java | 15 +- .../bavet/common/index/IndexerFactory.java | 589 +++++++----------- .../bavet/common/index/KeyFunction.java | 4 + .../bavet/common/index/KeysExtractor.java | 15 + .../bavet/common/index/ManyIndexKeys.java | 3 - .../bavet/common/index/QuadKeyFunction.java | 110 ++++ .../bavet/common/index/QuadKeysExtractor.java | 8 + .../common/index/QuadMappingFunction.java | 16 + .../bavet/common/index/TriKeyFunction.java | 110 ++++ .../bavet/common/index/TriKeysExtractor.java | 8 + .../common/index/TriMappingFunction.java | 16 + .../bavet/common/index/TwoIndexKeys.java | 23 +- .../bavet/common/index/UniKeyFunction.java | 155 +++++ .../bavet/common/index/UniKeysExtractor.java | 8 + .../common/index/UniMappingFunction.java | 17 + .../AbstractConditionalTupleLifecycle.java | 42 -- .../tuple/AggregatedTupleLifecycle.java | 31 +- .../tuple/ConditionalTupleLifecycle.java | 46 ++ .../common/tuple/LeftTupleLifecycleImpl.java | 11 +- .../common/tuple/RightTupleLifecycleImpl.java | 11 +- .../bavet/common/tuple/TupleLifecycle.java | 41 +- .../quad/BavetFilterQuadConstraintStream.java | 5 +- .../quad/ConditionalQuadTupleLifecycle.java | 21 - .../tri/BavetFilterTriConstraintStream.java | 5 +- .../tri/ConditionalTriTupleLifecycle.java | 21 - .../uni/BavetFilterUniConstraintStream.java | 5 +- .../uni/ConditionalUniTupleLifecycle.java | 21 - .../score/stream/bavet/visual/NodeGraph.java | 5 +- .../timefold/solver/core/impl/util/Pair.java | 21 + .../solver/core/impl/util/Quadruple.java | 26 + .../solver/core/impl/util/Triple.java | 24 + 50 files changed, 1244 insertions(+), 732 deletions(-) delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/ConditionalBiTupleLifecycle.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractTwoInputNode.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiKeyFunction.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiKeysExtractor.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiMappingFunction.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/KeyFunction.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/KeysExtractor.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadKeyFunction.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadKeysExtractor.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadMappingFunction.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriKeyFunction.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriKeysExtractor.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriMappingFunction.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniKeyFunction.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniKeysExtractor.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniMappingFunction.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/AbstractConditionalTupleLifecycle.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/ConditionalTupleLifecycle.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/ConditionalQuadTupleLifecycle.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/ConditionalTriTupleLifecycle.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/ConditionalUniTupleLifecycle.java diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetFilterBiConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetFilterBiConstraintStream.java index 5c4c63a2cc..1523e41b75 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetFilterBiConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetFilterBiConstraintStream.java @@ -7,6 +7,7 @@ import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; import ai.timefold.solver.core.impl.score.stream.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.BiTuple; +import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle; final class BavetFilterBiConstraintStream extends BavetAbstractBiConstraintStream { @@ -30,7 +31,7 @@ public BavetFilterBiConstraintStream(BavetConstraintFactory constrain @Override public > void buildNode(NodeBuildHelper buildHelper) { buildHelper.> putInsertUpdateRetract(this, childStreamList, - tupleLifecycle -> new ConditionalBiTupleLifecycle<>(predicate, tupleLifecycle)); + tupleLifecycle -> TupleLifecycle.conditionally(tupleLifecycle, predicate)); } // ************************************************************************ @@ -46,7 +47,7 @@ public int hashCode() { public boolean equals(Object o) { if (this == o) { return true; - } else if (o instanceof BavetFilterBiConstraintStream other) { + } else if (o instanceof BavetFilterBiConstraintStream other) { return parent == other.parent && predicate == other.predicate; } else { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/ConditionalBiTupleLifecycle.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/ConditionalBiTupleLifecycle.java deleted file mode 100644 index 9597aa2246..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/ConditionalBiTupleLifecycle.java +++ /dev/null @@ -1,22 +0,0 @@ -package ai.timefold.solver.core.impl.score.stream.bavet.bi; - -import java.util.function.BiPredicate; - -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractConditionalTupleLifecycle; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.BiTuple; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle; - -final class ConditionalBiTupleLifecycle extends AbstractConditionalTupleLifecycle> { - private final BiPredicate predicate; - - public ConditionalBiTupleLifecycle(BiPredicate predicate, TupleLifecycle> tupleLifecycle) { - super(tupleLifecycle); - this.predicate = predicate; - } - - @Override - protected boolean test(BiTuple tuple) { - return predicate.test(tuple.factA, tuple.factB); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractConcatNode.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractConcatNode.java index f6307c5b23..8aaf95fc91 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractConcatNode.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractConcatNode.java @@ -1,8 +1,6 @@ package ai.timefold.solver.core.impl.score.stream.bavet.common; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleState; @@ -27,8 +25,8 @@ * have the same {@link TupleSource}), it creates another clone. */ public abstract class AbstractConcatNode - extends AbstractNode - implements LeftTupleLifecycle, RightTupleLifecycle { + extends AbstractTwoInputNode { + private final int leftSourceTupleCloneStoreIndex; private final int rightSourceTupleCloneStoreIndex; protected final int outputStoreSize; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractGroupNode.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractGroupNode.java index 84084f97bb..2f5f965c76 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractGroupNode.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractGroupNode.java @@ -126,7 +126,7 @@ private OutTuple_ accumulate(InTuple_ tuple, Group } private Group getOrCreateGroup(GroupKey_ userSuppliedKey) { - var groupMapKey = useAssertingGroupKey ? new AssertingGroupKey(userSuppliedKey) : userSuppliedKey; + var groupMapKey = useAssertingGroupKey ? new AssertingGroupKey<>(userSuppliedKey) : userSuppliedKey; if (hasMultipleGroups) { // Avoids computeIfAbsent in order to not create lambdas on the hot path. var group = groupMap.get(groupMapKey); @@ -167,8 +167,9 @@ private Group createGroupWithoutGroupKey() { return group; } + @SuppressWarnings("unchecked") private GroupKey_ extractUserSuppliedKey(Object groupMapKey) { - return useAssertingGroupKey ? ((AssertingGroupKey) groupMapKey).getKey() : (GroupKey_) groupMapKey; + return useAssertingGroupKey ? ((AssertingGroupKey) groupMapKey).key() : (GroupKey_) groupMapKey; } @Override @@ -289,39 +290,23 @@ private void updateOutTupleToFinisher(OutTuple_ outTuple, ResultContainer_ resul * Since this situation is far too frequent and users run into this, * we have this helper class that will optionally throw an exception when it detects this. */ - private final class AssertingGroupKey { - - private final GroupKey_ key; - private final int initialHashCode; + private record AssertingGroupKey(GroupKey_ key, int initialHashCode) { public AssertingGroupKey(GroupKey_ key) { - this.key = key; - this.initialHashCode = key == null ? 0 : key.hashCode(); + this(key, key == null ? 0 : key.hashCode()); } - public GroupKey_ getKey() { + public GroupKey_ key() { if (key != null && key.hashCode() != initialHashCode) { - throw new IllegalStateException("hashCode of object (" + key + ") of class (" + key.getClass() - + ") has changed while it was being used as a group key within groupBy (" - + AbstractGroupNode.this.getClass() + ").\n" - + "Group key hashCode must consistently return the same integer, " - + "as required by the general hashCode contract."); + throw new IllegalStateException( + """ + hashCode of object (%s) of class (%s) has changed while it was being used as a group key. + Group key hashCode must consistently return the same integer, as required by the general hashCode contract.""" + .formatted(key, key.getClass().getName())); } return key; } - @Override - public boolean equals(Object other) { - if (other == null || getClass() != other.getClass()) - return false; - return Objects.equals(getKey(), ((AssertingGroupKey) other).getKey()); - } - - @Override - public int hashCode() { - var key = getKey(); - return key == null ? 0 : key.hashCode(); - } } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractIfExistsNode.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractIfExistsNode.java index cd94f75605..847d65aa32 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractIfExistsNode.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractIfExistsNode.java @@ -1,8 +1,6 @@ package ai.timefold.solver.core.impl.score.stream.bavet.common; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleState; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.UniTuple; @@ -19,8 +17,7 @@ * @param */ public abstract class AbstractIfExistsNode - extends AbstractNode - implements LeftTupleLifecycle, RightTupleLifecycle> { + extends AbstractTwoInputNode> { protected final boolean shouldExist; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractIndexedIfExistsNode.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractIndexedIfExistsNode.java index d0ff80931b..b86b09cf7f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractIndexedIfExistsNode.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractIndexedIfExistsNode.java @@ -1,9 +1,11 @@ package ai.timefold.solver.core.impl.score.stream.bavet.common; +import java.util.Objects; + import ai.timefold.solver.core.impl.score.stream.bavet.common.index.Indexer; import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory; -import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory.KeysExtractor; -import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory.UniKeysExtractor; +import ai.timefold.solver.core.impl.score.stream.bavet.common.index.KeysExtractor; +import ai.timefold.solver.core.impl.score.stream.bavet.common.index.UniKeysExtractor; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle; @@ -57,7 +59,7 @@ public final void insertLeft(LeftTuple_ leftTuple) { throw new IllegalStateException("Impossible state: the input for the tuple (" + leftTuple + ") was already added in the tupleStore."); } - var indexKeys = keysExtractorLeft.apply(leftTuple); + var indexKeys = keysExtractorLeft.apply(leftTuple, null); leftTuple.setStore(inputStoreIndexLeftKeys, indexKeys); var counter = new ExistsCounter<>(leftTuple); @@ -87,11 +89,11 @@ public final void updateLeft(LeftTuple_ leftTuple) { insertLeft(leftTuple); return; } - var newIndexKeys = keysExtractorLeft.apply(leftTuple); + var newIndexKeys = keysExtractorLeft.apply(leftTuple, oldIndexKeys); ElementAwareListEntry> counterEntry = leftTuple.getStore(inputStoreIndexLeftCounterEntry); var counter = counterEntry.getElement(); - if (oldIndexKeys.equals(newIndexKeys)) { + if (Objects.equals(oldIndexKeys, newIndexKeys)) { // No need for re-indexing because the index keys didn't change // The indexers contain counters in the DEAD state, to track the rightCount. if (!isFiltering) { @@ -143,7 +145,7 @@ public final void insertRight(UniTuple rightTuple) { throw new IllegalStateException("Impossible state: the input for the tuple (" + rightTuple + ") was already added in the tupleStore."); } - var indexKeys = keysExtractorRight.apply(rightTuple); + var indexKeys = keysExtractorRight.apply(rightTuple, null); rightTuple.setStore(inputStoreIndexRightKeys, indexKeys); var rightEntry = indexerRight.put(indexKeys, rightTuple); @@ -169,8 +171,8 @@ public final void updateRight(UniTuple rightTuple) { insertRight(rightTuple); return; } - var newIndexKeys = keysExtractorRight.apply(rightTuple); - if (oldIndexKeys.equals(newIndexKeys)) { + var newIndexKeys = keysExtractorRight.apply(rightTuple, oldIndexKeys); + if (Objects.equals(oldIndexKeys, newIndexKeys)) { // No need for re-indexing because the index keys didn't change if (isFiltering) { var rightTrackerList = updateRightTrackerList(rightTuple); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractIndexedJoinNode.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractIndexedJoinNode.java index 8ab2ad7df2..2ed73ef3b7 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractIndexedJoinNode.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractIndexedJoinNode.java @@ -1,9 +1,11 @@ package ai.timefold.solver.core.impl.score.stream.bavet.common; +import java.util.Objects; + import ai.timefold.solver.core.impl.score.stream.bavet.common.index.Indexer; import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory; -import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory.KeysExtractor; -import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory.UniKeysExtractor; +import ai.timefold.solver.core.impl.score.stream.bavet.common.index.KeysExtractor; +import ai.timefold.solver.core.impl.score.stream.bavet.common.index.UniKeysExtractor; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle; @@ -58,7 +60,7 @@ public final void insertLeft(LeftTuple_ leftTuple) { throw new IllegalStateException("Impossible state: the input for the tuple (" + leftTuple + ") was already added in the tupleStore."); } - var indexKeys = keysExtractorLeft.apply(leftTuple); + var indexKeys = keysExtractorLeft.apply(leftTuple, null); var outTupleListLeft = new ElementAwareList(); leftTuple.setStore(inputStoreIndexLeftOutTupleList, outTupleListLeft); indexAndPropagateLeft(leftTuple, indexKeys); @@ -72,8 +74,8 @@ public final void updateLeft(LeftTuple_ leftTuple) { insertLeft(leftTuple); return; } - var newIndexKeys = keysExtractorLeft.apply(leftTuple); - if (oldIndexKeys.equals(newIndexKeys)) { + var newIndexKeys = keysExtractorLeft.apply(leftTuple, oldIndexKeys); + if (Objects.equals(oldIndexKeys, newIndexKeys)) { // No need for re-indexing because the index keys didn't change // Prefer an update over retract-insert if possible innerUpdateLeft(leftTuple, consumer -> indexerRight.forEach(oldIndexKeys, consumer)); @@ -114,7 +116,7 @@ public final void insertRight(UniTuple rightTuple) { throw new IllegalStateException("Impossible state: the input for the tuple (" + rightTuple + ") was already added in the tupleStore."); } - var indexKeys = keysExtractorRight.apply(rightTuple); + var indexKeys = keysExtractorRight.apply(rightTuple, null); var outTupleListRight = new ElementAwareList(); rightTuple.setStore(inputStoreIndexRightOutTupleList, outTupleListRight); indexAndPropagateRight(rightTuple, indexKeys); @@ -128,8 +130,8 @@ public final void updateRight(UniTuple rightTuple) { insertRight(rightTuple); return; } - var newIndexKeys = keysExtractorRight.apply(rightTuple); - if (oldIndexKeys.equals(newIndexKeys)) { + var newIndexKeys = keysExtractorRight.apply(rightTuple, oldIndexKeys); + if (Objects.equals(oldIndexKeys, newIndexKeys)) { // No need for re-indexing because the index keys didn't change // Prefer an update over retract-insert if possible innerUpdateRight(rightTuple, consumer -> indexerLeft.forEach(oldIndexKeys, consumer)); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractJoinNode.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractJoinNode.java index f71960e9fa..f2ee59c717 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractJoinNode.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractJoinNode.java @@ -3,8 +3,6 @@ import java.util.function.Consumer; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleState; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.UniTuple; @@ -21,8 +19,7 @@ * @param */ public abstract class AbstractJoinNode - extends AbstractNode - implements LeftTupleLifecycle, RightTupleLifecycle> { + extends AbstractTwoInputNode> { protected final int inputStoreIndexLeftOutTupleList; protected final int inputStoreIndexRightOutTupleList; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractTwoInputNode.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractTwoInputNode.java new file mode 100644 index 0000000000..0178c9483d --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractTwoInputNode.java @@ -0,0 +1,11 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common; + +import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple; +import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle; +import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle; + +public abstract class AbstractTwoInputNode + extends AbstractNode + implements LeftTupleLifecycle, RightTupleLifecycle { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractUnindexedIfExistsNode.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractUnindexedIfExistsNode.java index 96b78cd7ef..5281064d4a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractUnindexedIfExistsNode.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractUnindexedIfExistsNode.java @@ -94,7 +94,8 @@ public final void retractLeft(LeftTuple_ leftTuple) { var counter = counterEntry.getElement(); counterEntry.remove(); if (isFiltering) { - ElementAwareList> leftTrackerList = leftTuple.getStore(inputStoreIndexLeftTrackerList); + ElementAwareList> leftTrackerList = + leftTuple.removeStore(inputStoreIndexLeftTrackerList); leftTrackerList.forEach(FilteringTracker::remove); } killCounterLeft(counter); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/DynamicPropagationQueue.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/DynamicPropagationQueue.java index ed08103e81..76e38854ce 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/DynamicPropagationQueue.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/DynamicPropagationQueue.java @@ -19,15 +19,16 @@ final class DynamicPropagationQueue> implements PropagationQueue { + private static final int INITIAL_CAPACITY = 1000; // Selected arbitrarily. + private final Consumer preprocessor; private final List dirtyList; private final BitSet retractQueue; private final BitSet insertQueue; - private final Consumer retractPropagator; - private final Consumer updatePropagator; - private final Consumer insertPropagator; + private final TupleLifecycle nextNodesTupleLifecycle; - private DynamicPropagationQueue(TupleLifecycle nextNodesTupleLifecycle, Consumer preprocessor, int size) { + private DynamicPropagationQueue(TupleLifecycle nextNodesTupleLifecycle, Consumer preprocessor, + int size) { this.preprocessor = preprocessor; /* * All dirty carriers are stored in a list, never moved, never removed unless after propagation. @@ -38,23 +39,20 @@ private DynamicPropagationQueue(TupleLifecycle nextNodesTupleLifecycle, // Updates tend to be dominant; update queue isn't stored, it's deduced as neither insert nor retract. this.retractQueue = new BitSet(size); this.insertQueue = new BitSet(size); - // Don't create these lambdas over and over again. - this.retractPropagator = nextNodesTupleLifecycle::retract; - this.updatePropagator = nextNodesTupleLifecycle::update; - this.insertPropagator = nextNodesTupleLifecycle::insert; + this.nextNodesTupleLifecycle = nextNodesTupleLifecycle; } public DynamicPropagationQueue(TupleLifecycle nextNodesTupleLifecycle) { - this(nextNodesTupleLifecycle, null); + this(nextNodesTupleLifecycle, null, INITIAL_CAPACITY); } public DynamicPropagationQueue(TupleLifecycle nextNodesTupleLifecycle, Consumer preprocessor) { - this(nextNodesTupleLifecycle, preprocessor, 1000); + this(nextNodesTupleLifecycle, preprocessor, INITIAL_CAPACITY); } @Override public void insert(Carrier_ carrier) { - int positionInDirtyList = carrier.positionInDirtyList; + var positionInDirtyList = carrier.positionInDirtyList; if (positionInDirtyList < 0) { makeDirty(carrier, insertQueue); } else { @@ -65,7 +63,8 @@ public void insert(Carrier_ carrier) { insertQueue.set(positionInDirtyList); } default -> - throw new IllegalStateException("Impossible state: Cannot insert (" + carrier + "), already inserting."); + throw new IllegalStateException("Impossible state: Cannot insert (%s), already inserting." + .formatted(carrier)); } } carrier.setState(TupleState.CREATING); @@ -73,14 +72,14 @@ public void insert(Carrier_ carrier) { private void makeDirty(Carrier_ carrier, BitSet queue) { dirtyList.add(carrier); - int position = dirtyList.size() - 1; + var position = dirtyList.size() - 1; queue.set(position); carrier.positionInDirtyList = position; } @Override public void update(Carrier_ carrier) { - int positionInDirtyList = carrier.positionInDirtyList; + var positionInDirtyList = carrier.positionInDirtyList; if (positionInDirtyList < 0) { dirtyList.add(carrier); carrier.positionInDirtyList = dirtyList.size() - 1; @@ -99,9 +98,10 @@ public void update(Carrier_ carrier) { @Override public void retract(Carrier_ carrier, TupleState state) { if (state.isActive() || state == TupleState.DEAD) { - throw new IllegalArgumentException("Impossible state: The state (" + state + ") is not a valid retract state."); + throw new IllegalArgumentException("Impossible state: The state (%s) is not a valid retract state." + .formatted(state)); } - int positionInDirtyList = carrier.positionInDirtyList; + var positionInDirtyList = carrier.positionInDirtyList; if (positionInDirtyList < 0) { makeDirty(carrier, retractQueue); } else { @@ -112,7 +112,8 @@ public void retract(Carrier_ carrier, TupleState state) { } case UPDATING -> retractQueue.set(positionInDirtyList); default -> - throw new IllegalStateException("Impossible state: Cannot retract (" + carrier + "), already retracting."); + throw new IllegalStateException("Impossible state: Cannot retract (%s), already retracting." + .formatted(carrier)); } } @@ -124,24 +125,21 @@ public void propagateRetracts() { if (retractQueue.isEmpty()) { return; } - int i = retractQueue.nextSetBit(0); + var i = retractQueue.nextSetBit(0); while (i != -1) { - Carrier_ carrier = dirtyList.get(i); - TupleState state = carrier.getState(); + var carrier = dirtyList.get(i); + var state = carrier.getState(); switch (state) { - case DYING -> propagate(carrier, retractPropagator, TupleState.DEAD); + case DYING -> { + clean(carrier, TupleState.DEAD); // Hide original state from the next node by doing this before propagation. + nextNodesTupleLifecycle.retract(carrier.getTuple()); + } case ABORTING -> clean(carrier, TupleState.DEAD); } i = retractQueue.nextSetBit(i + 1); } } - private static > void - propagate(Carrier_ carrier, Consumer propagator, TupleState tupleState) { - clean(carrier, tupleState); // Hide original state from the next node by doing this before propagation. - propagator.accept(carrier.getTuple()); - } - private static void clean(AbstractPropagationMetadataCarrier carrier, TupleState tupleState) { carrier.setState(tupleState); carrier.positionInDirtyList = -1; @@ -149,25 +147,25 @@ private static void clean(AbstractPropagationMetadataCarrier carrier, TupleSt @Override public void propagateUpdates() { - int dirtyListSize = dirtyList.size(); - BitSet insertAndRetractQueue = buildInsertAndRetractQueue(insertQueue, retractQueue); + var dirtyListSize = dirtyList.size(); + var insertAndRetractQueue = buildInsertAndRetractQueue(insertQueue, retractQueue); if (insertAndRetractQueue == null) { // Iterate over the entire list more efficiently. - for (int i = 0; i < dirtyListSize; i++) { + for (var i = 0; i < dirtyListSize; i++) { // Not using enhanced for loop in order not to create so many iterators in the hot path. - propagateInsertOrUpdate(dirtyList.get(i), updatePropagator); + propagateInsertOrUpdate(dirtyList.get(i), true); } } else { // The gaps in the queue are the updates. - int i = insertAndRetractQueue.nextClearBit(0); + var i = insertAndRetractQueue.nextClearBit(0); while (i != -1 && i < dirtyListSize) { - propagateInsertOrUpdate(dirtyList.get(i), updatePropagator); + propagateInsertOrUpdate(dirtyList.get(i), true); i = insertAndRetractQueue.nextClearBit(i + 1); } } } private static BitSet buildInsertAndRetractQueue(BitSet insertQueue, BitSet retractQueue) { - boolean noInserts = insertQueue.isEmpty(); - boolean noRetracts = retractQueue.isEmpty(); + var noInserts = insertQueue.isEmpty(); + var noRetracts = retractQueue.isEmpty(); if (noInserts && noRetracts) { return null; } else if (noInserts) { @@ -175,19 +173,31 @@ private static BitSet buildInsertAndRetractQueue(BitSet insertQueue, BitSet retr } else if (noRetracts) { return insertQueue; } else { - BitSet updateQueue = new BitSet(); + var updateQueue = new BitSet(Math.max(insertQueue.length(), retractQueue.length())); updateQueue.or(insertQueue); updateQueue.or(retractQueue); return updateQueue; } } + private void propagateInsertOrUpdate(Carrier_ carrier, boolean isUpdate) { + if (preprocessor != null) { + preprocessor.accept(carrier); + } + clean(carrier, TupleState.OK); // Hide original state from the next node by doing this before propagation. + if (isUpdate) { + nextNodesTupleLifecycle.update(carrier.getTuple()); + } else { + nextNodesTupleLifecycle.insert(carrier.getTuple()); + } + } + @Override public void propagateInserts() { if (!insertQueue.isEmpty()) { - int i = insertQueue.nextSetBit(0); + var i = insertQueue.nextSetBit(0); while (i != -1) { - propagateInsertOrUpdate(dirtyList.get(i), insertPropagator); + propagateInsertOrUpdate(dirtyList.get(i), false); i = insertQueue.nextSetBit(i + 1); } insertQueue.clear(); @@ -196,17 +206,4 @@ public void propagateInserts() { dirtyList.clear(); } - /** - * Exists so that implementations can customize the update/insert propagation. - * - * @param carrier never null - * @param propagator never null - */ - private void propagateInsertOrUpdate(Carrier_ carrier, Consumer propagator) { - if (preprocessor != null) { - preprocessor.accept(carrier); - } - propagate(carrier, propagator, TupleState.OK); - } - } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/Group.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/Group.java index cbe02c73bd..d0e9a13e6c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/Group.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/Group.java @@ -8,34 +8,36 @@ final class Group public static Group createWithoutAccumulate(Object groupKey, OutTuple_ outTuple) { - return new Group<>(new GroupDataWithKey<>(groupKey), outTuple); + return new Group<>(groupKey, null, outTuple); } public static Group createWithoutGroupKey(ResultContainer_ resultContainer, OutTuple_ outTuple) { - return new Group<>(new GroupDataWithAccumulate<>(resultContainer), outTuple); + return new Group<>(null, resultContainer, outTuple); } public static Group create(Object groupKey, ResultContainer_ resultContainer, OutTuple_ outTuple) { - return new Group<>(new GroupDataWithKeyAndAccumulate<>(groupKey, resultContainer), outTuple); + return new Group<>(groupKey, resultContainer, outTuple); } - private final GroupData groupData; + private final Object groupKey; + private final ResultContainer_ resultContainer; private final OutTuple_ outTuple; public int parentCount = 1; - private Group(GroupData groupData, OutTuple_ outTuple) { - this.groupData = groupData; + private Group(Object groupKey, ResultContainer_ resultContainer, OutTuple_ outTuple) { + this.groupKey = groupKey; + this.resultContainer = resultContainer; this.outTuple = outTuple; } public Object getGroupKey() { - return groupData.groupKey(); + return groupKey; } public ResultContainer_ getResultContainer() { - return groupData.resultContainer(); + return resultContainer; } @Override @@ -53,41 +55,4 @@ public void setState(TupleState state) { outTuple.state = state; } - /** - * Save memory by allowing to not store the group key or the result container. - * - * @param - */ - private sealed interface GroupData { - - Object groupKey(); - - ResultContainer_ resultContainer(); - - } - - private record GroupDataWithKey(Object groupKey) implements GroupData { - - @Override - public ResultContainer_ resultContainer() { - throw new UnsupportedOperationException("Impossible state: no result container for group (" + groupKey + ")."); - } - - } - - private record GroupDataWithAccumulate( - ResultContainer_ resultContainer) implements GroupData { - - @Override - public Object groupKey() { - throw new UnsupportedOperationException("Impossible state: no group key."); - } - - } - - private record GroupDataWithKeyAndAccumulate(Object groupKey, - ResultContainer_ resultContainer) implements GroupData { - - } - } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/NodeBuildHelper.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/NodeBuildHelper.java index b4663acab6..ecf48dafc0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/NodeBuildHelper.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/NodeBuildHelper.java @@ -6,14 +6,11 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Function; +import java.util.function.UnaryOperator; import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.api.score.stream.ConstraintStream; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractConditionalTupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.uni.AbstractForEachUniNode; import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraintStream; @@ -62,13 +59,13 @@ public void addNode(AbstractNode node, BavetAbstractConstraintStream creator, } } - public void addNode(AbstractNode node, BavetAbstractConstraintStream creator, - BavetAbstractConstraintStream leftParent, - BavetAbstractConstraintStream rightParent) { + public void addNode( + AbstractTwoInputNode node, BavetAbstractConstraintStream creator, + BavetAbstractConstraintStream leftParent, BavetAbstractConstraintStream rightParent) { reversedNodeList.add(node); nodeCreatorMap.put(node, creator); - putInsertUpdateRetract(leftParent, TupleLifecycle.ofLeft((LeftTupleLifecycle) node)); - putInsertUpdateRetract(rightParent, TupleLifecycle.ofRight((RightTupleLifecycle) node)); + putInsertUpdateRetract(leftParent, TupleLifecycle.ofLeft(node)); + putInsertUpdateRetract(rightParent, TupleLifecycle.ofRight(node)); } public void putInsertUpdateRetract(ConstraintStream stream, @@ -78,31 +75,28 @@ public void putInsertUpdateRetract(ConstraintStre public void putInsertUpdateRetract(ConstraintStream stream, List> childStreamList, - Function, AbstractConditionalTupleLifecycle> tupleLifecycleFunction) { + UnaryOperator> tupleLifecycleFunction) { TupleLifecycle tupleLifecycle = getAggregatedTupleLifecycle(childStreamList); putInsertUpdateRetract(stream, tupleLifecycleFunction.apply(tupleLifecycle)); } - public TupleLifecycle getAggregatedTupleLifecycle( - List streamList) { - TupleLifecycle[] tupleLifecycles = streamList.stream() + @SuppressWarnings("unchecked") + public TupleLifecycle + getAggregatedTupleLifecycle(List streamList) { + var tupleLifecycles = streamList.stream() .filter(this::isStreamActive) .map(s -> getTupleLifecycle(s, tupleLifecycleMap)) .toArray(TupleLifecycle[]::new); - switch (tupleLifecycles.length) { - case 0: - throw new IllegalStateException("Impossible state: None of the streamList (" + streamList - + ") are active."); - case 1: - return tupleLifecycles[0]; - default: - return TupleLifecycle.of(tupleLifecycles); + if (tupleLifecycles.length == 0) { + throw new IllegalStateException("Impossible state: None of the streamList (%s) are active.".formatted(streamList)); } + return TupleLifecycle.aggregate(tupleLifecycles); } + @SuppressWarnings("unchecked") private static TupleLifecycle getTupleLifecycle(ConstraintStream stream, Map> tupleLifecycleMap) { - TupleLifecycle tupleLifecycle = (TupleLifecycle) tupleLifecycleMap.get(stream); + var tupleLifecycle = (TupleLifecycle) tupleLifecycleMap.get(stream); if (tupleLifecycle == null) { throw new IllegalStateException("Impossible state: the stream (" + stream + ") hasn't built a node yet."); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/StaticPropagationQueue.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/StaticPropagationQueue.java index 05012d74ef..06253680c3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/StaticPropagationQueue.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/StaticPropagationQueue.java @@ -2,7 +2,6 @@ import java.util.ArrayDeque; import java.util.Deque; -import java.util.function.Consumer; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle; @@ -22,19 +21,14 @@ public final class StaticPropagationQueue private final Deque retractQueue; private final Deque updateQueue; private final Deque insertQueue; - private final Consumer retractPropagator; - private final Consumer updatePropagator; - private final Consumer insertPropagator; + private final TupleLifecycle nextNodesTupleLifecycle; public StaticPropagationQueue(TupleLifecycle nextNodesTupleLifecycle, int size) { // Guesstimate that updates are dominant. this.retractQueue = new ArrayDeque<>(size / 20); this.updateQueue = new ArrayDeque<>((size / 20) * 18); this.insertQueue = new ArrayDeque<>(size / 20); - // Don't create these lambdas over and over again. - this.retractPropagator = nextNodesTupleLifecycle::retract; - this.updatePropagator = nextNodesTupleLifecycle::update; - this.insertPropagator = nextNodesTupleLifecycle::insert; + this.nextNodesTupleLifecycle = nextNodesTupleLifecycle; } public StaticPropagationQueue(TupleLifecycle nextNodesTupleLifecycle) { @@ -44,7 +38,8 @@ public StaticPropagationQueue(TupleLifecycle nextNodesTupleLifecycle) { @Override public void insert(Tuple_ carrier) { if (carrier.state == TupleState.CREATING) { - throw new IllegalStateException("Impossible state: The tuple (" + carrier + ") is already in the insert queue."); + throw new IllegalStateException("Impossible state: The tuple (%s) is already in the insert queue." + .formatted(carrier)); } carrier.state = TupleState.CREATING; insertQueue.add(carrier); @@ -61,13 +56,16 @@ public void update(Tuple_ carrier) { @Override public void retract(Tuple_ carrier, TupleState state) { - if (carrier.state == state) { // Skip double retracts. + var carrierState = carrier.state; + if (carrierState == state) { // Skip double retracts. return; } if (state.isActive() || state == TupleState.DEAD) { - throw new IllegalArgumentException("Impossible state: The state (" + state + ") is not a valid retract state."); - } else if (carrier.state == TupleState.ABORTING || carrier.state == TupleState.DYING) { - throw new IllegalStateException("Impossible state: The tuple (" + carrier + ") is already in the retract queue."); + throw new IllegalArgumentException("Impossible state: The state (%s) is not a valid retract state." + .formatted(state)); + } else if (carrierState == TupleState.ABORTING || carrierState == TupleState.DYING) { + throw new IllegalStateException("Impossible state: The tuple (%s) is already in the retract queue." + .formatted(carrier)); } carrier.state = state; retractQueue.add(carrier); @@ -78,31 +76,29 @@ public void propagateRetracts() { if (retractQueue.isEmpty()) { return; } - for (Tuple_ tuple : retractQueue) { + for (var tuple : retractQueue) { switch (tuple.state) { - case DYING -> propagate(tuple, retractPropagator, TupleState.DEAD); + case DYING -> { + // Change state before propagation, so that the next node can't make decisions on the original state. + tuple.state = TupleState.DEAD; + nextNodesTupleLifecycle.retract(tuple); + } case ABORTING -> tuple.state = TupleState.DEAD; } } retractQueue.clear(); } - private void propagate(Tuple_ tuple, Consumer propagator, TupleState tupleState) { - // Change state before propagation, so that the next node can't make decisions on the original state. - tuple.state = tupleState; - propagator.accept(tuple); - } - @Override public void propagateUpdates() { - processAndClear(updateQueue, updatePropagator); + processAndClear(updateQueue); } - private void processAndClear(Deque dirtyQueue, Consumer propagator) { + private void processAndClear(Deque dirtyQueue) { if (dirtyQueue.isEmpty()) { return; } - for (Tuple_ tuple : dirtyQueue) { + for (var tuple : dirtyQueue) { if (tuple.state == TupleState.DEAD) { /* * DEAD signifies the tuple was both in insert/update and retract queues. @@ -113,18 +109,26 @@ private void processAndClear(Deque dirtyQueue, Consumer propagat */ continue; } - propagate(tuple, propagator, TupleState.OK); + // Change state before propagation, so that the next node can't make decisions on the original state. + tuple.state = TupleState.OK; + if (dirtyQueue == updateQueue) { + nextNodesTupleLifecycle.update(tuple); + } else { + nextNodesTupleLifecycle.insert(tuple); + } } dirtyQueue.clear(); } @Override public void propagateInserts() { - processAndClear(insertQueue, insertPropagator); + processAndClear(insertQueue); if (!retractQueue.isEmpty()) { - throw new IllegalStateException("Impossible state: The retract queue (" + retractQueue + ") is not empty."); + throw new IllegalStateException("Impossible state: The retract queue (%s) is not empty." + .formatted(retractQueue)); } else if (!updateQueue.isEmpty()) { - throw new IllegalStateException("Impossible state: The update queue (" + updateQueue + ") is not empty."); + throw new IllegalStateException("Impossible state: The update queue (%s) is not empty." + .formatted(updateQueue)); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiKeyFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiKeyFunction.java new file mode 100644 index 0000000000..52be55e236 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiKeyFunction.java @@ -0,0 +1,110 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import ai.timefold.solver.core.api.function.TriFunction; +import ai.timefold.solver.core.impl.util.Pair; +import ai.timefold.solver.core.impl.util.Quadruple; +import ai.timefold.solver.core.impl.util.Triple; + +final class BiKeyFunction + implements TriFunction, KeyFunction { + + private final int keyId; + private final int mappingFunctionCount; + private final BiMappingFunction[] mappingFunctions; + private final BiMappingFunction mappingFunction0; + private final BiMappingFunction mappingFunction1; + private final BiMappingFunction mappingFunction2; + private final BiMappingFunction mappingFunction3; + + public BiKeyFunction(BiMappingFunction mappingFunction) { + this(-1, Collections.singletonList(mappingFunction)); + } + + @SuppressWarnings("unchecked") + public BiKeyFunction(int keyId, List> mappingFunctionList) { + this.keyId = keyId; + this.mappingFunctionCount = mappingFunctionList.size(); + this.mappingFunctions = mappingFunctionList.toArray(new BiMappingFunction[0]); + this.mappingFunction0 = mappingFunctions[0]; + this.mappingFunction1 = mappingFunctionCount > 1 ? mappingFunctions[1] : null; + this.mappingFunction2 = mappingFunctionCount > 2 ? mappingFunctions[2] : null; + this.mappingFunction3 = mappingFunctionCount > 3 ? mappingFunctions[3] : null; + } + + @Override + public Object apply(A a, B b, Object oldKey) { + return switch (mappingFunctionCount) { + case 1 -> apply1(a, b); + case 2 -> apply2(a, b, oldKey); + case 3 -> apply3(a, b, oldKey); + case 4 -> apply4(a, b, oldKey); + default -> applyMany(a, b, oldKey); + }; + } + + private Object apply1(A a, B b) { + return mappingFunction0.apply(a, b); + } + + @SuppressWarnings("unchecked") + private Object apply2(A a, B b, Object oldKey) { + var subkey1 = mappingFunction0.apply(a, b); + var subkey2 = mappingFunction1.apply(a, b); + if (oldKey == null) { + return new Pair<>(subkey1, subkey2); + } + return ((Pair) UniKeyFunction.extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2); + } + + @SuppressWarnings("unchecked") + private Object apply3(A a, B b, Object oldKey) { + var subkey1 = mappingFunction0.apply(a, b); + var subkey2 = mappingFunction1.apply(a, b); + var subkey3 = mappingFunction2.apply(a, b); + if (oldKey == null) { + return new Triple<>(subkey1, subkey2, subkey3); + } + return ((Triple) UniKeyFunction.extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2, subkey3); + } + + @SuppressWarnings("unchecked") + private Object apply4(A a, B b, Object oldKey) { + var subkey1 = mappingFunction0.apply(a, b); + var subkey2 = mappingFunction1.apply(a, b); + var subkey3 = mappingFunction2.apply(a, b); + var subkey4 = mappingFunction3.apply(a, b); + if (oldKey == null) { + return new Quadruple<>(subkey1, subkey2, subkey3, subkey4); + } + return ((Quadruple) UniKeyFunction.extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2, subkey3, subkey4); + } + + private Object applyMany(A a, B b, Object oldKey) { + var result = new Object[mappingFunctionCount]; + if (oldKey == null) { + for (var i = 0; i < mappingFunctionCount; i++) { + result[i] = mappingFunctions[i].apply(a, b); + } + } else { + var oldArray = ((IndexerKey) UniKeyFunction.extractSubkey(keyId, oldKey)).properties(); + var subKeysEqual = true; + for (var i = 0; i < mappingFunctionCount; i++) { + var subkey = mappingFunctions[i].apply(a, b); + subKeysEqual = subKeysEqual && Objects.equals(subkey, oldArray[i]); + result[i] = subkey; + } + if (subKeysEqual) { + return oldKey; + } + } + return new IndexerKey(result); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiKeysExtractor.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiKeysExtractor.java new file mode 100644 index 0000000000..4046f84e2f --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiKeysExtractor.java @@ -0,0 +1,8 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.BiTuple; + +@FunctionalInterface +public interface BiKeysExtractor extends KeysExtractor> { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiMappingFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiMappingFunction.java new file mode 100644 index 0000000000..3b98851b48 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiMappingFunction.java @@ -0,0 +1,17 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +import java.util.function.BiFunction; + +import ai.timefold.solver.core.impl.score.stream.common.AbstractJoiner; +import ai.timefold.solver.core.impl.score.stream.common.tri.DefaultTriJoiner; + +@FunctionalInterface +interface BiMappingFunction extends BiFunction { + + static BiMappingFunction of(AbstractJoiner joiner, int index) { + var castJoiner = (DefaultTriJoiner) joiner; + var mapping = castJoiner.getLeftMapping(index); + return mapping::apply; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/ComparisonIndexer.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/ComparisonIndexer.java index da7781940c..8580440be2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/ComparisonIndexer.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/ComparisonIndexer.java @@ -62,7 +62,7 @@ private ComparisonIndexer(JoinerType comparisonJoinerType, KeyRetriever ke @Override public ElementAwareListEntry put(Object indexKeys, T tuple) { - Key_ indexKey = keyRetriever.apply(indexKeys); + var indexKey = keyRetriever.apply(indexKeys); // Avoids computeIfAbsent in order to not create lambdas on the hot path. var downstreamIndexer = comparisonMap.get(indexKey); if (downstreamIndexer == null) { @@ -74,7 +74,7 @@ public ElementAwareListEntry put(Object indexKeys, T tuple) { @Override public void remove(Object indexKeys, ElementAwareListEntry entry) { - Key_ indexKey = keyRetriever.apply(indexKeys); + var indexKey = keyRetriever.apply(indexKeys); var downstreamIndexer = getDownstreamIndexer(indexKeys, indexKey, entry); downstreamIndexer.remove(indexKeys, entry); if (downstreamIndexer.isEmpty()) { @@ -99,7 +99,7 @@ public int size(Object indexKeys) { if (mapSize == 0) { return 0; } - Key_ indexKey = keyRetriever.apply(indexKeys); + var indexKey = keyRetriever.apply(indexKeys); if (mapSize == 1) { // Avoid creation of the entry set and iterator. var entry = comparisonMap.firstEntry(); var comparison = keyComparator.compare(entry.getKey(), indexKey); @@ -133,7 +133,7 @@ public void forEach(Object indexKeys, Consumer tupleConsumer) { if (size == 0) { return; } - Key_ indexKey = keyRetriever.apply(indexKeys); + var indexKey = keyRetriever.apply(indexKeys); if (size == 1) { // Avoid creation of the entry set and iterator. var entry = comparisonMap.firstEntry(); visitEntry(indexKeys, tupleConsumer, indexKey, entry); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/EqualsIndexer.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/EqualsIndexer.java index 196c274f8e..4a3bc690b3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/EqualsIndexer.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/EqualsIndexer.java @@ -37,9 +37,9 @@ public EqualsIndexer(int keyIndex, Supplier> downstreamIndexerSupplie @Override public ElementAwareListEntry put(Object indexKeys, T tuple) { - Key_ indexKey = keyRetriever.apply(indexKeys); + var indexKey = keyRetriever.apply(indexKeys); // Avoids computeIfAbsent in order to not create lambdas on the hot path. - Indexer downstreamIndexer = downstreamIndexerMap.get(indexKey); + var downstreamIndexer = downstreamIndexerMap.get(indexKey); if (downstreamIndexer == null) { downstreamIndexer = downstreamIndexerSupplier.get(); downstreamIndexerMap.put(indexKey, downstreamIndexer); @@ -49,8 +49,8 @@ public ElementAwareListEntry put(Object indexKeys, T tuple) { @Override public void remove(Object indexKeys, ElementAwareListEntry entry) { - Key_ indexKey = keyRetriever.apply(indexKeys); - Indexer downstreamIndexer = getDownstreamIndexer(indexKeys, indexKey, entry); + var indexKey = keyRetriever.apply(indexKeys); + var downstreamIndexer = getDownstreamIndexer(indexKeys, indexKey, entry); downstreamIndexer.remove(indexKeys, entry); if (downstreamIndexer.isEmpty()) { downstreamIndexerMap.remove(indexKey); @@ -58,7 +58,7 @@ public void remove(Object indexKeys, ElementAwareListEntry entry) { } private Indexer getDownstreamIndexer(Object indexKeys, Key_ indexerKey, ElementAwareListEntry entry) { - Indexer downstreamIndexer = downstreamIndexerMap.get(indexerKey); + var downstreamIndexer = downstreamIndexerMap.get(indexerKey); if (downstreamIndexer == null) { throw new IllegalStateException( "Impossible state: the tuple (%s) with indexKey (%s) doesn't exist in the indexer %s." @@ -69,8 +69,8 @@ private Indexer getDownstreamIndexer(Object indexKeys, Key_ indexerKey, Eleme @Override public int size(Object indexKeys) { - Key_ indexKey = keyRetriever.apply(indexKeys); - Indexer downstreamIndexer = downstreamIndexerMap.get(indexKey); + var indexKey = keyRetriever.apply(indexKeys); + var downstreamIndexer = downstreamIndexerMap.get(indexKey); if (downstreamIndexer == null) { return 0; } @@ -79,8 +79,8 @@ public int size(Object indexKeys) { @Override public void forEach(Object indexKeys, Consumer tupleConsumer) { - Key_ indexKey = keyRetriever.apply(indexKeys); - Indexer downstreamIndexer = downstreamIndexerMap.get(indexKey); + var indexKey = keyRetriever.apply(indexKeys); + var downstreamIndexer = downstreamIndexerMap.get(indexKey); if (downstreamIndexer == null) { return; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/IndexKeys.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/IndexKeys.java index b875da7974..3828a50d44 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/IndexKeys.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/IndexKeys.java @@ -1,5 +1,7 @@ package ai.timefold.solver.core.impl.score.stream.bavet.common.index; +import java.util.Objects; + /** * Cached in tuples; each tuple carries its unique instance. *

@@ -39,8 +41,17 @@ static Object of(Object key) { return key == null ? ManyIndexKeys.SINGLE_NULL : key; } - static IndexKeys of(Key1_ key1, Key2_ key2) { - return new TwoIndexKeys<>(key1, key2); + static IndexKeys of(Object keyA, Object keyB) { + return new TwoIndexKeys<>(keyA, keyB); + } + + static IndexKeys of(Object keyA, Object keyB, Object oldKeys) { + if (oldKeys instanceof TwoIndexKeys twoIndexKeys && + Objects.equals(twoIndexKeys.keyA(), keyA) && + Objects.equals(twoIndexKeys.keyB(), keyB)) { + return twoIndexKeys; + } + return of(keyA, keyB); } static IndexKeys ofMany(Object... keys) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/IndexerFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/IndexerFactory.java index 4729b26427..e7ee815007 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/IndexerFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/IndexerFactory.java @@ -1,30 +1,20 @@ package ai.timefold.solver.core.impl.score.stream.bavet.common.index; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.NavigableMap; +import java.util.Objects; import java.util.TreeMap; -import java.util.function.BiFunction; -import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Supplier; -import ai.timefold.solver.core.api.function.QuadFunction; -import ai.timefold.solver.core.api.function.TriFunction; import ai.timefold.solver.core.impl.score.stream.JoinerType; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.BiTuple; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.QuadTuple; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TriTuple; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.UniTuple; import ai.timefold.solver.core.impl.score.stream.common.AbstractJoiner; -import ai.timefold.solver.core.impl.score.stream.common.bi.DefaultBiJoiner; -import ai.timefold.solver.core.impl.score.stream.common.penta.DefaultPentaJoiner; -import ai.timefold.solver.core.impl.score.stream.common.quad.DefaultQuadJoiner; -import ai.timefold.solver.core.impl.score.stream.common.tri.DefaultTriJoiner; import ai.timefold.solver.core.impl.util.Pair; import ai.timefold.solver.core.impl.util.Quadruple; -import ai.timefold.solver.core.impl.util.Triple; /** * {@link Indexer Indexers} form a parent-child hierarchy, @@ -101,376 +91,107 @@ public boolean hasJoiners() { } public UniKeysExtractor buildUniLeftKeysExtractor() { - var castJoiner = (DefaultBiJoiner) joiner; - return buildUniKeysExtractor(castJoiner::getLeftMapping); + return buildUniKeysExtractor(value -> UniMappingFunction.of(joiner, value)); } - @SuppressWarnings("unchecked") - private UniKeysExtractor buildUniKeysExtractor(IntFunction> mappingExtractor) { + private UniKeysExtractor buildUniKeysExtractor(IntFunction> mappingExtractor) { var joinerCount = joiner.getJoinerCount(); if (joinerCount == 0) { - return tuple -> IndexKeys.none(); + return (tuple, oldKeys) -> IndexKeys.none(); } else if (joinerCount == 1) { - return toKeysExtractor(mappingExtractor.apply(0)); + return buildUniKeysExtractor(new UniKeyFunction<>(mappingExtractor.apply(0))); } + var keyFunctions = extractKeyFunctions(mappingExtractor, UniKeyFunction::new); + return buildUniKeysExtractor(keyFunctions); + } + + private + List extractKeyFunctions(IntFunction mappingExtractor, + MappingToKeyFunction constructor) { + var joinerCount = joiner.getJoinerCount(); var startIndexInclusive = 0; - var keyFunctionList = new ArrayList>(); + var keyFunctionList = new ArrayList(); for (var entry : joinerTypeMap.entrySet()) { var endIndexExclusive = entry.getKey(); var keyFunctionLength = endIndexExclusive - startIndexInclusive; // Consecutive EQUAL joiners are merged into a single composite keyFunction. - Function keyFunction = switch (keyFunctionLength) { - case 1 -> mappingExtractor.apply(startIndexInclusive); - case 2 -> { - var mapping1 = mappingExtractor.apply(startIndexInclusive); - var mapping2 = mappingExtractor.apply(startIndexInclusive + 1); - yield a -> new Pair<>(mapping1.apply(a), mapping2.apply(a)); - } - case 3 -> { - var mapping1 = mappingExtractor.apply(startIndexInclusive); - var mapping2 = mappingExtractor.apply(startIndexInclusive + 1); - var mapping3 = mappingExtractor.apply(startIndexInclusive + 2); - yield a -> new Triple<>(mapping1.apply(a), mapping2.apply(a), mapping3.apply(a)); - } - case 4 -> { - var mapping1 = mappingExtractor.apply(startIndexInclusive); - var mapping2 = mappingExtractor.apply(startIndexInclusive + 1); - var mapping3 = mappingExtractor.apply(startIndexInclusive + 2); - var mapping4 = mappingExtractor.apply(startIndexInclusive + 3); - yield a -> new Quadruple<>(mapping1.apply(a), mapping2.apply(a), mapping3.apply(a), - mapping4.apply(a)); - } + var keyFunctions = switch (keyFunctionLength) { + case 1 -> Collections.singletonList(mappingExtractor.apply(startIndexInclusive)); + case 2 -> List.of(mappingExtractor.apply(startIndexInclusive), + mappingExtractor.apply(startIndexInclusive + 1)); + case 3 -> List.of(mappingExtractor.apply(startIndexInclusive), + mappingExtractor.apply(startIndexInclusive + 1), + mappingExtractor.apply(startIndexInclusive + 2)); + case 4 -> List.of(mappingExtractor.apply(startIndexInclusive), + mappingExtractor.apply(startIndexInclusive + 1), + mappingExtractor.apply(startIndexInclusive + 2), + mappingExtractor.apply(startIndexInclusive + 3)); default -> { - Function[] mappings = new Function[joinerCount]; + var result = new ArrayList(keyFunctionLength); for (var i = 0; i < joinerCount; i++) { var mapping = mappingExtractor.apply(i); - mappings[i] = mapping; + result.add(mapping); } - yield toCompositeKeyFunction(mappings); + yield result; } }; - keyFunctionList.add(keyFunction); + keyFunctionList.add(constructor.apply(keyFunctionList.size(), keyFunctions)); startIndexInclusive = endIndexExclusive; } - return toKeysExtractor(keyFunctionList); + return keyFunctionList; } - @SafeVarargs - private static Function toCompositeKeyFunction(Function... mappings) { - return a -> { - var mappingCount = mappings.length; - var result = new Object[mappingCount]; - for (var i = 0; i < mappingCount; i++) { - result[i] = mappings[i].apply(a); - } - return new IndexerKey(result); - }; - } + @FunctionalInterface + private interface MappingToKeyFunction { - private static UniKeysExtractor toKeysExtractor(Function keyFunction) { - return tuple -> { - var a = tuple.factA; - return IndexKeys.of(keyFunction.apply(a)); - }; - } + KeyFunction_ apply(int keyId, List mappingFunctions); - private static UniKeysExtractor toKeysExtractor(List> keyFunctionList) { - var keyFunctionCount = keyFunctionList.size(); - return switch (keyFunctionCount) { - case 1 -> toKeysExtractor(keyFunctionList.get(0)); - case 2 -> { - var keyFunction1 = keyFunctionList.get(0); - var keyFunction2 = keyFunctionList.get(1); - yield tuple -> { - var a = tuple.factA; - return IndexKeys.of(keyFunction1.apply(a), keyFunction2.apply(a)); - }; - } - default -> tuple -> { - var a = tuple.factA; - var arr = new Object[keyFunctionCount]; - for (var i = 0; i < keyFunctionCount; i++) { - arr[i] = keyFunctionList.get(i).apply(a); - } - return IndexKeys.ofMany(arr); - }; - }; } - @SuppressWarnings("unchecked") public BiKeysExtractor buildBiLeftKeysExtractor() { var joinerCount = joiner.getJoinerCount(); - var castJoiner = (DefaultTriJoiner) joiner; if (joinerCount == 0) { - return tuple -> IndexKeys.none(); + return (tuple, oldKeys) -> IndexKeys.none(); } else if (joinerCount == 1) { - return toKeysExtractor(castJoiner.getLeftMapping(0)); + return buildBiKeysExtractor(new BiKeyFunction<>(BiMappingFunction.of(joiner, 0))); } - var startIndexInclusive = 0; - var keyFunctionList = new ArrayList>(); - for (var entry : joinerTypeMap.entrySet()) { - var endIndexExclusive = entry.getKey(); - var keyFunctionLength = endIndexExclusive - startIndexInclusive; - // Consecutive EQUAL joiners are merged into a single composite keyFunction. - BiFunction keyFunction = switch (keyFunctionLength) { - case 1 -> castJoiner.getLeftMapping(startIndexInclusive); - case 2 -> { - var mapping1 = castJoiner.getLeftMapping(startIndexInclusive); - var mapping2 = castJoiner.getLeftMapping(startIndexInclusive + 1); - yield (a, b) -> new Pair<>(mapping1.apply(a, b), mapping2.apply(a, b)); - } - case 3 -> { - var mapping1 = castJoiner.getLeftMapping(startIndexInclusive); - var mapping2 = castJoiner.getLeftMapping(startIndexInclusive + 1); - var mapping3 = castJoiner.getLeftMapping(startIndexInclusive + 2); - yield (a, b) -> new Triple<>(mapping1.apply(a, b), mapping2.apply(a, b), mapping3.apply(a, b)); - } - case 4 -> { - var mapping1 = castJoiner.getLeftMapping(startIndexInclusive); - var mapping2 = castJoiner.getLeftMapping(startIndexInclusive + 1); - var mapping3 = castJoiner.getLeftMapping(startIndexInclusive + 2); - var mapping4 = castJoiner.getLeftMapping(startIndexInclusive + 3); - yield (a, b) -> new Quadruple<>(mapping1.apply(a, b), mapping2.apply(a, b), mapping3.apply(a, b), - mapping4.apply(a, b)); - } - default -> { - BiFunction[] mappings = new BiFunction[joinerCount]; - for (var i = 0; i < joinerCount; i++) { - var mapping = castJoiner.getLeftMapping(i); - mappings[i] = mapping; - } - yield (a, b) -> { - var mappingCount = mappings.length; - var result = new Object[mappingCount]; - for (var i = 0; i < mappingCount; i++) { - result[i] = mappings[i].apply(a, b); - } - return new IndexerKey(result); - }; - } - }; - keyFunctionList.add(keyFunction); - startIndexInclusive = endIndexExclusive; - } - var keyFunctionCount = keyFunctionList.size(); - return switch (keyFunctionCount) { - case 1 -> toKeysExtractor(keyFunctionList.get(0)); - case 2 -> { - var keyFunction1 = keyFunctionList.get(0); - var keyFunction2 = keyFunctionList.get(1); - yield tuple -> { - var a = tuple.factA; - var b = tuple.factB; - return IndexKeys.of(keyFunction1.apply(a, b), keyFunction2.apply(a, b)); - }; - } - default -> tuple -> { - var a = tuple.factA; - var b = tuple.factB; - var arr = new Object[keyFunctionCount]; - for (var i = 0; i < keyFunctionCount; i++) { - arr[i] = keyFunctionList.get(i).apply(a, b); - } - return IndexKeys.ofMany(arr); - }; - }; - } - - private static BiKeysExtractor toKeysExtractor(BiFunction keyFunction) { - return tuple -> { - var a = tuple.factA; - var b = tuple.factB; - return IndexKeys.of(keyFunction.apply(a, b)); - }; + var keyFunctions = extractKeyFunctions( + value -> BiMappingFunction. of(joiner, value), + BiKeyFunction::new); + return buildBiKeysExtractor(keyFunctions); } - @SuppressWarnings("unchecked") public TriKeysExtractor buildTriLeftKeysExtractor() { var joinerCount = joiner.getJoinerCount(); - var castJoiner = (DefaultQuadJoiner) joiner; if (joinerCount == 0) { - return tuple -> IndexKeys.none(); + return (tuple, oldKeys) -> IndexKeys.none(); } else if (joinerCount == 1) { - return toKeysExtractor(castJoiner.getLeftMapping(0)); - } - var startIndexInclusive = 0; - var keyFunctionList = new ArrayList>(); - for (var entry : joinerTypeMap.entrySet()) { - var endIndexExclusive = entry.getKey(); - var keyFunctionLength = endIndexExclusive - startIndexInclusive; - // Consecutive EQUAL joiners are merged into a single composite keyFunction. - TriFunction keyFunction = switch (keyFunctionLength) { - case 1 -> castJoiner.getLeftMapping(startIndexInclusive); - case 2 -> { - var mapping1 = castJoiner.getLeftMapping(startIndexInclusive); - var mapping2 = castJoiner.getLeftMapping(startIndexInclusive + 1); - yield (a, b, c) -> new Pair<>(mapping1.apply(a, b, c), mapping2.apply(a, b, c)); - } - case 3 -> { - var mapping1 = castJoiner.getLeftMapping(startIndexInclusive); - var mapping2 = castJoiner.getLeftMapping(startIndexInclusive + 1); - var mapping3 = castJoiner.getLeftMapping(startIndexInclusive + 2); - yield (a, b, c) -> new Triple<>(mapping1.apply(a, b, c), mapping2.apply(a, b, c), - mapping3.apply(a, b, c)); - } - case 4 -> { - var mapping1 = castJoiner.getLeftMapping(startIndexInclusive); - var mapping2 = castJoiner.getLeftMapping(startIndexInclusive + 1); - var mapping3 = castJoiner.getLeftMapping(startIndexInclusive + 2); - var mapping4 = castJoiner.getLeftMapping(startIndexInclusive + 3); - yield (a, b, c) -> new Quadruple<>(mapping1.apply(a, b, c), mapping2.apply(a, b, c), - mapping3.apply(a, b, c), mapping4.apply(a, b, c)); - } - default -> { - TriFunction[] mappings = new TriFunction[joinerCount]; - for (var i = 0; i < joinerCount; i++) { - var mapping = castJoiner.getLeftMapping(i); - mappings[i] = mapping; - } - yield (a, b, c) -> { - var mappingCount = mappings.length; - var result = new Object[mappingCount]; - for (var i = 0; i < mappingCount; i++) { - result[i] = mappings[i].apply(a, b, c); - } - return new IndexerKey(result); - }; - } - }; - keyFunctionList.add(keyFunction); - startIndexInclusive = endIndexExclusive; + return buildTriKeysExtractor(new TriKeyFunction<>(TriMappingFunction.of(joiner, 0))); } - var keyFunctionCount = keyFunctionList.size(); - return switch (keyFunctionCount) { - case 1 -> toKeysExtractor(keyFunctionList.get(0)); - case 2 -> { - var keyFunction1 = keyFunctionList.get(0); - var keyFunction2 = keyFunctionList.get(1); - yield tuple -> { - var a = tuple.factA; - var b = tuple.factB; - var c = tuple.factC; - return IndexKeys.of(keyFunction1.apply(a, b, c), keyFunction2.apply(a, b, c)); - }; - } - default -> tuple -> { - var a = tuple.factA; - var b = tuple.factB; - var c = tuple.factC; - var arr = new Object[keyFunctionCount]; - for (var i = 0; i < keyFunctionCount; i++) { - arr[i] = keyFunctionList.get(i).apply(a, b, c); - } - return IndexKeys.ofMany(arr); - }; - }; + var keyFunctions = extractKeyFunctions( + value -> TriMappingFunction. of(joiner, value), + TriKeyFunction::new); + return buildTriKeysExtractor(keyFunctions); } - private static TriKeysExtractor toKeysExtractor(TriFunction keyFunction) { - return tuple -> { - var a = tuple.factA; - var b = tuple.factB; - var c = tuple.factC; - return IndexKeys.of(keyFunction.apply(a, b, c)); - }; - } - - @SuppressWarnings("unchecked") public QuadKeysExtractor buildQuadLeftKeysExtractor() { var joinerCount = joiner.getJoinerCount(); - var castJoiner = (DefaultPentaJoiner) joiner; if (joinerCount == 0) { - return tuple -> IndexKeys.none(); + return (tuple, oldKeys) -> IndexKeys.none(); } else if (joinerCount == 1) { - return toKeysExtractor(castJoiner.getLeftMapping(0)); + return buildQuadKeysExtractor(new QuadKeyFunction<>(QuadMappingFunction.of(joiner, 0))); } - var startIndexInclusive = 0; - var keyFunctionList = new ArrayList>(); - for (var entry : joinerTypeMap.entrySet()) { - var endIndexExclusive = entry.getKey(); - var keyFunctionLength = endIndexExclusive - startIndexInclusive; - // Consecutive EQUAL joiners are merged into a single composite keyFunction. - QuadFunction keyFunction = switch (keyFunctionLength) { - case 1 -> castJoiner.getLeftMapping(startIndexInclusive); - case 2 -> { - var mapping1 = castJoiner.getLeftMapping(startIndexInclusive); - var mapping2 = castJoiner.getLeftMapping(startIndexInclusive + 1); - yield (a, b, c, d) -> new Pair<>(mapping1.apply(a, b, c, d), mapping2.apply(a, b, c, d)); - } - case 3 -> { - var mapping1 = castJoiner.getLeftMapping(startIndexInclusive); - var mapping2 = castJoiner.getLeftMapping(startIndexInclusive + 1); - var mapping3 = castJoiner.getLeftMapping(startIndexInclusive + 2); - yield (a, b, c, d) -> new Triple<>(mapping1.apply(a, b, c, d), mapping2.apply(a, b, c, d), - mapping3.apply(a, b, c, d)); - } - case 4 -> { - var mapping1 = castJoiner.getLeftMapping(startIndexInclusive); - var mapping2 = castJoiner.getLeftMapping(startIndexInclusive + 1); - var mapping3 = castJoiner.getLeftMapping(startIndexInclusive + 2); - var mapping4 = castJoiner.getLeftMapping(startIndexInclusive + 3); - yield (a, b, c, d) -> new Quadruple<>(mapping1.apply(a, b, c, d), mapping2.apply(a, b, c, d), - mapping3.apply(a, b, c, d), mapping4.apply(a, b, c, d)); - } - default -> { - QuadFunction[] mappings = new QuadFunction[joinerCount]; - for (var i = 0; i < joinerCount; i++) { - var mapping = castJoiner.getLeftMapping(i); - mappings[i] = mapping; - } - yield (a, b, c, d) -> { - var mappingCount = mappings.length; - var result = new Object[mappingCount]; - for (var i = 0; i < mappingCount; i++) { - result[i] = mappings[i].apply(a, b, c, d); - } - return new IndexerKey(result); - }; - } - }; - keyFunctionList.add(keyFunction); - startIndexInclusive = endIndexExclusive; - } - var keyFunctionCount = keyFunctionList.size(); - return switch (keyFunctionList.size()) { - case 1 -> toKeysExtractor(keyFunctionList.get(0)); - case 2 -> { - var keyFunction1 = keyFunctionList.get(0); - var keyFunction2 = keyFunctionList.get(1); - yield tuple -> { - var a = tuple.factA; - var b = tuple.factB; - var c = tuple.factC; - var d = tuple.factD; - return IndexKeys.of(keyFunction1.apply(a, b, c, d), keyFunction2.apply(a, b, c, d)); - }; - } - default -> tuple -> { - var a = tuple.factA; - var b = tuple.factB; - var c = tuple.factC; - var d = tuple.factD; - var arr = new Object[keyFunctionCount]; - for (var i = 0; i < keyFunctionCount; i++) { - arr[i] = keyFunctionList.get(i).apply(a, b, c, d); - } - return IndexKeys.ofMany(arr); - }; - }; - } - - private static QuadKeysExtractor toKeysExtractor(QuadFunction keyFunction) { - return tuple -> { - var a = tuple.factA; - var b = tuple.factB; - var c = tuple.factC; - var d = tuple.factD; - return IndexKeys.of(keyFunction.apply(a, b, c, d)); - }; + var keyFunctions = extractKeyFunctions( + (value) -> QuadMappingFunction. of(joiner, value), + QuadKeyFunction::new); + return buildQuadKeysExtractor(keyFunctions); } public UniKeysExtractor buildRightKeysExtractor() { - return buildUniKeysExtractor(joiner::getRightMapping); + return buildUniKeysExtractor(value -> { + var mapping = joiner.getRightMapping(value); + return mapping::apply; + }); } public Indexer buildIndexer(boolean isLeftBridge) { @@ -519,29 +240,197 @@ public Indexer buildIndexer(boolean isLeftBridge) { return downstreamIndexerSupplier.get(); } - /** - * Represents a function which extracts index keys from a tuple. - * - * @param - */ - @FunctionalInterface - public interface KeysExtractor extends Function { + private static UniKeysExtractor buildUniKeysExtractor(UniKeyFunction keyFunction) { + return (tuple, oldKeys) -> IndexKeys.of(keyFunction.apply(tuple.factA, oldKeys)); } - @FunctionalInterface - public interface UniKeysExtractor extends KeysExtractor> { + private static UniKeysExtractor buildUniKeysExtractor(List> keyFunctionList) { + var keyFunctionCount = keyFunctionList.size(); + return switch (keyFunctionCount) { + case 1 -> buildUniKeysExtractor(keyFunctionList.get(0)); + case 2 -> { + var keyFunction1 = keyFunctionList.get(0); + var keyFunction2 = keyFunctionList.get(1); + yield (tuple, oldKeys) -> { + var a = tuple.factA; + var key1 = keyFunction1.apply(a, oldKeys); + var key2 = keyFunction2.apply(a, oldKeys); + return IndexKeys.of(key1, key2, oldKeys); + }; + } + default -> (tuple, oldKeys) -> { + var a = tuple.factA; + var arr = new Object[keyFunctionCount]; + if (oldKeys == null) { + for (var i = 0; i < keyFunctionCount; i++) { + arr[i] = keyFunctionList.get(i).apply(a, null); + } + } else { + var oldIndexKeys = ((ManyIndexKeys) oldKeys).properties(); + var sameOldIndexKeys = true; + for (var i = 0; i < keyFunctionCount; i++) { + var oldIndexKey = oldIndexKeys[i]; + var key = keyFunctionList.get(i).apply(a, oldIndexKey); + if (sameOldIndexKeys) { + sameOldIndexKeys = Objects.equals(key, oldIndexKey); + } + arr[i] = key; + } + if (sameOldIndexKeys) { + return oldKeys; + } + } + return IndexKeys.ofMany(arr); + }; + }; } - @FunctionalInterface - public interface BiKeysExtractor extends KeysExtractor> { + private static BiKeysExtractor buildBiKeysExtractor(BiKeyFunction keyFunction) { + return (tuple, oldKeys) -> IndexKeys.of(keyFunction.apply(tuple.factA, tuple.factB, oldKeys)); } - @FunctionalInterface - public interface TriKeysExtractor extends KeysExtractor> { + private static BiKeysExtractor buildBiKeysExtractor(List> keyFunctionList) { + var keyFunctionCount = keyFunctionList.size(); + return switch (keyFunctionCount) { + case 1 -> buildBiKeysExtractor(keyFunctionList.get(0)); + case 2 -> { + var keyFunction1 = keyFunctionList.get(0); + var keyFunction2 = keyFunctionList.get(1); + yield (tuple, oldKeys) -> { + var a = tuple.factA; + var b = tuple.factB; + var key1 = keyFunction1.apply(a, b, oldKeys); + var key2 = keyFunction2.apply(a, b, oldKeys); + return IndexKeys.of(key1, key2, oldKeys); + }; + } + default -> (tuple, oldKeys) -> { + var a = tuple.factA; + var b = tuple.factB; + var arr = new Object[keyFunctionCount]; + if (oldKeys == null) { + for (var i = 0; i < keyFunctionCount; i++) { + arr[i] = keyFunctionList.get(i).apply(a, b, null); + } + } else { + var oldIndexKeys = ((ManyIndexKeys) oldKeys).properties(); + var sameOldIndexKeys = true; + for (var i = 0; i < keyFunctionCount; i++) { + var oldIndexKey = oldIndexKeys[i]; + var key = keyFunctionList.get(i).apply(a, b, oldIndexKey); + if (sameOldIndexKeys) { + sameOldIndexKeys = Objects.equals(key, oldIndexKey); + } + arr[i] = key; + } + if (sameOldIndexKeys) { + return oldKeys; + } + } + return IndexKeys.ofMany(arr); + }; + }; } - @FunctionalInterface - public interface QuadKeysExtractor extends KeysExtractor> { + private static TriKeysExtractor buildTriKeysExtractor(TriKeyFunction keyFunction) { + return (tuple, oldKeys) -> IndexKeys.of(keyFunction.apply(tuple.factA, tuple.factB, tuple.factC, oldKeys)); + } + + private static TriKeysExtractor buildTriKeysExtractor(List> keyFunctionList) { + var keyFunctionCount = keyFunctionList.size(); + return switch (keyFunctionCount) { + case 1 -> buildTriKeysExtractor(keyFunctionList.get(0)); + case 2 -> { + var keyFunction1 = keyFunctionList.get(0); + var keyFunction2 = keyFunctionList.get(1); + yield (tuple, oldKeys) -> { + var a = tuple.factA; + var b = tuple.factB; + var c = tuple.factC; + var key1 = keyFunction1.apply(a, b, c, oldKeys); + var key2 = keyFunction2.apply(a, b, c, oldKeys); + return IndexKeys.of(key1, key2, oldKeys); + }; + } + default -> (tuple, oldKeys) -> { + var a = tuple.factA; + var b = tuple.factB; + var c = tuple.factC; + var arr = new Object[keyFunctionCount]; + if (oldKeys == null) { + for (var i = 0; i < keyFunctionCount; i++) { + arr[i] = keyFunctionList.get(i).apply(a, b, c, null); + } + } else { + var oldIndexKeys = ((ManyIndexKeys) oldKeys).properties(); + var sameOldIndexKeys = true; + for (var i = 0; i < keyFunctionCount; i++) { + var oldIndexKey = oldIndexKeys[i]; + var key = keyFunctionList.get(i).apply(a, b, c, oldIndexKey); + if (sameOldIndexKeys) { + sameOldIndexKeys = Objects.equals(key, oldIndexKey); + } + arr[i] = key; + } + if (sameOldIndexKeys) { + return oldKeys; + } + } + return IndexKeys.ofMany(arr); + }; + }; + } + + private static QuadKeysExtractor buildQuadKeysExtractor(QuadKeyFunction keyFunction) { + return (tuple, oldKeys) -> IndexKeys.of(keyFunction.apply(tuple.factA, tuple.factB, tuple.factC, tuple.factD, oldKeys)); + } + + private static QuadKeysExtractor + buildQuadKeysExtractor(List> keyFunctionList) { + var keyFunctionCount = keyFunctionList.size(); + return switch (keyFunctionCount) { + case 1 -> buildQuadKeysExtractor(keyFunctionList.get(0)); + case 2 -> { + var keyFunction1 = keyFunctionList.get(0); + var keyFunction2 = keyFunctionList.get(1); + yield (tuple, oldKeys) -> { + var a = tuple.factA; + var b = tuple.factB; + var c = tuple.factC; + var d = tuple.factD; + var key1 = keyFunction1.apply(a, b, c, d, oldKeys); + var key2 = keyFunction2.apply(a, b, c, d, oldKeys); + return IndexKeys.of(key1, key2, oldKeys); + }; + } + default -> (tuple, oldKeys) -> { + var a = tuple.factA; + var b = tuple.factB; + var c = tuple.factC; + var d = tuple.factD; + var arr = new Object[keyFunctionCount]; + if (oldKeys == null) { + for (var i = 0; i < keyFunctionCount; i++) { + arr[i] = keyFunctionList.get(i).apply(a, b, c, d, null); + } + } else { + var oldIndexKeys = ((ManyIndexKeys) oldKeys).properties(); + var sameOldIndexKeys = true; + for (var i = 0; i < keyFunctionCount; i++) { + var oldIndexKey = oldIndexKeys[i]; + var key = keyFunctionList.get(i).apply(a, b, c, d, oldIndexKey); + if (sameOldIndexKeys) { + sameOldIndexKeys = Objects.equals(key, oldIndexKey); + } + arr[i] = key; + } + if (sameOldIndexKeys) { + return oldKeys; + } + } + return IndexKeys.ofMany(arr); + }; + }; } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/KeyFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/KeyFunction.java new file mode 100644 index 0000000000..9c82e2d114 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/KeyFunction.java @@ -0,0 +1,4 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +interface KeyFunction { +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/KeysExtractor.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/KeysExtractor.java new file mode 100644 index 0000000000..d33dbfc557 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/KeysExtractor.java @@ -0,0 +1,15 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +import java.util.function.BiFunction; + +import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple; + +/** + * Represents a function which extracts index keys from a tuple. + * + * @param + */ +@FunctionalInterface +public interface KeysExtractor + extends BiFunction { +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/ManyIndexKeys.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/ManyIndexKeys.java index 5ddb6f05a1..2b10773f2e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/ManyIndexKeys.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/ManyIndexKeys.java @@ -15,9 +15,6 @@ public Key_ get(int id) { @Override public boolean equals(Object o) { - if (this == o) { // Due to the use of SINGLE_NULL, this is possible. - return true; - } return o instanceof ManyIndexKeys other && Arrays.equals(properties, other.properties); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadKeyFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadKeyFunction.java new file mode 100644 index 0000000000..0fa3503688 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadKeyFunction.java @@ -0,0 +1,110 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import ai.timefold.solver.core.api.function.PentaFunction; +import ai.timefold.solver.core.impl.util.Pair; +import ai.timefold.solver.core.impl.util.Quadruple; +import ai.timefold.solver.core.impl.util.Triple; + +final class QuadKeyFunction + implements PentaFunction, KeyFunction { + + private final int keyId; + private final int mappingFunctionCount; + private final QuadMappingFunction[] mappingFunctions; + private final QuadMappingFunction mappingFunction0; + private final QuadMappingFunction mappingFunction1; + private final QuadMappingFunction mappingFunction2; + private final QuadMappingFunction mappingFunction3; + + public QuadKeyFunction(QuadMappingFunction mappingFunction) { + this(-1, Collections.singletonList(mappingFunction)); + } + + @SuppressWarnings("unchecked") + public QuadKeyFunction(int keyId, List> mappingFunctionList) { + this.keyId = keyId; + this.mappingFunctionCount = mappingFunctionList.size(); + this.mappingFunctions = mappingFunctionList.toArray(new QuadMappingFunction[0]); + this.mappingFunction0 = mappingFunctions[0]; + this.mappingFunction1 = mappingFunctionCount > 1 ? mappingFunctions[1] : null; + this.mappingFunction2 = mappingFunctionCount > 2 ? mappingFunctions[2] : null; + this.mappingFunction3 = mappingFunctionCount > 3 ? mappingFunctions[3] : null; + } + + @Override + public Object apply(A a, B b, C c, D d, Object oldKey) { + return switch (mappingFunctionCount) { + case 1 -> apply1(a, b, c, d); + case 2 -> apply2(a, b, c, d, oldKey); + case 3 -> apply3(a, b, c, d, oldKey); + case 4 -> apply4(a, b, c, d, oldKey); + default -> applyMany(a, b, c, d, oldKey); + }; + } + + private Object apply1(A a, B b, C c, D d) { + return mappingFunction0.apply(a, b, c, d); + } + + @SuppressWarnings("unchecked") + private Object apply2(A a, B b, C c, D d, Object oldKey) { + var subkey1 = mappingFunction0.apply(a, b, c, d); + var subkey2 = mappingFunction1.apply(a, b, c, d); + if (oldKey == null) { + return new Pair<>(subkey1, subkey2); + } + return ((Pair) UniKeyFunction.extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2); + } + + @SuppressWarnings("unchecked") + private Object apply3(A a, B b, C c, D d, Object oldKey) { + var subkey1 = mappingFunction0.apply(a, b, c, d); + var subkey2 = mappingFunction1.apply(a, b, c, d); + var subkey3 = mappingFunction2.apply(a, b, c, d); + if (oldKey == null) { + return new Triple<>(subkey1, subkey2, subkey3); + } + return ((Triple) UniKeyFunction.extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2, subkey3); + } + + @SuppressWarnings("unchecked") + private Object apply4(A a, B b, C c, D d, Object oldKey) { + var subkey1 = mappingFunction0.apply(a, b, c, d); + var subkey2 = mappingFunction1.apply(a, b, c, d); + var subkey3 = mappingFunction2.apply(a, b, c, d); + var subkey4 = mappingFunction3.apply(a, b, c, d); + if (oldKey == null) { + return new Quadruple<>(subkey1, subkey2, subkey3, subkey4); + } + return ((Quadruple) UniKeyFunction.extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2, subkey3, subkey4); + } + + private Object applyMany(A a, B b, C c, D d, Object oldKey) { + var result = new Object[mappingFunctionCount]; + if (oldKey == null) { + for (var i = 0; i < mappingFunctionCount; i++) { + result[i] = mappingFunctions[i].apply(a, b, c, d); + } + } else { + var oldArray = ((IndexerKey) UniKeyFunction.extractSubkey(keyId, oldKey)).properties(); + var subKeysEqual = true; + for (var i = 0; i < mappingFunctionCount; i++) { + var subkey = mappingFunctions[i].apply(a, b, c, d); + subKeysEqual = subKeysEqual && Objects.equals(subkey, oldArray[i]); + result[i] = subkey; + } + if (subKeysEqual) { + return oldKey; + } + } + return new IndexerKey(result); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadKeysExtractor.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadKeysExtractor.java new file mode 100644 index 0000000000..6c18d36cc3 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadKeysExtractor.java @@ -0,0 +1,8 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.QuadTuple; + +@FunctionalInterface +public interface QuadKeysExtractor extends KeysExtractor> { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadMappingFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadMappingFunction.java new file mode 100644 index 0000000000..294572d16b --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadMappingFunction.java @@ -0,0 +1,16 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +import ai.timefold.solver.core.api.function.QuadFunction; +import ai.timefold.solver.core.impl.score.stream.common.AbstractJoiner; +import ai.timefold.solver.core.impl.score.stream.common.penta.DefaultPentaJoiner; + +@FunctionalInterface +interface QuadMappingFunction extends QuadFunction { + + static QuadMappingFunction of(AbstractJoiner joiner, int index) { + var castJoiner = (DefaultPentaJoiner) joiner; + var mapping = castJoiner.getLeftMapping(index); + return mapping::apply; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriKeyFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriKeyFunction.java new file mode 100644 index 0000000000..51464cc65a --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriKeyFunction.java @@ -0,0 +1,110 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import ai.timefold.solver.core.api.function.QuadFunction; +import ai.timefold.solver.core.impl.util.Pair; +import ai.timefold.solver.core.impl.util.Quadruple; +import ai.timefold.solver.core.impl.util.Triple; + +final class TriKeyFunction + implements QuadFunction, KeyFunction { + + private final int keyId; + private final int mappingFunctionCount; + private final TriMappingFunction[] mappingFunctions; + private final TriMappingFunction mappingFunction0; + private final TriMappingFunction mappingFunction1; + private final TriMappingFunction mappingFunction2; + private final TriMappingFunction mappingFunction3; + + public TriKeyFunction(TriMappingFunction mappingFunction) { + this(0, Collections.singletonList(mappingFunction)); + } + + @SuppressWarnings("unchecked") + public TriKeyFunction(int keyId, List> mappingFunctionList) { + this.keyId = keyId; + this.mappingFunctionCount = mappingFunctionList.size(); + this.mappingFunctions = mappingFunctionList.toArray(new TriMappingFunction[0]); + this.mappingFunction0 = mappingFunctions[0]; + this.mappingFunction1 = mappingFunctionCount > 1 ? mappingFunctions[1] : null; + this.mappingFunction2 = mappingFunctionCount > 2 ? mappingFunctions[2] : null; + this.mappingFunction3 = mappingFunctionCount > 3 ? mappingFunctions[3] : null; + } + + @Override + public Object apply(A a, B b, C c, Object oldKey) { + return switch (mappingFunctionCount) { + case 1 -> apply1(a, b, c); + case 2 -> apply2(a, b, c, oldKey); + case 3 -> apply3(a, b, c, oldKey); + case 4 -> apply4(a, b, c, oldKey); + default -> applyMany(a, b, c, oldKey); + }; + } + + private Object apply1(A a, B b, C c) { + return mappingFunction0.apply(a, b, c); + } + + @SuppressWarnings("unchecked") + private Object apply2(A a, B b, C c, Object oldKey) { + var subkey1 = mappingFunction0.apply(a, b, c); + var subkey2 = mappingFunction1.apply(a, b, c); + if (oldKey == null) { + return new Pair<>(subkey1, subkey2); + } + return ((Pair) UniKeyFunction.extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2); + } + + @SuppressWarnings("unchecked") + private Object apply3(A a, B b, C c, Object oldKey) { + var subkey1 = mappingFunction0.apply(a, b, c); + var subkey2 = mappingFunction1.apply(a, b, c); + var subkey3 = mappingFunction2.apply(a, b, c); + if (oldKey == null) { + return new Triple<>(subkey1, subkey2, subkey3); + } + return ((Triple) UniKeyFunction.extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2, subkey3); + } + + @SuppressWarnings("unchecked") + private Object apply4(A a, B b, C c, Object oldKey) { + var subkey1 = mappingFunction0.apply(a, b, c); + var subkey2 = mappingFunction1.apply(a, b, c); + var subkey3 = mappingFunction2.apply(a, b, c); + var subkey4 = mappingFunction3.apply(a, b, c); + if (oldKey == null) { + return new Quadruple<>(subkey1, subkey2, subkey3, subkey4); + } + return ((Quadruple) UniKeyFunction.extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2, subkey3, subkey4); + } + + private Object applyMany(A a, B b, C c, Object oldKey) { + var result = new Object[mappingFunctionCount]; + if (oldKey == null) { + for (var i = 0; i < mappingFunctionCount; i++) { + result[i] = mappingFunctions[i].apply(a, b, c); + } + } else { + var oldArray = ((IndexerKey) UniKeyFunction.extractSubkey(keyId, oldKey)).properties(); + var subKeysEqual = true; + for (var i = 0; i < mappingFunctionCount; i++) { + var subkey = mappingFunctions[i].apply(a, b, c); + subKeysEqual = subKeysEqual && Objects.equals(subkey, oldArray[i]); + result[i] = subkey; + } + if (subKeysEqual) { + return oldKey; + } + } + return new IndexerKey(result); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriKeysExtractor.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriKeysExtractor.java new file mode 100644 index 0000000000..5be4e7975d --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriKeysExtractor.java @@ -0,0 +1,8 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TriTuple; + +@FunctionalInterface +public interface TriKeysExtractor extends KeysExtractor> { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriMappingFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriMappingFunction.java new file mode 100644 index 0000000000..ec6560fc33 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriMappingFunction.java @@ -0,0 +1,16 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +import ai.timefold.solver.core.api.function.TriFunction; +import ai.timefold.solver.core.impl.score.stream.common.AbstractJoiner; +import ai.timefold.solver.core.impl.score.stream.common.quad.DefaultQuadJoiner; + +@FunctionalInterface +interface TriMappingFunction extends TriFunction { + + static TriMappingFunction of(AbstractJoiner joiner, int index) { + var castJoiner = (DefaultQuadJoiner) joiner; + var mapping = castJoiner.getLeftMapping(index); + return mapping::apply; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TwoIndexKeys.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TwoIndexKeys.java index 4e2bcf2388..06f7b7a387 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TwoIndexKeys.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TwoIndexKeys.java @@ -1,16 +1,33 @@ package ai.timefold.solver.core.impl.score.stream.bavet.common.index; -record TwoIndexKeys(A propertyA, B propertyB) implements IndexKeys { +import java.util.Objects; + +record TwoIndexKeys(A keyA, B keyB) implements IndexKeys { @SuppressWarnings("unchecked") @Override public Key_ get(int id) { return (Key_) switch (id) { - case 0 -> propertyA; - case 1 -> propertyB; + case 0 -> keyA; + case 1 -> keyB; default -> throw new IllegalArgumentException("Impossible state: index (%d) > 1" .formatted(id)); }; } + @Override + public boolean equals(Object o) { + return o instanceof TwoIndexKeys that && + Objects.equals(keyA, that.keyA) + && Objects.equals(keyB, that.keyB); + } + + @Override + public int hashCode() { + var hash = 1; + hash = 31 * hash + Objects.hashCode(keyA); + hash = 31 * hash + Objects.hashCode(keyB); + return hash; + } + } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniKeyFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniKeyFunction.java new file mode 100644 index 0000000000..f774b1ad52 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniKeyFunction.java @@ -0,0 +1,155 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.BiFunction; + +import ai.timefold.solver.core.impl.util.Pair; +import ai.timefold.solver.core.impl.util.Quadruple; +import ai.timefold.solver.core.impl.util.Triple; + +final class UniKeyFunction + implements BiFunction, KeyFunction { + + private final int keyId; + private final int mappingFunctionCount; + private final UniMappingFunction[] mappingFunctions; + private final UniMappingFunction mappingFunction0; + private final UniMappingFunction mappingFunction1; + private final UniMappingFunction mappingFunction2; + private final UniMappingFunction mappingFunction3; + + public UniKeyFunction(UniMappingFunction mappingFunction) { + this(-1, Collections.singletonList(mappingFunction)); + } + + @SuppressWarnings("unchecked") + public UniKeyFunction(int keyId, List> mappingFunctionList) { + this.keyId = keyId; + this.mappingFunctionCount = mappingFunctionList.size(); + this.mappingFunctions = mappingFunctionList.toArray(new UniMappingFunction[0]); + this.mappingFunction0 = mappingFunctions[0]; + this.mappingFunction1 = mappingFunctionCount > 1 ? mappingFunctions[1] : null; + this.mappingFunction2 = mappingFunctionCount > 2 ? mappingFunctions[2] : null; + this.mappingFunction3 = mappingFunctionCount > 3 ? mappingFunctions[3] : null; + } + + public Object apply(A a) { + return switch (mappingFunctionCount) { + case 1 -> apply1(a); + case 2 -> apply2Fresh(a); + case 3 -> apply3Fresh(a); + case 4 -> apply4Fresh(a); + default -> applyManyFresh(a); + }; + } + + @Override + public Object apply(A a, Object oldKey) { + return oldKey == null ? apply(a) : switch (mappingFunctionCount) { + case 1 -> apply1(a); + case 2 -> apply2(a, oldKey); + case 3 -> apply3(a, oldKey); + case 4 -> apply4(a, oldKey); + default -> applyMany(a, oldKey); + }; + } + + private Object apply1(A a) { + return mappingFunction0.apply(a); + } + + private Object apply2Fresh(A a) { + var subkey1 = mappingFunction0.apply(a); + var subkey2 = mappingFunction1.apply(a); + return new Pair<>(subkey1, subkey2); + } + + private Object apply2(A a, Object oldKey) { + var subkey1 = mappingFunction0.apply(a); + var subkey2 = mappingFunction1.apply(a); + return buildPair(keyId, oldKey, subkey1, subkey2); + } + + @SuppressWarnings("unchecked") + static Pair buildPair(int keyId, Object oldKey, Object subkey1, Object subkey2) { + return ((Pair) extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2); + } + + @SuppressWarnings("unchecked") + static Key_ extractSubkey(int keyId, Object key) { + if (key instanceof IndexKeys indexKeys) { + return indexKeys.get(keyId); + } + // This is a single key, not an IndexKeys. + // See IndexKeys.of(Object o) for details. + return (Key_) key; + } + + private Object apply3Fresh(A a) { + var subkey1 = mappingFunction0.apply(a); + var subkey2 = mappingFunction1.apply(a); + var subkey3 = mappingFunction2.apply(a); + return new Triple<>(subkey1, subkey2, subkey3); + } + + private Object apply3(A a, Object oldKey) { + var subkey1 = mappingFunction0.apply(a); + var subkey2 = mappingFunction1.apply(a); + var subkey3 = mappingFunction2.apply(a); + return buildTriple(keyId, oldKey, subkey1, subkey2, subkey3); + } + + @SuppressWarnings("unchecked") + static Triple buildTriple(int keyId, Object oldKey, Object subkey1, Object subkey2, + Object subkey3) { + return ((Triple) extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2, subkey3); + } + + private Object apply4Fresh(A a) { + var subkey1 = mappingFunction0.apply(a); + var subkey2 = mappingFunction1.apply(a); + var subkey3 = mappingFunction2.apply(a); + var subkey4 = mappingFunction3.apply(a); + return new Quadruple<>(subkey1, subkey2, subkey3, subkey4); + } + + private Object apply4(A a, Object oldKey) { + var subkey1 = mappingFunction0.apply(a); + var subkey2 = mappingFunction1.apply(a); + var subkey3 = mappingFunction2.apply(a); + var subkey4 = mappingFunction3.apply(a); + return buildQuadruple(keyId, oldKey, subkey1, subkey2, subkey3, subkey4); + } + + @SuppressWarnings("unchecked") + static Quadruple buildQuadruple(int keyId, Object oldKey, Object subkey1, Object subkey2, + Object subkey3, Object subkey4) { + return ((Quadruple) extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2, subkey3, subkey4); + } + + private Object applyManyFresh(A a) { + var result = new Object[mappingFunctionCount]; + for (var i = 0; i < mappingFunctionCount; i++) { + result[i] = mappingFunctions[i].apply(a); + } + return new IndexerKey(result); + } + + private Object applyMany(A a, Object oldKey) { + var result = new Object[mappingFunctionCount]; + var oldArray = ((IndexerKey) extractSubkey(keyId, oldKey)).properties(); + var subKeysEqual = true; + for (var i = 0; i < mappingFunctionCount; i++) { + var subkey = mappingFunctions[i].apply(a); + subKeysEqual = subKeysEqual && Objects.equals(subkey, oldArray[i]); + result[i] = subkey; + } + return subKeysEqual ? oldKey : new IndexerKey(result); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniKeysExtractor.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniKeysExtractor.java new file mode 100644 index 0000000000..889fd5aa6d --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniKeysExtractor.java @@ -0,0 +1,8 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.UniTuple; + +@FunctionalInterface +public interface UniKeysExtractor extends KeysExtractor> { + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniMappingFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniMappingFunction.java new file mode 100644 index 0000000000..a9a6d342a5 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniMappingFunction.java @@ -0,0 +1,17 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.index; + +import java.util.function.Function; + +import ai.timefold.solver.core.impl.score.stream.common.AbstractJoiner; +import ai.timefold.solver.core.impl.score.stream.common.bi.DefaultBiJoiner; + +@FunctionalInterface +interface UniMappingFunction extends Function { + + static UniMappingFunction of(AbstractJoiner joiner, int index) { + var castJoiner = (DefaultBiJoiner) joiner; + var mapping = castJoiner.getLeftMapping(index); + return mapping::apply; + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/AbstractConditionalTupleLifecycle.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/AbstractConditionalTupleLifecycle.java deleted file mode 100644 index 8d3b61f14f..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/AbstractConditionalTupleLifecycle.java +++ /dev/null @@ -1,42 +0,0 @@ -package ai.timefold.solver.core.impl.score.stream.bavet.common.tuple; - -import java.util.Objects; - -public abstract class AbstractConditionalTupleLifecycle - implements TupleLifecycle { - - private final TupleLifecycle tupleLifecycle; - - protected AbstractConditionalTupleLifecycle(TupleLifecycle tupleLifecycle) { - this.tupleLifecycle = Objects.requireNonNull(tupleLifecycle); - } - - @Override - public final void insert(Tuple_ tuple) { - if (test(tuple)) { - tupleLifecycle.insert(tuple); - } - } - - @Override - public final void update(Tuple_ tuple) { - if (test(tuple)) { - tupleLifecycle.update(tuple); - } else { - tupleLifecycle.retract(tuple); - } - } - - @Override - public final void retract(Tuple_ tuple) { - tupleLifecycle.retract(tuple); - } - - abstract protected boolean test(Tuple_ tuple); - - @Override - public String toString() { - return "Conditional " + tupleLifecycle; - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/AggregatedTupleLifecycle.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/AggregatedTupleLifecycle.java index 9d9d95251c..b0959e77d1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/AggregatedTupleLifecycle.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/AggregatedTupleLifecycle.java @@ -1,36 +1,51 @@ package ai.timefold.solver.core.impl.score.stream.bavet.common.tuple; -final class AggregatedTupleLifecycle implements TupleLifecycle { - private final TupleLifecycle[] lifecycles; +import java.util.Arrays; +import java.util.Objects; - public AggregatedTupleLifecycle(TupleLifecycle[] lifecycles) { - this.lifecycles = lifecycles; +record AggregatedTupleLifecycle(TupleLifecycle... lifecycles) + implements + TupleLifecycle { + + @SafeVarargs + public AggregatedTupleLifecycle { + // Exists so that we have something to put the @SafeVarargs annotation on. } @Override public void insert(Tuple_ tuple) { - for (TupleLifecycle lifecycle : lifecycles) { + for (var lifecycle : lifecycles) { lifecycle.insert(tuple); } } @Override public void update(Tuple_ tuple) { - for (TupleLifecycle lifecycle : lifecycles) { + for (var lifecycle : lifecycles) { lifecycle.update(tuple); } } @Override public void retract(Tuple_ tuple) { - for (TupleLifecycle lifecycle : lifecycles) { + for (var lifecycle : lifecycles) { lifecycle.retract(tuple); } } + @Override + public boolean equals(Object o) { + return o instanceof AggregatedTupleLifecycle that && + Objects.deepEquals(lifecycles, that.lifecycles); + } + + @Override + public int hashCode() { + return Arrays.hashCode(lifecycles); + } + @Override public String toString() { return "size = " + lifecycles.length; } - } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/ConditionalTupleLifecycle.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/ConditionalTupleLifecycle.java new file mode 100644 index 0000000000..f808dd1e17 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/ConditionalTupleLifecycle.java @@ -0,0 +1,46 @@ +package ai.timefold.solver.core.impl.score.stream.bavet.common.tuple; + +import java.util.Objects; +import java.util.function.Predicate; + +record ConditionalTupleLifecycle(TupleLifecycle downstreamLifecycle, + TuplePredicate predicate) + implements + TupleLifecycle { + + ConditionalTupleLifecycle { + Objects.requireNonNull(downstreamLifecycle); + Objects.requireNonNull(predicate); + } + + @Override + public void insert(Tuple_ tuple) { + if (predicate.test(tuple)) { + downstreamLifecycle.insert(tuple); + } + } + + @Override + public void update(Tuple_ tuple) { + if (predicate.test(tuple)) { + downstreamLifecycle.update(tuple); + } else { + downstreamLifecycle.retract(tuple); + } + } + + @Override + public void retract(Tuple_ tuple) { + downstreamLifecycle.retract(tuple); + } + + @Override + public String toString() { + return "Conditional " + downstreamLifecycle; + } + + @FunctionalInterface + interface TuplePredicate extends Predicate { + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/LeftTupleLifecycleImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/LeftTupleLifecycleImpl.java index a1ee8dda2a..060eb96de5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/LeftTupleLifecycleImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/LeftTupleLifecycleImpl.java @@ -2,13 +2,12 @@ import java.util.Objects; -final class LeftTupleLifecycleImpl - implements TupleLifecycle { +record LeftTupleLifecycleImpl(LeftTupleLifecycle leftTupleLifecycle) + implements + TupleLifecycle { - private final LeftTupleLifecycle leftTupleLifecycle; - - LeftTupleLifecycleImpl(LeftTupleLifecycle leftTupleLifecycle) { - this.leftTupleLifecycle = Objects.requireNonNull(leftTupleLifecycle); + LeftTupleLifecycleImpl { + Objects.requireNonNull(leftTupleLifecycle); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/RightTupleLifecycleImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/RightTupleLifecycleImpl.java index 24119a9f53..d5d119f004 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/RightTupleLifecycleImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/RightTupleLifecycleImpl.java @@ -2,13 +2,12 @@ import java.util.Objects; -final class RightTupleLifecycleImpl - implements TupleLifecycle { +record RightTupleLifecycleImpl(RightTupleLifecycle rightTupleLifecycle) + implements + TupleLifecycle { - private final RightTupleLifecycle rightTupleLifecycle; - - RightTupleLifecycleImpl(RightTupleLifecycle rightTupleLifecycle) { - this.rightTupleLifecycle = Objects.requireNonNull(rightTupleLifecycle); + RightTupleLifecycleImpl { + Objects.requireNonNull(rightTupleLifecycle); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/TupleLifecycle.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/TupleLifecycle.java index 3ac17c22d7..ab54be2b2f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/TupleLifecycle.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/tuple/TupleLifecycle.java @@ -1,5 +1,13 @@ package ai.timefold.solver.core.impl.score.stream.bavet.common.tuple; +import java.util.Arrays; +import java.util.Objects; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + +import ai.timefold.solver.core.api.function.QuadPredicate; +import ai.timefold.solver.core.api.function.TriPredicate; + public interface TupleLifecycle { static TupleLifecycle ofLeft(LeftTupleLifecycle leftTupleLifecycle) { @@ -10,8 +18,37 @@ static TupleLifecycle ofRight(RightTupleL return new RightTupleLifecycleImpl<>(rightTupleLifecycle); } - static TupleLifecycle of(TupleLifecycle... tupleLifecycles) { - return new AggregatedTupleLifecycle<>(tupleLifecycles); + @SuppressWarnings({ "rawtypes", "unchecked" }) + @SafeVarargs + static TupleLifecycle aggregate(TupleLifecycle... tupleLifecycles) { + var distinctTupleLifecycles = Arrays.stream(Objects.requireNonNull(tupleLifecycles)) + .distinct() + .toArray(TupleLifecycle[]::new); + return switch (distinctTupleLifecycles.length) { + case 0 -> throw new IllegalStateException("Impossible state: there are no tuple lifecycles."); + case 1 -> distinctTupleLifecycles[0]; + default -> new AggregatedTupleLifecycle<>(distinctTupleLifecycles); + }; + } + + static TupleLifecycle> conditionally(TupleLifecycle> tupleLifecycle, Predicate predicate) { + return new ConditionalTupleLifecycle<>(tupleLifecycle, tuple -> predicate.test(tuple.factA)); + } + + static TupleLifecycle> conditionally(TupleLifecycle> tupleLifecycle, + BiPredicate predicate) { + return new ConditionalTupleLifecycle<>(tupleLifecycle, tuple -> predicate.test(tuple.factA, tuple.factB)); + } + + static TupleLifecycle> conditionally(TupleLifecycle> tupleLifecycle, + TriPredicate predicate) { + return new ConditionalTupleLifecycle<>(tupleLifecycle, tuple -> predicate.test(tuple.factA, tuple.factB, tuple.factC)); + } + + static TupleLifecycle> + conditionally(TupleLifecycle> tupleLifecycle, QuadPredicate predicate) { + return new ConditionalTupleLifecycle<>(tupleLifecycle, + tuple -> predicate.test(tuple.factA, tuple.factB, tuple.factC, tuple.factD)); } void insert(Tuple_ tuple); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetFilterQuadConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetFilterQuadConstraintStream.java index e59781158b..3713d23044 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetFilterQuadConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/BavetFilterQuadConstraintStream.java @@ -7,6 +7,7 @@ import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; import ai.timefold.solver.core.impl.score.stream.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.QuadTuple; +import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle; final class BavetFilterQuadConstraintStream extends BavetAbstractQuadConstraintStream { @@ -30,7 +31,7 @@ public BavetFilterQuadConstraintStream(BavetConstraintFactory constra @Override public > void buildNode(NodeBuildHelper buildHelper) { buildHelper.> putInsertUpdateRetract(this, childStreamList, - tupleLifecycle -> new ConditionalQuadTupleLifecycle<>(predicate, tupleLifecycle)); + tupleLifecycle -> TupleLifecycle.conditionally(tupleLifecycle, predicate)); } // ************************************************************************ @@ -46,7 +47,7 @@ public int hashCode() { public boolean equals(Object o) { if (this == o) { return true; - } else if (o instanceof BavetFilterQuadConstraintStream other) { + } else if (o instanceof BavetFilterQuadConstraintStream other) { return parent == other.parent && predicate == other.predicate; } else { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/ConditionalQuadTupleLifecycle.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/ConditionalQuadTupleLifecycle.java deleted file mode 100644 index 1fabeea5f1..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/quad/ConditionalQuadTupleLifecycle.java +++ /dev/null @@ -1,21 +0,0 @@ -package ai.timefold.solver.core.impl.score.stream.bavet.quad; - -import ai.timefold.solver.core.api.function.QuadPredicate; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractConditionalTupleLifecycle; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.QuadTuple; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle; - -final class ConditionalQuadTupleLifecycle extends AbstractConditionalTupleLifecycle> { - private final QuadPredicate predicate; - - public ConditionalQuadTupleLifecycle(QuadPredicate predicate, - TupleLifecycle> tupleLifecycle) { - super(tupleLifecycle); - this.predicate = predicate; - } - - @Override - protected boolean test(QuadTuple tuple) { - return predicate.test(tuple.factA, tuple.factB, tuple.factC, tuple.factD); - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetFilterTriConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetFilterTriConstraintStream.java index e45471b32a..4539dfe012 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetFilterTriConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/BavetFilterTriConstraintStream.java @@ -7,6 +7,7 @@ import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; import ai.timefold.solver.core.impl.score.stream.bavet.common.NodeBuildHelper; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TriTuple; +import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle; final class BavetFilterTriConstraintStream extends BavetAbstractTriConstraintStream { @@ -30,7 +31,7 @@ public BavetFilterTriConstraintStream(BavetConstraintFactory constrai @Override public > void buildNode(NodeBuildHelper buildHelper) { buildHelper.> putInsertUpdateRetract(this, childStreamList, - tupleLifecycle -> new ConditionalTriTupleLifecycle<>(predicate, tupleLifecycle)); + tupleLifecycle -> TupleLifecycle.conditionally(tupleLifecycle, predicate)); } // ************************************************************************ @@ -46,7 +47,7 @@ public int hashCode() { public boolean equals(Object o) { if (this == o) { return true; - } else if (o instanceof BavetFilterTriConstraintStream other) { + } else if (o instanceof BavetFilterTriConstraintStream other) { return parent == other.parent && predicate == other.predicate; } else { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/ConditionalTriTupleLifecycle.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/ConditionalTriTupleLifecycle.java deleted file mode 100644 index 0b78314a1f..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/tri/ConditionalTriTupleLifecycle.java +++ /dev/null @@ -1,21 +0,0 @@ -package ai.timefold.solver.core.impl.score.stream.bavet.tri; - -import ai.timefold.solver.core.api.function.TriPredicate; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractConditionalTupleLifecycle; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TriTuple; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle; - -final class ConditionalTriTupleLifecycle extends AbstractConditionalTupleLifecycle> { - private final TriPredicate predicate; - - public ConditionalTriTupleLifecycle(TriPredicate predicate, TupleLifecycle> tupleLifecycle) { - super(tupleLifecycle); - this.predicate = predicate; - } - - @Override - protected boolean test(TriTuple tuple) { - return predicate.test(tuple.factA, tuple.factB, tuple.factC); - } - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetFilterUniConstraintStream.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetFilterUniConstraintStream.java index 5a4e2b836f..2be6fc2dca 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetFilterUniConstraintStream.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/BavetFilterUniConstraintStream.java @@ -6,6 +6,7 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; import ai.timefold.solver.core.impl.score.stream.bavet.common.NodeBuildHelper; +import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.UniTuple; final class BavetFilterUniConstraintStream @@ -29,7 +30,7 @@ public BavetFilterUniConstraintStream(BavetConstraintFactory constrai @Override public > void buildNode(NodeBuildHelper buildHelper) { buildHelper.> putInsertUpdateRetract(this, childStreamList, - tupleLifecycle -> new ConditionalUniTupleLifecycle<>(predicate, tupleLifecycle)); + tupleLifecycle -> TupleLifecycle.conditionally(tupleLifecycle, predicate)); } // ************************************************************************ @@ -45,7 +46,7 @@ public int hashCode() { public boolean equals(Object o) { if (this == o) { return true; - } else if (o instanceof BavetFilterUniConstraintStream other) { + } else if (o instanceof BavetFilterUniConstraintStream other) { return parent == other.parent && predicate == other.predicate; } else { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/ConditionalUniTupleLifecycle.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/ConditionalUniTupleLifecycle.java deleted file mode 100644 index eccea6dd98..0000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/uni/ConditionalUniTupleLifecycle.java +++ /dev/null @@ -1,21 +0,0 @@ -package ai.timefold.solver.core.impl.score.stream.bavet.uni; - -import java.util.function.Predicate; - -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractConditionalTupleLifecycle; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.UniTuple; - -final class ConditionalUniTupleLifecycle extends AbstractConditionalTupleLifecycle> { - private final Predicate predicate; - - public ConditionalUniTupleLifecycle(Predicate predicate, TupleLifecycle> tupleLifecycle) { - super(tupleLifecycle); - this.predicate = predicate; - } - - @Override - protected boolean test(UniTuple tuple) { - return predicate.test(tuple.factA); - } -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/visual/NodeGraph.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/visual/NodeGraph.java index 69d4f03be0..7aca289e4a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/visual/NodeGraph.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/visual/NodeGraph.java @@ -15,10 +15,9 @@ import ai.timefold.solver.core.api.score.stream.Constraint; import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraint; import ai.timefold.solver.core.impl.score.stream.bavet.common.AbstractNode; +import ai.timefold.solver.core.impl.score.stream.bavet.common.AbstractTwoInputNode; import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetAbstractConstraintStream; import ai.timefold.solver.core.impl.score.stream.bavet.common.BavetStreamBinaryOperation; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle; -import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle; import ai.timefold.solver.core.impl.score.stream.bavet.uni.AbstractForEachUniNode; import ai.timefold.solver.core.impl.score.stream.bavet.uni.BavetForEachUniConstraintStream; @@ -119,7 +118,7 @@ private static String getMetadata(AbstractNode node) { metadata.put("style", "filled"); metadata.put("fillcolor", "#3e00ff"); metadata.put("fontcolor", "white"); - } else if (node instanceof LeftTupleLifecycle && node instanceof RightTupleLifecycle) { + } else if (node instanceof AbstractTwoInputNode) { // Nodes that join get a different color. metadata.put("style", "filled"); metadata.put("fillcolor", "#ff7700"); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/util/Pair.java b/core/src/main/java/ai/timefold/solver/core/impl/util/Pair.java index 5184a311d3..01b0e66883 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/util/Pair.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/util/Pair.java @@ -1,5 +1,7 @@ package ai.timefold.solver.core.impl.util; +import java.util.Objects; + /** * An immutable key-value tuple. * Two instances {@link Object#equals(Object) are equal} if both values in the first instance @@ -10,4 +12,23 @@ */ public record Pair(Key_ key, Value_ value) { + public Pair newIfDifferent(Key_ newA, Value_ newB) { + return Objects.equals(key, newA) && Objects.equals(value, newB) ? this : new Pair<>(newA, newB); + } + + @Override + public boolean equals(Object o) { + return o instanceof Pair other && + Objects.equals(key, other.key) && + Objects.equals(value, other.value); + } + + @Override + public int hashCode() { + var hash = 1; + hash = 31 * hash + Objects.hashCode(key); + hash = 31 * hash + Objects.hashCode(value); + return hash; + } + } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/util/Quadruple.java b/core/src/main/java/ai/timefold/solver/core/impl/util/Quadruple.java index 7c03e4eb19..1e49b7a55a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/util/Quadruple.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/util/Quadruple.java @@ -1,5 +1,7 @@ package ai.timefold.solver.core.impl.util; +import java.util.Objects; + /** * An immutable tuple of four values. * Two instances {@link Object#equals(Object) are equal} if all four values in the first instance @@ -12,4 +14,28 @@ */ public record Quadruple(A a, B b, C c, D d) { + public Quadruple newIfDifferent(A newA, B newB, C newC, D newD) { + return Objects.equals(a, newA) && Objects.equals(b, newB) && Objects.equals(c, newC) && Objects.equals(d, newD) ? this + : new Quadruple<>(newA, newB, newC, newD); + } + + @Override + public boolean equals(Object o) { + return o instanceof Quadruple other && + Objects.equals(a, other.a) && + Objects.equals(b, other.b) && + Objects.equals(c, other.c) && + Objects.equals(d, other.d); + } + + @Override + public int hashCode() { + var hash = 1; + hash = 31 * hash + Objects.hashCode(a); + hash = 31 * hash + Objects.hashCode(b); + hash = 31 * hash + Objects.hashCode(c); + hash = 31 * hash + Objects.hashCode(d); + return hash; + } + } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/util/Triple.java b/core/src/main/java/ai/timefold/solver/core/impl/util/Triple.java index c35fc84e46..218e2b8a9d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/util/Triple.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/util/Triple.java @@ -1,5 +1,7 @@ package ai.timefold.solver.core.impl.util; +import java.util.Objects; + /** * An immutable tuple of three values. * Two instances {@link Object#equals(Object) are equal} if all three values in the first instance @@ -11,4 +13,26 @@ */ public record Triple(A a, B b, C c) { + public Triple newIfDifferent(A newA, B newB, C newC) { + return Objects.equals(a, newA) && Objects.equals(b, newB) && Objects.equals(c, newC) ? this + : new Triple<>(newA, newB, newC); + } + + @Override + public boolean equals(Object o) { + return o instanceof Triple other && + Objects.equals(a, other.a) && + Objects.equals(b, other.b) && + Objects.equals(c, other.c); + } + + @Override + public int hashCode() { + var hash = 1; + hash = 31 * hash + Objects.hashCode(a); + hash = 31 * hash + Objects.hashCode(b); + hash = 31 * hash + Objects.hashCode(c); + return hash; + } + } From 3569fd74e39f009d6868b01e365fb41c94bb0216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Tue, 21 Jan 2025 14:57:06 +0100 Subject: [PATCH 2/3] Reuse code --- .../bavet/common/index/BiKeyFunction.java | 56 +++++--------- .../bavet/common/index/KeyFunction.java | 42 ++++++++++ .../bavet/common/index/QuadKeyFunction.java | 56 +++++--------- .../bavet/common/index/TriKeyFunction.java | 56 +++++--------- .../bavet/common/index/UniKeyFunction.java | 77 ++----------------- 5 files changed, 108 insertions(+), 179 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiKeyFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiKeyFunction.java index 52be55e236..677772a446 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiKeyFunction.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/BiKeyFunction.java @@ -5,9 +5,6 @@ import java.util.Objects; import ai.timefold.solver.core.api.function.TriFunction; -import ai.timefold.solver.core.impl.util.Pair; -import ai.timefold.solver.core.impl.util.Quadruple; -import ai.timefold.solver.core.impl.util.Triple; final class BiKeyFunction implements TriFunction, KeyFunction { @@ -42,7 +39,7 @@ public Object apply(A a, B b, Object oldKey) { case 2 -> apply2(a, b, oldKey); case 3 -> apply3(a, b, oldKey); case 4 -> apply4(a, b, oldKey); - default -> applyMany(a, b, oldKey); + default -> oldKey == null ? applyManyFresh(a, b) : applyMany(a, b, oldKey); }; } @@ -50,59 +47,46 @@ private Object apply1(A a, B b) { return mappingFunction0.apply(a, b); } - @SuppressWarnings("unchecked") private Object apply2(A a, B b, Object oldKey) { var subkey1 = mappingFunction0.apply(a, b); var subkey2 = mappingFunction1.apply(a, b); - if (oldKey == null) { - return new Pair<>(subkey1, subkey2); - } - return ((Pair) UniKeyFunction.extractSubkey(keyId, oldKey)) - .newIfDifferent(subkey1, subkey2); + return KeyFunction.buildPair(keyId, oldKey, subkey1, subkey2); } - @SuppressWarnings("unchecked") private Object apply3(A a, B b, Object oldKey) { var subkey1 = mappingFunction0.apply(a, b); var subkey2 = mappingFunction1.apply(a, b); var subkey3 = mappingFunction2.apply(a, b); - if (oldKey == null) { - return new Triple<>(subkey1, subkey2, subkey3); - } - return ((Triple) UniKeyFunction.extractSubkey(keyId, oldKey)) - .newIfDifferent(subkey1, subkey2, subkey3); + return KeyFunction.buildTriple(keyId, oldKey, subkey1, subkey2, subkey3); } - @SuppressWarnings("unchecked") private Object apply4(A a, B b, Object oldKey) { var subkey1 = mappingFunction0.apply(a, b); var subkey2 = mappingFunction1.apply(a, b); var subkey3 = mappingFunction2.apply(a, b); var subkey4 = mappingFunction3.apply(a, b); - if (oldKey == null) { - return new Quadruple<>(subkey1, subkey2, subkey3, subkey4); + return KeyFunction.buildQuadruple(keyId, oldKey, subkey1, subkey2, subkey3, subkey4); + } + + private Object applyManyFresh(A a, B b) { + var result = new Object[mappingFunctionCount]; + for (var i = 0; i < mappingFunctionCount; i++) { + result[i] = mappingFunctions[i].apply(a, b); } - return ((Quadruple) UniKeyFunction.extractSubkey(keyId, oldKey)) - .newIfDifferent(subkey1, subkey2, subkey3, subkey4); + return new IndexerKey(result); } private Object applyMany(A a, B b, Object oldKey) { var result = new Object[mappingFunctionCount]; - if (oldKey == null) { - for (var i = 0; i < mappingFunctionCount; i++) { - result[i] = mappingFunctions[i].apply(a, b); - } - } else { - var oldArray = ((IndexerKey) UniKeyFunction.extractSubkey(keyId, oldKey)).properties(); - var subKeysEqual = true; - for (var i = 0; i < mappingFunctionCount; i++) { - var subkey = mappingFunctions[i].apply(a, b); - subKeysEqual = subKeysEqual && Objects.equals(subkey, oldArray[i]); - result[i] = subkey; - } - if (subKeysEqual) { - return oldKey; - } + var oldArray = ((IndexerKey) KeyFunction.extractSubkey(keyId, oldKey)).properties(); + var subKeysEqual = true; + for (var i = 0; i < mappingFunctionCount; i++) { + var subkey = mappingFunctions[i].apply(a, b); + subKeysEqual = subKeysEqual && Objects.equals(subkey, oldArray[i]); + result[i] = subkey; + } + if (subKeysEqual) { + return oldKey; } return new IndexerKey(result); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/KeyFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/KeyFunction.java index 9c82e2d114..70ac93490e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/KeyFunction.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/KeyFunction.java @@ -1,4 +1,46 @@ package ai.timefold.solver.core.impl.score.stream.bavet.common.index; +import ai.timefold.solver.core.impl.util.Pair; +import ai.timefold.solver.core.impl.util.Quadruple; +import ai.timefold.solver.core.impl.util.Triple; + interface KeyFunction { + + @SuppressWarnings("unchecked") + static Object buildPair(int keyId, Object oldKey, Object subkey1, Object subkey2) { + if (oldKey == null) { + return new Pair<>(subkey1, subkey2); + } + return ((Pair) extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2); + } + + @SuppressWarnings("unchecked") + static Object buildTriple(int keyId, Object oldKey, Object subkey1, Object subkey2, Object subkey3) { + if (oldKey == null) { + return new Triple<>(subkey1, subkey2, subkey3); + } + return ((Triple) extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2, subkey3); + } + + @SuppressWarnings("unchecked") + static Object buildQuadruple(int keyId, Object oldKey, Object subkey1, Object subkey2, + Object subkey3, Object subkey4) { + if (oldKey == null) { + return new Quadruple<>(subkey1, subkey2, subkey3, subkey4); + } + return ((Quadruple) extractSubkey(keyId, oldKey)) + .newIfDifferent(subkey1, subkey2, subkey3, subkey4); + } + + static Object extractSubkey(int keyId, Object key) { + if (key instanceof IndexKeys indexKeys) { + return indexKeys.get(keyId); + } + // This is a single key, not an IndexKeys. + // See IndexKeys.of(Object o) for details. + return key; + } + } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadKeyFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadKeyFunction.java index 0fa3503688..33d87330b0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadKeyFunction.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/QuadKeyFunction.java @@ -5,9 +5,6 @@ import java.util.Objects; import ai.timefold.solver.core.api.function.PentaFunction; -import ai.timefold.solver.core.impl.util.Pair; -import ai.timefold.solver.core.impl.util.Quadruple; -import ai.timefold.solver.core.impl.util.Triple; final class QuadKeyFunction implements PentaFunction, KeyFunction { @@ -42,7 +39,7 @@ public Object apply(A a, B b, C c, D d, Object oldKey) { case 2 -> apply2(a, b, c, d, oldKey); case 3 -> apply3(a, b, c, d, oldKey); case 4 -> apply4(a, b, c, d, oldKey); - default -> applyMany(a, b, c, d, oldKey); + default -> oldKey == null ? applyManyFresh(a, b, c, d) : applyMany(a, b, c, d, oldKey); }; } @@ -50,59 +47,46 @@ private Object apply1(A a, B b, C c, D d) { return mappingFunction0.apply(a, b, c, d); } - @SuppressWarnings("unchecked") private Object apply2(A a, B b, C c, D d, Object oldKey) { var subkey1 = mappingFunction0.apply(a, b, c, d); var subkey2 = mappingFunction1.apply(a, b, c, d); - if (oldKey == null) { - return new Pair<>(subkey1, subkey2); - } - return ((Pair) UniKeyFunction.extractSubkey(keyId, oldKey)) - .newIfDifferent(subkey1, subkey2); + return KeyFunction.buildPair(keyId, oldKey, subkey1, subkey2); } - @SuppressWarnings("unchecked") private Object apply3(A a, B b, C c, D d, Object oldKey) { var subkey1 = mappingFunction0.apply(a, b, c, d); var subkey2 = mappingFunction1.apply(a, b, c, d); var subkey3 = mappingFunction2.apply(a, b, c, d); - if (oldKey == null) { - return new Triple<>(subkey1, subkey2, subkey3); - } - return ((Triple) UniKeyFunction.extractSubkey(keyId, oldKey)) - .newIfDifferent(subkey1, subkey2, subkey3); + return KeyFunction.buildTriple(keyId, oldKey, subkey1, subkey2, subkey3); } - @SuppressWarnings("unchecked") private Object apply4(A a, B b, C c, D d, Object oldKey) { var subkey1 = mappingFunction0.apply(a, b, c, d); var subkey2 = mappingFunction1.apply(a, b, c, d); var subkey3 = mappingFunction2.apply(a, b, c, d); var subkey4 = mappingFunction3.apply(a, b, c, d); - if (oldKey == null) { - return new Quadruple<>(subkey1, subkey2, subkey3, subkey4); + return KeyFunction.buildQuadruple(keyId, oldKey, subkey1, subkey2, subkey3, subkey4); + } + + private Object applyManyFresh(A a, B b, C c, D d) { + var result = new Object[mappingFunctionCount]; + for (var i = 0; i < mappingFunctionCount; i++) { + result[i] = mappingFunctions[i].apply(a, b, c, d); } - return ((Quadruple) UniKeyFunction.extractSubkey(keyId, oldKey)) - .newIfDifferent(subkey1, subkey2, subkey3, subkey4); + return new IndexerKey(result); } private Object applyMany(A a, B b, C c, D d, Object oldKey) { var result = new Object[mappingFunctionCount]; - if (oldKey == null) { - for (var i = 0; i < mappingFunctionCount; i++) { - result[i] = mappingFunctions[i].apply(a, b, c, d); - } - } else { - var oldArray = ((IndexerKey) UniKeyFunction.extractSubkey(keyId, oldKey)).properties(); - var subKeysEqual = true; - for (var i = 0; i < mappingFunctionCount; i++) { - var subkey = mappingFunctions[i].apply(a, b, c, d); - subKeysEqual = subKeysEqual && Objects.equals(subkey, oldArray[i]); - result[i] = subkey; - } - if (subKeysEqual) { - return oldKey; - } + var oldArray = ((IndexerKey) KeyFunction.extractSubkey(keyId, oldKey)).properties(); + var subKeysEqual = true; + for (var i = 0; i < mappingFunctionCount; i++) { + var subkey = mappingFunctions[i].apply(a, b, c, d); + subKeysEqual = subKeysEqual && Objects.equals(subkey, oldArray[i]); + result[i] = subkey; + } + if (subKeysEqual) { + return oldKey; } return new IndexerKey(result); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriKeyFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriKeyFunction.java index 51464cc65a..9aeda7dab7 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriKeyFunction.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/TriKeyFunction.java @@ -5,9 +5,6 @@ import java.util.Objects; import ai.timefold.solver.core.api.function.QuadFunction; -import ai.timefold.solver.core.impl.util.Pair; -import ai.timefold.solver.core.impl.util.Quadruple; -import ai.timefold.solver.core.impl.util.Triple; final class TriKeyFunction implements QuadFunction, KeyFunction { @@ -42,7 +39,7 @@ public Object apply(A a, B b, C c, Object oldKey) { case 2 -> apply2(a, b, c, oldKey); case 3 -> apply3(a, b, c, oldKey); case 4 -> apply4(a, b, c, oldKey); - default -> applyMany(a, b, c, oldKey); + default -> oldKey == null ? applyManyFresh(a, b, c) : applyMany(a, b, c, oldKey); }; } @@ -50,59 +47,46 @@ private Object apply1(A a, B b, C c) { return mappingFunction0.apply(a, b, c); } - @SuppressWarnings("unchecked") private Object apply2(A a, B b, C c, Object oldKey) { var subkey1 = mappingFunction0.apply(a, b, c); var subkey2 = mappingFunction1.apply(a, b, c); - if (oldKey == null) { - return new Pair<>(subkey1, subkey2); - } - return ((Pair) UniKeyFunction.extractSubkey(keyId, oldKey)) - .newIfDifferent(subkey1, subkey2); + return KeyFunction.buildPair(keyId, oldKey, subkey1, subkey2); } - @SuppressWarnings("unchecked") private Object apply3(A a, B b, C c, Object oldKey) { var subkey1 = mappingFunction0.apply(a, b, c); var subkey2 = mappingFunction1.apply(a, b, c); var subkey3 = mappingFunction2.apply(a, b, c); - if (oldKey == null) { - return new Triple<>(subkey1, subkey2, subkey3); - } - return ((Triple) UniKeyFunction.extractSubkey(keyId, oldKey)) - .newIfDifferent(subkey1, subkey2, subkey3); + return KeyFunction.buildTriple(keyId, oldKey, subkey1, subkey2, subkey3); } - @SuppressWarnings("unchecked") private Object apply4(A a, B b, C c, Object oldKey) { var subkey1 = mappingFunction0.apply(a, b, c); var subkey2 = mappingFunction1.apply(a, b, c); var subkey3 = mappingFunction2.apply(a, b, c); var subkey4 = mappingFunction3.apply(a, b, c); - if (oldKey == null) { - return new Quadruple<>(subkey1, subkey2, subkey3, subkey4); + return KeyFunction.buildQuadruple(keyId, oldKey, subkey1, subkey2, subkey3, subkey4); + } + + private Object applyManyFresh(A a, B b, C c) { + var result = new Object[mappingFunctionCount]; + for (var i = 0; i < mappingFunctionCount; i++) { + result[i] = mappingFunctions[i].apply(a, b, c); } - return ((Quadruple) UniKeyFunction.extractSubkey(keyId, oldKey)) - .newIfDifferent(subkey1, subkey2, subkey3, subkey4); + return new IndexerKey(result); } private Object applyMany(A a, B b, C c, Object oldKey) { var result = new Object[mappingFunctionCount]; - if (oldKey == null) { - for (var i = 0; i < mappingFunctionCount; i++) { - result[i] = mappingFunctions[i].apply(a, b, c); - } - } else { - var oldArray = ((IndexerKey) UniKeyFunction.extractSubkey(keyId, oldKey)).properties(); - var subKeysEqual = true; - for (var i = 0; i < mappingFunctionCount; i++) { - var subkey = mappingFunctions[i].apply(a, b, c); - subKeysEqual = subKeysEqual && Objects.equals(subkey, oldArray[i]); - result[i] = subkey; - } - if (subKeysEqual) { - return oldKey; - } + var oldArray = ((IndexerKey) KeyFunction.extractSubkey(keyId, oldKey)).properties(); + var subKeysEqual = true; + for (var i = 0; i < mappingFunctionCount; i++) { + var subkey = mappingFunctions[i].apply(a, b, c); + subKeysEqual = subKeysEqual && Objects.equals(subkey, oldArray[i]); + result[i] = subkey; + } + if (subKeysEqual) { + return oldKey; } return new IndexerKey(result); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniKeyFunction.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniKeyFunction.java index f774b1ad52..691a8fdd59 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniKeyFunction.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/UniKeyFunction.java @@ -5,10 +5,6 @@ import java.util.Objects; import java.util.function.BiFunction; -import ai.timefold.solver.core.impl.util.Pair; -import ai.timefold.solver.core.impl.util.Quadruple; -import ai.timefold.solver.core.impl.util.Triple; - final class UniKeyFunction implements BiFunction, KeyFunction { @@ -35,24 +31,14 @@ public UniKeyFunction(int keyId, List> mappingFunctionList this.mappingFunction3 = mappingFunctionCount > 3 ? mappingFunctions[3] : null; } - public Object apply(A a) { - return switch (mappingFunctionCount) { - case 1 -> apply1(a); - case 2 -> apply2Fresh(a); - case 3 -> apply3Fresh(a); - case 4 -> apply4Fresh(a); - default -> applyManyFresh(a); - }; - } - @Override public Object apply(A a, Object oldKey) { - return oldKey == null ? apply(a) : switch (mappingFunctionCount) { + return switch (mappingFunctionCount) { case 1 -> apply1(a); case 2 -> apply2(a, oldKey); case 3 -> apply3(a, oldKey); case 4 -> apply4(a, oldKey); - default -> applyMany(a, oldKey); + default -> oldKey == null ? applyManyFresh(a) : applyMany(a, oldKey); }; } @@ -60,61 +46,17 @@ private Object apply1(A a) { return mappingFunction0.apply(a); } - private Object apply2Fresh(A a) { - var subkey1 = mappingFunction0.apply(a); - var subkey2 = mappingFunction1.apply(a); - return new Pair<>(subkey1, subkey2); - } - private Object apply2(A a, Object oldKey) { var subkey1 = mappingFunction0.apply(a); var subkey2 = mappingFunction1.apply(a); - return buildPair(keyId, oldKey, subkey1, subkey2); - } - - @SuppressWarnings("unchecked") - static Pair buildPair(int keyId, Object oldKey, Object subkey1, Object subkey2) { - return ((Pair) extractSubkey(keyId, oldKey)) - .newIfDifferent(subkey1, subkey2); - } - - @SuppressWarnings("unchecked") - static Key_ extractSubkey(int keyId, Object key) { - if (key instanceof IndexKeys indexKeys) { - return indexKeys.get(keyId); - } - // This is a single key, not an IndexKeys. - // See IndexKeys.of(Object o) for details. - return (Key_) key; - } - - private Object apply3Fresh(A a) { - var subkey1 = mappingFunction0.apply(a); - var subkey2 = mappingFunction1.apply(a); - var subkey3 = mappingFunction2.apply(a); - return new Triple<>(subkey1, subkey2, subkey3); + return KeyFunction.buildPair(keyId, oldKey, subkey1, subkey2); } private Object apply3(A a, Object oldKey) { var subkey1 = mappingFunction0.apply(a); var subkey2 = mappingFunction1.apply(a); var subkey3 = mappingFunction2.apply(a); - return buildTriple(keyId, oldKey, subkey1, subkey2, subkey3); - } - - @SuppressWarnings("unchecked") - static Triple buildTriple(int keyId, Object oldKey, Object subkey1, Object subkey2, - Object subkey3) { - return ((Triple) extractSubkey(keyId, oldKey)) - .newIfDifferent(subkey1, subkey2, subkey3); - } - - private Object apply4Fresh(A a) { - var subkey1 = mappingFunction0.apply(a); - var subkey2 = mappingFunction1.apply(a); - var subkey3 = mappingFunction2.apply(a); - var subkey4 = mappingFunction3.apply(a); - return new Quadruple<>(subkey1, subkey2, subkey3, subkey4); + return KeyFunction.buildTriple(keyId, oldKey, subkey1, subkey2, subkey3); } private Object apply4(A a, Object oldKey) { @@ -122,14 +64,7 @@ private Object apply4(A a, Object oldKey) { var subkey2 = mappingFunction1.apply(a); var subkey3 = mappingFunction2.apply(a); var subkey4 = mappingFunction3.apply(a); - return buildQuadruple(keyId, oldKey, subkey1, subkey2, subkey3, subkey4); - } - - @SuppressWarnings("unchecked") - static Quadruple buildQuadruple(int keyId, Object oldKey, Object subkey1, Object subkey2, - Object subkey3, Object subkey4) { - return ((Quadruple) extractSubkey(keyId, oldKey)) - .newIfDifferent(subkey1, subkey2, subkey3, subkey4); + return KeyFunction.buildQuadruple(keyId, oldKey, subkey1, subkey2, subkey3, subkey4); } private Object applyManyFresh(A a) { @@ -142,7 +77,7 @@ private Object applyManyFresh(A a) { private Object applyMany(A a, Object oldKey) { var result = new Object[mappingFunctionCount]; - var oldArray = ((IndexerKey) extractSubkey(keyId, oldKey)).properties(); + var oldArray = ((IndexerKey) KeyFunction.extractSubkey(keyId, oldKey)).properties(); var subKeysEqual = true; for (var i = 0; i < mappingFunctionCount; i++) { var subkey = mappingFunctions[i].apply(a); From 486a0bf7e11168e2306e066cb3aaa206eef68dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Tue, 21 Jan 2025 15:40:43 +0100 Subject: [PATCH 3/3] Less capturing lambdas --- .../bavet/common/index/IndexerFactory.java | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/IndexerFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/IndexerFactory.java index e7ee815007..4b4da3593a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/IndexerFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/index/IndexerFactory.java @@ -6,7 +6,6 @@ import java.util.NavigableMap; import java.util.Objects; import java.util.TreeMap; -import java.util.function.IntFunction; import java.util.function.Supplier; import ai.timefold.solver.core.impl.score.stream.JoinerType; @@ -91,22 +90,29 @@ public boolean hasJoiners() { } public UniKeysExtractor buildUniLeftKeysExtractor() { - return buildUniKeysExtractor(value -> UniMappingFunction.of(joiner, value)); + return buildUniKeysExtractor(UniMappingFunction::of); } - private UniKeysExtractor buildUniKeysExtractor(IntFunction> mappingExtractor) { + private UniKeysExtractor buildUniKeysExtractor(MappingExtractor> mappingExtractor) { var joinerCount = joiner.getJoinerCount(); if (joinerCount == 0) { return (tuple, oldKeys) -> IndexKeys.none(); } else if (joinerCount == 1) { - return buildUniKeysExtractor(new UniKeyFunction<>(mappingExtractor.apply(0))); + return buildUniKeysExtractor(new UniKeyFunction<>(mappingExtractor.apply(joiner, 0))); } var keyFunctions = extractKeyFunctions(mappingExtractor, UniKeyFunction::new); return buildUniKeysExtractor(keyFunctions); } + @FunctionalInterface + private interface MappingExtractor { + + MappingFunction_ apply(AbstractJoiner joiner, int value); + + } + private - List extractKeyFunctions(IntFunction mappingExtractor, + List extractKeyFunctions(MappingExtractor mappingExtractor, MappingToKeyFunction constructor) { var joinerCount = joiner.getJoinerCount(); var startIndexInclusive = 0; @@ -116,20 +122,20 @@ List extractKeyFunctions(IntFunction mappingExtr var keyFunctionLength = endIndexExclusive - startIndexInclusive; // Consecutive EQUAL joiners are merged into a single composite keyFunction. var keyFunctions = switch (keyFunctionLength) { - case 1 -> Collections.singletonList(mappingExtractor.apply(startIndexInclusive)); - case 2 -> List.of(mappingExtractor.apply(startIndexInclusive), - mappingExtractor.apply(startIndexInclusive + 1)); - case 3 -> List.of(mappingExtractor.apply(startIndexInclusive), - mappingExtractor.apply(startIndexInclusive + 1), - mappingExtractor.apply(startIndexInclusive + 2)); - case 4 -> List.of(mappingExtractor.apply(startIndexInclusive), - mappingExtractor.apply(startIndexInclusive + 1), - mappingExtractor.apply(startIndexInclusive + 2), - mappingExtractor.apply(startIndexInclusive + 3)); + case 1 -> Collections.singletonList(mappingExtractor.apply(joiner, startIndexInclusive)); + case 2 -> List.of(mappingExtractor.apply(joiner, startIndexInclusive), + mappingExtractor.apply(joiner, startIndexInclusive + 1)); + case 3 -> List.of(mappingExtractor.apply(joiner, startIndexInclusive), + mappingExtractor.apply(joiner, startIndexInclusive + 1), + mappingExtractor.apply(joiner, startIndexInclusive + 2)); + case 4 -> List.of(mappingExtractor.apply(joiner, startIndexInclusive), + mappingExtractor.apply(joiner, startIndexInclusive + 1), + mappingExtractor.apply(joiner, startIndexInclusive + 2), + mappingExtractor.apply(joiner, startIndexInclusive + 3)); default -> { var result = new ArrayList(keyFunctionLength); for (var i = 0; i < joinerCount; i++) { - var mapping = mappingExtractor.apply(i); + var mapping = mappingExtractor.apply(joiner, i); result.add(mapping); } yield result; @@ -155,9 +161,7 @@ public BiKeysExtractor buildBiLeftKeysExtractor() { } else if (joinerCount == 1) { return buildBiKeysExtractor(new BiKeyFunction<>(BiMappingFunction.of(joiner, 0))); } - var keyFunctions = extractKeyFunctions( - value -> BiMappingFunction. of(joiner, value), - BiKeyFunction::new); + var keyFunctions = extractKeyFunctions(BiMappingFunction:: of, BiKeyFunction::new); return buildBiKeysExtractor(keyFunctions); } @@ -168,9 +172,7 @@ public TriKeysExtractor buildTriLeftKeysExtractor() { } else if (joinerCount == 1) { return buildTriKeysExtractor(new TriKeyFunction<>(TriMappingFunction.of(joiner, 0))); } - var keyFunctions = extractKeyFunctions( - value -> TriMappingFunction. of(joiner, value), - TriKeyFunction::new); + var keyFunctions = extractKeyFunctions(TriMappingFunction:: of, TriKeyFunction::new); return buildTriKeysExtractor(keyFunctions); } @@ -181,16 +183,14 @@ public QuadKeysExtractor buildQuadLeftKeysExtractor() { } else if (joinerCount == 1) { return buildQuadKeysExtractor(new QuadKeyFunction<>(QuadMappingFunction.of(joiner, 0))); } - var keyFunctions = extractKeyFunctions( - (value) -> QuadMappingFunction. of(joiner, value), - QuadKeyFunction::new); + var keyFunctions = extractKeyFunctions(QuadMappingFunction:: of, QuadKeyFunction::new); return buildQuadKeysExtractor(keyFunctions); } public UniKeysExtractor buildRightKeysExtractor() { - return buildUniKeysExtractor(value -> { - var mapping = joiner.getRightMapping(value); - return mapping::apply; + return buildUniKeysExtractor((joiner, value) -> { + var castJoiner = (AbstractJoiner) joiner; + return castJoiner.getRightMapping(value)::apply; }); }