Skip to content

Commit 16d52c7

Browse files
committed
feat: enable sorting on ValueRangeManager
1 parent a64b6c6 commit 16d52c7

File tree

16 files changed

+280
-33
lines changed

16 files changed

+280
-33
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import ai.timefold.solver.core.api.domain.variable.PlanningListVariable;
1010
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
11+
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;
1112

1213
import org.jspecify.annotations.NullMarked;
1314
import org.jspecify.annotations.Nullable;
@@ -45,6 +46,14 @@ public interface ValueRange<T> {
4546
*/
4647
boolean contains(@Nullable T value);
4748

49+
/**
50+
* The sorting operation copies the current value range and sorts it using the provided sorter.
51+
*
52+
* @param sorter never null, the value range sorter
53+
* @return A new instance of the value range, with the data sorted.
54+
*/
55+
ValueRange<T> sort(ValueRangeSorter<T> sorter);
56+
4857
/**
4958
* Select in random order, but without shuffling the elements.
5059
* Each element might be selected multiple times.

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import ai.timefold.solver.core.api.domain.valuerange.CountableValueRange;
66
import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
77
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeFactory;
8+
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;
9+
10+
import org.jspecify.annotations.NullMarked;
811

912
/**
1013
* Abstract superclass for {@link CountableValueRange} (and therefore {@link ValueRange}).
@@ -13,6 +16,7 @@
1316
* @see ValueRange
1417
* @see ValueRangeFactory
1518
*/
19+
@NullMarked
1620
public abstract class AbstractCountableValueRange<T> implements CountableValueRange<T> {
1721

1822
/**
@@ -33,4 +37,10 @@ public boolean isEmpty() {
3337
return getSize() == 0L;
3438
}
3539

40+
@Override
41+
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
42+
// The sorting operation is not supported by default
43+
// and must be explicitly implemented by the child classes if needed.
44+
throw new UnsupportedOperationException();
45+
}
3646
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import ai.timefold.solver.core.api.domain.valuerange.CountableValueRange;
44
import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
55
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeFactory;
6+
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;
67

78
/**
89
* Abstract superclass for {@link ValueRange} that is not a {@link CountableValueRange}).
@@ -16,4 +17,8 @@
1617
@Deprecated(forRemoval = true, since = "1.1.0")
1718
public abstract class AbstractUncountableValueRange<T> implements ValueRange<T> {
1819

20+
@Override
21+
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
22+
throw new UnsupportedOperationException();
23+
}
1924
}

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

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.Random;
99
import java.util.Set;
1010

11+
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;
1112
import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.CachedListRandomIterator;
1213
import ai.timefold.solver.core.impl.util.CollectionUtils;
1314

@@ -25,16 +26,19 @@ public final class ValueRangeCache<Value_>
2526

2627
private final List<Value_> valuesWithFastRandomAccess;
2728
private final Set<Value_> valuesWithFastLookup;
29+
private final CacheType cacheType;
2830

29-
private ValueRangeCache(int size, Set<Value_> emptyCacheSet) {
31+
private ValueRangeCache(int size, Set<Value_> emptyCacheSet, CacheType cacheType) {
3032
this.valuesWithFastRandomAccess = new ArrayList<>(size);
3133
this.valuesWithFastLookup = emptyCacheSet;
34+
this.cacheType = cacheType;
3235
}
3336

34-
private ValueRangeCache(Collection<Value_> collection, Set<Value_> emptyCacheSet) {
37+
private ValueRangeCache(Collection<Value_> collection, Set<Value_> emptyCacheSet, CacheType cacheType) {
3538
this.valuesWithFastRandomAccess = new ArrayList<>(collection);
3639
this.valuesWithFastLookup = emptyCacheSet;
3740
this.valuesWithFastLookup.addAll(valuesWithFastRandomAccess);
41+
this.cacheType = cacheType;
3842
}
3943

4044
public void add(@Nullable Value_ value) {
@@ -72,6 +76,20 @@ public Iterator<Value_> iterator(Random workingRandom) {
7276
return new CachedListRandomIterator<>(valuesWithFastRandomAccess, workingRandom);
7377
}
7478

79+
/**
80+
* Creates a copy of the cache and apply a sorting operation.
81+
*
82+
* @param sorter never null, the sorter
83+
*/
84+
public ValueRangeCache<Value_> sort(ValueRangeSorter<Value_> sorter) {
85+
var valuesWithFastRandomAccessSorted = new ArrayList<>(valuesWithFastRandomAccess);
86+
sorter.sort(valuesWithFastRandomAccessSorted);
87+
return switch (cacheType) {
88+
case USER_VALUES -> Builder.FOR_USER_VALUES.buildCache(valuesWithFastRandomAccessSorted);
89+
case TRUSTED_VALUES -> Builder.FOR_TRUSTED_VALUES.buildCache(valuesWithFastRandomAccessSorted);
90+
};
91+
}
92+
7593
public enum Builder {
7694

7795
/**
@@ -80,12 +98,13 @@ public enum Builder {
8098
FOR_USER_VALUES {
8199
@Override
82100
public <Value_> ValueRangeCache<Value_> buildCache(int size) {
83-
return new ValueRangeCache<>(size, CollectionUtils.newIdentityHashSet(size));
101+
return new ValueRangeCache<>(size, CollectionUtils.newIdentityHashSet(size), CacheType.USER_VALUES);
84102
}
85103

86104
@Override
87105
public <Value_> ValueRangeCache<Value_> buildCache(Collection<Value_> collection) {
88-
return new ValueRangeCache<>(collection, CollectionUtils.newIdentityHashSet(collection.size()));
106+
return new ValueRangeCache<>(collection, CollectionUtils.newIdentityHashSet(collection.size()),
107+
CacheType.USER_VALUES);
89108
}
90109

91110
},
@@ -100,12 +119,13 @@ public <Value_> ValueRangeCache<Value_> buildCache(Collection<Value_> collection
100119
FOR_TRUSTED_VALUES {
101120
@Override
102121
public <Value_> ValueRangeCache<Value_> buildCache(int size) {
103-
return new ValueRangeCache<>(size, CollectionUtils.newHashSet(size));
122+
return new ValueRangeCache<>(size, CollectionUtils.newHashSet(size), CacheType.TRUSTED_VALUES);
104123
}
105124

106125
@Override
107126
public <Value_> ValueRangeCache<Value_> buildCache(Collection<Value_> collection) {
108-
return new ValueRangeCache<>(collection, CollectionUtils.newHashSet(collection.size()));
127+
return new ValueRangeCache<>(collection, CollectionUtils.newHashSet(collection.size()),
128+
CacheType.TRUSTED_VALUES);
109129
}
110130

111131
};
@@ -116,4 +136,9 @@ public <Value_> ValueRangeCache<Value_> buildCache(Collection<Value_> collection
116136

117137
}
118138

139+
private enum CacheType {
140+
USER_VALUES,
141+
TRUSTED_VALUES
142+
}
143+
119144
}

core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/buildin/EmptyValueRange.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import java.util.NoSuchElementException;
55
import java.util.Random;
66

7+
import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
78
import ai.timefold.solver.core.impl.domain.valuerange.AbstractCountableValueRange;
9+
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;
810

911
import org.jspecify.annotations.NonNull;
1012
import org.jspecify.annotations.NullMarked;
@@ -43,6 +45,12 @@ public long getSize() {
4345
return (Iterator<T>) EmptyIterator.INSTANCE;
4446
}
4547

48+
@Override
49+
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
50+
// Sorting operation ignored
51+
return this;
52+
}
53+
4654
@Override
4755
public boolean contains(@Nullable T value) {
4856
return false;

core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/buildin/collection/ListValueRange.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package ai.timefold.solver.core.impl.domain.valuerange.buildin.collection;
22

3+
import java.util.ArrayList;
34
import java.util.Iterator;
45
import java.util.List;
56
import java.util.Random;
67

8+
import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
79
import ai.timefold.solver.core.impl.domain.valuerange.AbstractCountableValueRange;
810
import ai.timefold.solver.core.impl.domain.valuerange.ValueRangeCache;
11+
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;
912
import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.CachedListRandomIterator;
1013

1114
import org.jspecify.annotations.NullMarked;
@@ -55,6 +58,13 @@ public boolean contains(@Nullable T value) {
5558
return cache.contains(value);
5659
}
5760

61+
@Override
62+
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
63+
var newList = new ArrayList<>(list);
64+
sorter.sort(newList);
65+
return new ListValueRange<>(newList, isValueImmutable);
66+
}
67+
5868
@Override
5969
public Iterator<T> createOriginalIterator() {
6070
return list.iterator();

core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/buildin/collection/SetValueRange.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
import java.util.Set;
66
import java.util.stream.Collectors;
77

8+
import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
89
import ai.timefold.solver.core.impl.domain.valuerange.AbstractCountableValueRange;
910
import ai.timefold.solver.core.impl.domain.valuerange.ValueRangeCache;
11+
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;
1012

1113
import org.jspecify.annotations.NullMarked;
1214
import org.jspecify.annotations.Nullable;
@@ -59,6 +61,12 @@ public boolean contains(@Nullable T value) {
5961
return set.contains(value);
6062
}
6163

64+
@Override
65+
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
66+
var sortedSet = sorter.sort(set);
67+
return new SetValueRange<>(sortedSet);
68+
}
69+
6270
@Override
6371
public Iterator<T> createOriginalIterator() {
6472
return set.iterator();

core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/buildin/composite/CompositeCountableValueRange.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
import java.util.List;
55
import java.util.Random;
66

7+
import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
78
import ai.timefold.solver.core.impl.domain.valuerange.AbstractCountableValueRange;
89
import ai.timefold.solver.core.impl.domain.valuerange.ValueRangeCache;
10+
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;
911

1012
import org.jspecify.annotations.NullMarked;
1113
import org.jspecify.annotations.Nullable;
@@ -18,13 +20,13 @@ public final class CompositeCountableValueRange<T> extends AbstractCountableValu
1820

1921
public CompositeCountableValueRange(List<? extends AbstractCountableValueRange<T>> childValueRangeList) {
2022
var maximumSize = 0L;
21-
var isValueImmutable = true;
23+
var isImmutable = true;
2224
for (AbstractCountableValueRange<T> childValueRange : childValueRangeList) {
23-
isValueImmutable &= childValueRange.isValueImmutable();
25+
isImmutable &= childValueRange.isValueImmutable();
2426
maximumSize += childValueRange.getSize();
2527
}
2628
// To eliminate duplicates, we immediately expand the child value ranges into a cache.
27-
var cacheBuilder = isValueImmutable ? ValueRangeCache.Builder.FOR_TRUSTED_VALUES
29+
var cacheBuilder = isImmutable ? ValueRangeCache.Builder.FOR_TRUSTED_VALUES
2830
: ValueRangeCache.Builder.FOR_USER_VALUES;
2931
this.cache = cacheBuilder.buildCache((int) maximumSize);
3032
for (var childValueRange : childValueRangeList) {
@@ -35,7 +37,12 @@ public CompositeCountableValueRange(List<? extends AbstractCountableValueRange<T
3537
}
3638
childValueRange.createOriginalIterator().forEachRemaining(cache::add);
3739
}
40+
this.isValueImmutable = isImmutable;
41+
}
42+
43+
private CompositeCountableValueRange(ValueRangeCache<T> cache, boolean isValueImmutable) {
3844
this.isValueImmutable = isValueImmutable;
45+
this.cache = cache;
3946
}
4047

4148
@Override
@@ -53,6 +60,12 @@ public T get(long index) {
5360
return cache.get((int) index);
5461
}
5562

63+
@Override
64+
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
65+
var sortedCache = this.cache.sort(sorter);
66+
return new CompositeCountableValueRange<>(sortedCache, isValueImmutable);
67+
}
68+
5669
@Override
5770
public boolean contains(@Nullable T value) {
5871
return cache.contains(value);

core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/buildin/composite/NullAllowingCountableValueRange.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import java.util.Random;
55

66
import ai.timefold.solver.core.api.domain.valuerange.CountableValueRange;
7+
import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
78
import ai.timefold.solver.core.impl.domain.valuerange.AbstractCountableValueRange;
9+
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;
810
import ai.timefold.solver.core.impl.domain.valuerange.util.ValueRangeIterator;
911
import ai.timefold.solver.core.impl.solver.random.RandomUtils;
1012

@@ -25,11 +27,6 @@ public NullAllowingCountableValueRange(CountableValueRange<T> childValueRange) {
2527
size = childValueRange.getSize() + 1L;
2628
}
2729

28-
@Override
29-
public boolean isValueImmutable() {
30-
return super.isValueImmutable();
31-
}
32-
3330
AbstractCountableValueRange<T> getChildValueRange() {
3431
return childValueRange;
3532
}
@@ -56,6 +53,11 @@ public boolean contains(T value) {
5653
return childValueRange.contains(value);
5754
}
5855

56+
@Override
57+
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
58+
return childValueRange.sort(sorter);
59+
}
60+
5961
@Override
6062
public @NonNull Iterator<T> createOriginalIterator() {
6163
return new OriginalNullValueRangeIterator(childValueRange.createOriginalIterator());
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package ai.timefold.solver.core.impl.domain.valuerange.sort;
2+
3+
import java.util.List;
4+
import java.util.Set;
5+
import java.util.SortedSet;
6+
7+
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSetSorter;
8+
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter;
9+
10+
import org.jspecify.annotations.NullMarked;
11+
12+
@NullMarked
13+
public record SelectionSorterAdapter<Solution_, T>(Solution_ solution,
14+
SelectionSorter<Solution_, T> selectionSorter) implements ValueRangeSorter<T> {
15+
16+
public static <Solution_, T> ValueRangeSorter<T> of(Solution_ solution, SelectionSorter<Solution_, T> selectionSorter) {
17+
return new SelectionSorterAdapter<>(solution, selectionSorter);
18+
}
19+
20+
@Override
21+
public void sort(List<T> selectionList) {
22+
selectionSorter.sort(solution, selectionList);
23+
}
24+
25+
@Override
26+
@SuppressWarnings({ "rawtypes", "unchecked" })
27+
public SortedSet<T> sort(Set<T> selectionSet) {
28+
if (!(selectionSorter instanceof SelectionSetSorter selectionSetSorter)) {
29+
throw new IllegalStateException(
30+
"Impossible state: the sorting operation cannot be performed because the sorter does not support sorting collection sets.");
31+
}
32+
return selectionSetSorter.sort(solution, selectionSet);
33+
}
34+
}

0 commit comments

Comments
 (0)