Skip to content

Commit 85242e0

Browse files
committed
WIP
1 parent 42711e4 commit 85242e0

8 files changed

Lines changed: 48 additions & 179 deletions

File tree

core/src/main/java/ai/timefold/solver/core/api/domain/valuerange/ValueRangeProvider.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@
2323
* A {@link Collection} is implicitly converted to a {@link ValueRange}.
2424
*
2525
* <p>
26+
* If two values in a value range are equal according to {@link Object#equals(Object)},
27+
* then they are considered the same value
28+
* and will only be present once in the value range,
29+
* regardless of how many times they are present originally.
30+
*
31+
* <p>
2632
* Value ranges are not allowed to change during solving.
2733
* This is especially important for value ranges defined on {@link PlanningEntity}-annotated classes;
2834
* these must never depend on any of that entity's variables, or on any other entity's variables.

core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/AbstractFromPropertyValueRangeDescriptor.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package ai.timefold.solver.core.impl.domain.valuerange.descriptor;
22

33
import java.lang.reflect.Array;
4-
import java.util.ArrayList;
4+
import java.util.Arrays;
55
import java.util.Collection;
6-
import java.util.LinkedList;
6+
import java.util.Collections;
77
import java.util.List;
88

99
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
@@ -12,7 +12,6 @@
1212
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
1313
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
1414
import ai.timefold.solver.core.config.util.ConfigUtils;
15-
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
1615
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
1716
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
1817
import ai.timefold.solver.core.impl.domain.valuerange.buildin.collection.ListValueRange;
@@ -105,8 +104,8 @@ protected <Value_> ValueRange<Value_> readValueRange(Object bean) {
105104
}
106105
ValueRange<Value_> valueRange;
107106
if (collectionWrapping || arrayWrapping) {
108-
List<Value_> list = collectionWrapping ? transformCollectionToList((Collection<Value_>) valueRangeObject)
109-
: ReflectionHelper.transformArrayToList(valueRangeObject);
107+
List<Value_> list = collectionWrapping ? transformCollectionToUniqueList((Collection<Value_>) valueRangeObject)
108+
: transformArrayToUniqueList(valueRangeObject);
110109
// Don't check the entire list for performance reasons, but do check common pitfalls
111110
if (!list.isEmpty() && (list.get(0) == null || list.get(list.size() - 1) == null)) {
112111
throw new IllegalStateException(
@@ -163,18 +162,27 @@ protected long readValueRangeSize(Object bean) {
163162
}
164163
}
165164

166-
private <T> List<T> transformCollectionToList(Collection<T> collection) {
167-
if (collection instanceof List<T> list) {
168-
if (collection instanceof LinkedList<T> linkedList) {
169-
// ValueRange.createRandomIterator(Random) and ValueRange.get(int) wouldn't be efficient.
170-
return new ArrayList<>(linkedList);
171-
} else {
172-
return list;
173-
}
174-
} else {
175-
// TODO If only ValueRange.createOriginalIterator() is used, cloning a Set to a List is a waste of time.
176-
return new ArrayList<>(collection);
165+
private <T> List<T> transformCollectionToUniqueList(Collection<T> collection) {
166+
if (collection.isEmpty()) {
167+
return Collections.emptyList();
168+
}
169+
return collection.stream()
170+
.distinct()
171+
.toList();
172+
}
173+
174+
@SuppressWarnings("unchecked")
175+
public static <Value_> List<Value_> transformArrayToUniqueList(Object arrayObject) {
176+
if (arrayObject == null) {
177+
return Collections.emptyList();
178+
}
179+
var array = (Value_[]) arrayObject;
180+
if (array.length == 0) {
181+
return Collections.emptyList();
177182
}
183+
return Arrays.stream(array)
184+
.distinct()
185+
.toList();
178186
}
179187

180188
}

core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/AbstractSolutionClonerTest.java

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@
5555
import ai.timefold.solver.core.testdomain.inheritance.solution.baseannotated.thirdparty.TestdataExtendedThirdPartyEntity;
5656
import ai.timefold.solver.core.testdomain.inheritance.solution.baseannotated.thirdparty.TestdataExtendedThirdPartySolution;
5757
import ai.timefold.solver.core.testdomain.inheritance.solution.baseannotated.thirdparty.TestdataThirdPartyEntityPojo;
58-
import ai.timefold.solver.core.testdomain.list.externalized.TestdataListEntityExternalized;
59-
import ai.timefold.solver.core.testdomain.list.externalized.TestdataListSolutionExternalized;
60-
import ai.timefold.solver.core.testdomain.list.externalized.TestdataListValueExternalized;
58+
import ai.timefold.solver.core.testdomain.list.TestdataListEntity;
59+
import ai.timefold.solver.core.testdomain.list.TestdataListSolution;
60+
import ai.timefold.solver.core.testdomain.list.TestdataListValue;
6161
import ai.timefold.solver.core.testdomain.reflect.accessmodifier.TestdataAccessModifierSolution;
6262
import ai.timefold.solver.core.testdomain.reflect.field.TestdataFieldAnnotatedEntity;
6363
import ai.timefold.solver.core.testdomain.reflect.field.TestdataFieldAnnotatedSolution;
@@ -118,19 +118,18 @@ void cloneSolution() {
118118

119119
@Test
120120
void cloneListVariableSolution() {
121-
var solutionDescriptor = SolutionDescriptor.buildSolutionDescriptor(
122-
TestdataListSolutionExternalized.class,
123-
TestdataListEntityExternalized.class);
121+
var solutionDescriptor =
122+
SolutionDescriptor.buildSolutionDescriptor(TestdataListSolution.class, TestdataListEntity.class);
124123

125124
var cloner = createSolutionCloner(solutionDescriptor);
126125

127-
var val1 = new TestdataListValueExternalized("1");
128-
var val2 = new TestdataListValueExternalized("2");
129-
var val3 = new TestdataListValueExternalized("3");
130-
var a = new TestdataListEntityExternalized("a", new ArrayList<>(List.of(val1, val3)));
131-
var b = new TestdataListEntityExternalized("b", new ArrayList<>(List.of(val2)));
126+
var val1 = new TestdataListValue("1");
127+
var val2 = new TestdataListValue("2");
128+
var val3 = new TestdataListValue("3");
129+
var a = new TestdataListEntity("a", new ArrayList<>(List.of(val1, val3)));
130+
var b = new TestdataListEntity("b", new ArrayList<>(List.of(val2)));
132131

133-
var original = new TestdataListSolutionExternalized();
132+
var original = new TestdataListSolution();
134133
var valueList = Arrays.asList(val1, val2, val3);
135134
original.setValueList(valueList);
136135
var originalEntityList = List.of(a, b);
@@ -140,7 +139,7 @@ void cloneListVariableSolution() {
140139
var clone = cloner.cloneSolution(original);
141140

142141
assertThat(clone).isNotSameAs(original);
143-
assertThat(clone.getValueList()).isSameAs(valueList);
142+
assertThat(clone.getValueList()).isEqualTo(valueList);
144143
assertThat(clone.getScore()).isEqualTo(original.getScore());
145144

146145
var cloneEntityList = clone.getEntityList();
@@ -410,8 +409,8 @@ private void assertEntityClone(TestdataThirdPartyEntityPojo originalEntity,
410409
assertCode(valueCode, cloneEntity.getValue());
411410
}
412411

413-
private void assertEntityListClone(TestdataListEntityExternalized originalEntity,
414-
TestdataListEntityExternalized cloneEntity, String entityCode, List<String> valueCodeList) {
412+
private void assertEntityListClone(TestdataListEntity originalEntity, TestdataListEntity cloneEntity, String entityCode,
413+
List<String> valueCodeList) {
415414
assertThat(cloneEntity).isNotSameAs(originalEntity);
416415
assertThat(cloneEntity.getValueList()).isNotSameAs(originalEntity.getValueList());
417416
assertCode(entityCode, cloneEntity);

core/src/test/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseTest.java

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@
2828
import ai.timefold.solver.core.testdomain.list.TestdataListEntity;
2929
import ai.timefold.solver.core.testdomain.list.TestdataListSolution;
3030
import ai.timefold.solver.core.testdomain.list.TestdataListValue;
31-
import ai.timefold.solver.core.testdomain.list.externalized.TestdataListEntityExternalized;
32-
import ai.timefold.solver.core.testdomain.list.externalized.TestdataListSolutionExternalized;
3331
import ai.timefold.solver.core.testdomain.pinned.TestdataPinnedEntity;
3432
import ai.timefold.solver.core.testdomain.pinned.TestdataPinnedSolution;
3533
import ai.timefold.solver.core.testdomain.pinned.unassignedvar.TestdataPinnedAllowsUnassignedEntity;
@@ -370,17 +368,6 @@ void solveMultiVarChainedVariable() {
370368
assertThat(solution).isNotNull();
371369
}
372370

373-
@Test
374-
void solveListVariableWithExternalizedInverseAndIndexSupplies() {
375-
var solverConfig = PlannerTestUtils.buildSolverConfig(
376-
TestdataListSolutionExternalized.class, TestdataListEntityExternalized.class);
377-
378-
var solution = TestdataListSolutionExternalized.generateUninitializedSolution(6, 2);
379-
380-
solution = PlannerTestUtils.solve(solverConfig, solution);
381-
assertThat(solution).isNotNull();
382-
}
383-
384371
@Test
385372
void failsFastWithUninitializedSolutionBasicVariable() {
386373
var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class);
@@ -392,17 +379,4 @@ void failsFastWithUninitializedSolutionBasicVariable() {
392379
.hasMessageContaining("uninitialized entities");
393380
}
394381

395-
@Test
396-
void failsFastWithUninitializedSolutionListVariable() {
397-
var solverConfig = PlannerTestUtils.buildSolverConfig(
398-
TestdataListSolutionExternalized.class, TestdataListEntityExternalized.class);
399-
solverConfig.withPhases(solverConfig.getPhaseConfigList().get(1)); // Remove construction heuristic.
400-
401-
var solution = TestdataListSolutionExternalized.generateUninitializedSolution(6, 2);
402-
403-
assertThatThrownBy(() -> PlannerTestUtils.solve(solverConfig, solution))
404-
.hasMessageContaining("planning list variable")
405-
.hasMessageContaining("unexpected unassigned values");
406-
}
407-
408382
}

core/src/test/java/ai/timefold/solver/core/testdomain/list/TestdataListEntity.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ public List<TestdataListValue> getValueList() {
5454
return valueList;
5555
}
5656

57+
public void setValueList(List<TestdataListValue> valueList) {
58+
this.valueList = valueList;
59+
}
60+
5761
public void addValue(TestdataListValue value) {
5862
addValueAt(valueList.size(), value);
5963
}

core/src/test/java/ai/timefold/solver/core/testdomain/list/externalized/TestdataListEntityExternalized.java

Lines changed: 0 additions & 36 deletions
This file was deleted.

core/src/test/java/ai/timefold/solver/core/testdomain/list/externalized/TestdataListSolutionExternalized.java

Lines changed: 0 additions & 61 deletions
This file was deleted.

core/src/test/java/ai/timefold/solver/core/testdomain/list/externalized/TestdataListValueExternalized.java

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)