Skip to content

Commit e589713

Browse files
committed
Fix an update issue
1 parent 667b285 commit e589713

File tree

9 files changed

+157
-75
lines changed

9 files changed

+157
-75
lines changed

core/src/main/java/ai/timefold/solver/core/impl/move/director/MoveDirector.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public final <Entity_, Value_> void changeVariable(PlanningVariableMetaModel<Sol
8888
PlanningListVariableMetaModel<Solution_, Entity_, Value_> variableMetaModel, Entity_ sourceEntity, int sourceIndex,
8989
Entity_ destinationEntity, int destinationIndex) {
9090
if (sourceEntity == destinationEntity) {
91-
return moveValueInList(variableMetaModel, sourceEntity, sourceIndex, destinationIndex);
91+
return swapValues(variableMetaModel, sourceEntity, sourceIndex, destinationIndex);
9292
}
9393
var variableDescriptor = extractVariableDescriptor(variableMetaModel);
9494
externalScoreDirector.beforeListVariableChanged(variableDescriptor, sourceEntity, sourceIndex, sourceIndex + 1);
@@ -106,13 +106,13 @@ public final <Entity_, Value_> void changeVariable(PlanningVariableMetaModel<Sol
106106

107107
@SuppressWarnings("unchecked")
108108
@Override
109-
public final <Entity_, Value_> @Nullable Value_ moveValueInList(
109+
public final <Entity_, Value_> @Nullable Value_ swapValues(
110110
PlanningListVariableMetaModel<Solution_, Entity_, Value_> variableMetaModel, Entity_ entity, int sourceIndex,
111111
int destinationIndex) {
112112
if (sourceIndex == destinationIndex) {
113113
return null;
114114
} else if (sourceIndex > destinationIndex) { // Always start from the lower index.
115-
return moveValueInList(variableMetaModel, entity, destinationIndex, sourceIndex);
115+
return swapValues(variableMetaModel, entity, destinationIndex, sourceIndex);
116116
}
117117
var variableDescriptor = extractVariableDescriptor(variableMetaModel);
118118
var toIndex = destinationIndex + 1;
@@ -185,7 +185,8 @@ public final <Entity_, Value_> Value_ getValue(PlanningVariableMetaModel<Solutio
185185
}
186186

187187
@Override
188-
public <Entity_, Value_> int countValues(PlanningListVariableMetaModel<Solution_, Entity_, Value_> variableMetaModel, Entity_ entity) {
188+
public <Entity_, Value_> int countValues(PlanningListVariableMetaModel<Solution_, Entity_, Value_> variableMetaModel,
189+
Entity_ entity) {
189190
return extractVariableDescriptor(variableMetaModel).getValue(entity).size();
190191
}
191192

core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/bi/BiDatasetInstance.java

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
package ai.timefold.solver.core.impl.move.streams.dataset.bi;
22

3-
import java.util.ArrayList;
4-
import java.util.Iterator;
5-
import java.util.LinkedHashMap;
6-
import java.util.List;
7-
import java.util.Map;
8-
import java.util.NoSuchElementException;
9-
import java.util.Random;
10-
113
import ai.timefold.solver.core.impl.bavet.common.tuple.BiTuple;
124
import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataset;
135
import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDatasetInstance;
146
import ai.timefold.solver.core.impl.util.CollectionUtils;
157
import ai.timefold.solver.core.impl.util.ElementAwareList;
168
import ai.timefold.solver.core.impl.util.ElementAwareListEntry;
17-
189
import org.jspecify.annotations.NullMarked;
1910
import org.jspecify.annotations.Nullable;
2011

12+
import java.util.ArrayList;
13+
import java.util.Iterator;
14+
import java.util.LinkedHashMap;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.NoSuchElementException;
18+
import java.util.Random;
19+
2120
@NullMarked
2221
public final class BiDatasetInstance<Solution_, A, B>
2322
extends AbstractDatasetInstance<Solution_, BiTuple<A, B>> {
@@ -35,6 +34,21 @@ public void insert(BiTuple<A, B> tuple) {
3534
tuple.setStore(inputStoreIndex, entry);
3635
}
3736

37+
@Override
38+
public void update(BiTuple<A, B> tuple) {
39+
var actualTupleList = tuple.getStore(inputStoreIndex);
40+
if (actualTupleList == null) { // The tuple was not inserted yet.
41+
insert(tuple);
42+
return;
43+
}
44+
var expectedTupleList = tupleListMap.get(tuple.factA);
45+
if (actualTupleList == expectedTupleList) {
46+
return; // Changing the tuple did not change the key.
47+
}
48+
retract(tuple);
49+
insert(tuple);
50+
}
51+
3852
@Override
3953
public void retract(BiTuple<A, B> tuple) {
4054
ElementAwareListEntry<BiTuple<A, B>> entry = tuple.removeStore(inputStoreIndex);

core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/common/AbstractDatasetInstance.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package ai.timefold.solver.core.impl.move.streams.dataset.common;
22

3-
import java.util.Iterator;
4-
import java.util.Objects;
5-
import java.util.Random;
6-
73
import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple;
84
import ai.timefold.solver.core.impl.bavet.common.tuple.TupleLifecycle;
9-
105
import org.jspecify.annotations.NullMarked;
116

7+
import java.util.Iterator;
8+
import java.util.Objects;
9+
import java.util.Random;
10+
1211
@NullMarked
1312
public abstract class AbstractDatasetInstance<Solution_, Tuple_ extends AbstractTuple>
1413
implements TupleLifecycle<Tuple_> {
@@ -25,11 +24,6 @@ public AbstractDataset<Solution_, Tuple_> getParent() {
2524
return parent;
2625
}
2726

28-
@Override
29-
public void update(Tuple_ tuple) {
30-
// No need to do anything.
31-
}
32-
3327
public abstract Iterator<Tuple_> iterator();
3428

3529
public abstract Iterator<Tuple_> iterator(Random workingRandom);

core/src/main/java/ai/timefold/solver/core/impl/move/streams/dataset/uni/UniDatasetInstance.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
package ai.timefold.solver.core.impl.move.streams.dataset.uni;
22

3-
import java.util.Iterator;
4-
import java.util.Random;
5-
63
import ai.timefold.solver.core.impl.bavet.common.tuple.UniTuple;
74
import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDataset;
85
import ai.timefold.solver.core.impl.move.streams.dataset.common.AbstractDatasetInstance;
96
import ai.timefold.solver.core.impl.util.ElementAwareList;
107
import ai.timefold.solver.core.impl.util.ElementAwareListEntry;
11-
128
import org.jspecify.annotations.NullMarked;
139

10+
import java.util.Iterator;
11+
import java.util.Random;
12+
1413
@NullMarked
1514
public final class UniDatasetInstance<Solution_, A>
1615
extends AbstractDatasetInstance<Solution_, UniTuple<A>> {
@@ -27,6 +26,11 @@ public void insert(UniTuple<A> tuple) {
2726
tuple.setStore(inputStoreIndex, entry);
2827
}
2928

29+
@Override
30+
public void update(UniTuple<A> tuple) {
31+
// No need to do anything.
32+
}
33+
3034
@Override
3135
public void retract(UniTuple<A> tuple) {
3236
ElementAwareListEntry<UniTuple<A>> entry = tuple.removeStore(inputStoreIndex);

core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/move/ListChangeMove.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
package ai.timefold.solver.core.impl.move.streams.maybeapi.generic.move;
22

3-
import java.util.Collection;
4-
import java.util.Collections;
5-
import java.util.List;
6-
import java.util.Objects;
7-
83
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
94
import ai.timefold.solver.core.api.domain.variable.PlanningListVariable;
105
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel;
116
import ai.timefold.solver.core.preview.api.domain.metamodel.VariableMetaModel;
127
import ai.timefold.solver.core.preview.api.move.MutableSolutionView;
138
import ai.timefold.solver.core.preview.api.move.Rebaser;
14-
159
import org.jspecify.annotations.NonNull;
1610

11+
import java.util.Collection;
12+
import java.util.Collections;
13+
import java.util.List;
14+
import java.util.Objects;
15+
1716
/**
1817
* Moves an element of a {@link PlanningListVariable list variable}. The moved element is identified
1918
* by an entity instance and a position in that entity's list variable. The element is inserted at the given index
@@ -121,7 +120,7 @@ private Value_ getMovedValue() {
121120
@Override
122121
public void execute(@NonNull MutableSolutionView<Solution_> solutionView) {
123122
if (sourceEntity == destinationEntity) {
124-
planningValue = solutionView.moveValueInList(variableMetaModel, sourceEntity, sourceIndex, destinationIndex);
123+
planningValue = solutionView.swapValues(variableMetaModel, sourceEntity, sourceIndex, destinationIndex);
125124
} else {
126125
planningValue = solutionView.moveValueBetweenLists(variableMetaModel, sourceEntity, sourceIndex, destinationEntity,
127126
destinationIndex);

core/src/main/java/ai/timefold/solver/core/impl/move/streams/maybeapi/generic/provider/ListChangeMoveProvider.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import ai.timefold.solver.core.impl.move.streams.maybeapi.stream.MoveStreamFactory;
1111
import ai.timefold.solver.core.preview.api.domain.metamodel.ElementPosition;
1212
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel;
13+
import ai.timefold.solver.core.preview.api.domain.metamodel.PositionInList;
1314
import ai.timefold.solver.core.preview.api.domain.metamodel.UnassignedElement;
1415
import org.jspecify.annotations.NullMarked;
1516

@@ -35,7 +36,27 @@ public ListChangeMoveProvider(PlanningListVariableMetaModel<Solution_, Entity_,
3536
};
3637
this.noChangeDetectionFilter = (solutionView, value, targetPosition) -> {
3738
var currentPosition = solutionView.getPositionOf(variableMetaModel, Objects.requireNonNull(value));
38-
return !currentPosition.equals(targetPosition);
39+
if (currentPosition.equals(targetPosition)) { // The target position is the same as the current position.
40+
return false;
41+
}
42+
if (!(currentPosition instanceof PositionInList currentPositionInList)) {
43+
// The current position is unassigned, so we can assign the value.
44+
return true;
45+
}
46+
if (!(targetPosition instanceof PositionInList targetPositionInList)) {
47+
// The target position is unassigned, so we can unassign the value.
48+
return true;
49+
}
50+
if (currentPositionInList.entity() != targetPositionInList.entity()) {
51+
return true; // Different entities, so we can freely change the position.
52+
}
53+
var listLength = solutionView.countValues(variableMetaModel, currentPositionInList.entity());
54+
if (listLength == 1) {
55+
return false; // No need to change the position, as the value is the only one in this list.
56+
}
57+
// Make sure we're not moving the value past the list end.
58+
// This would happen if the value was already at the end of the list.
59+
return currentPositionInList.index() != listLength - 1 || targetPositionInList.index() != listLength;
3960
};
4061
}
4162

core/src/main/java/ai/timefold/solver/core/preview/api/move/MutableSolutionView.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ <Entity_, Value_> void changeVariable(PlanningVariableMetaModel<Solution_, Entit
125125
* @return the value that was moved; null if nothing was moved
126126
* @throws IndexOutOfBoundsException if the index is out of bounds
127127
*/
128-
<Entity_, Value_> @Nullable Value_ moveValueInList(
128+
<Entity_, Value_> @Nullable Value_ swapValues(
129129
PlanningListVariableMetaModel<Solution_, Entity_, Value_> variableMetaModel, Entity_ entity, int sourceIndex,
130130
int destinationIndex);
131131

core/src/test/java/ai/timefold/solver/core/impl/move/director/MoveDirectorTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ void readListVariable() {
120120
}
121121

122122
@Test
123-
void moveValueInList() {
123+
void swapValues() {
124124
var solutionMetaModel = TestdataListSolution.buildSolutionDescriptor()
125125
.getMetaModel();
126126
var variableMetaModel = solutionMetaModel.entity(TestdataListEntity.class)
@@ -137,7 +137,7 @@ void moveValueInList() {
137137
// Swap between second and last position.
138138
var mockScoreDirector = (InnerScoreDirector<TestdataListSolution, ?>) mock(InnerScoreDirector.class);
139139
var moveDirector = new MoveDirector<>(mockScoreDirector).ephemeral();
140-
moveDirector.moveValueInList(variableMetaModel, entity, 1, 2);
140+
moveDirector.swapValues(variableMetaModel, entity, 1, 2);
141141
assertThat(entity.getValueList()).containsExactly(expectedValue1, expectedValue3, expectedValue2);
142142
verify(mockScoreDirector).beforeListVariableChanged(variableDescriptor, entity, 1, 3);
143143
verify(mockScoreDirector).afterListVariableChanged(variableDescriptor, entity, 1, 3);
@@ -151,7 +151,7 @@ void moveValueInList() {
151151

152152
// Do the same in reverse.
153153
moveDirector = new MoveDirector<>(mockScoreDirector).ephemeral();
154-
moveDirector.moveValueInList(variableMetaModel, entity, 2, 1);
154+
moveDirector.swapValues(variableMetaModel, entity, 2, 1);
155155
assertThat(entity.getValueList()).containsExactly(expectedValue1, expectedValue3, expectedValue2);
156156
verify(mockScoreDirector).beforeListVariableChanged(variableDescriptor, entity, 1, 3);
157157
verify(mockScoreDirector).afterListVariableChanged(variableDescriptor, entity, 1, 3);

0 commit comments

Comments
 (0)