Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
import ai.timefold.solver.core.api.domain.valuerange.CountableValueRange;
import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeFactory;
import ai.timefold.solver.core.impl.domain.valuerange.sort.SortableValueRange;
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;

import org.jspecify.annotations.NullMarked;

/**
* Abstract superclass for {@link CountableValueRange} (and therefore {@link ValueRange}).
Expand All @@ -13,7 +17,8 @@
* @see ValueRange
* @see ValueRangeFactory
*/
public abstract class AbstractCountableValueRange<T> implements CountableValueRange<T> {
@NullMarked
public abstract class AbstractCountableValueRange<T> implements CountableValueRange<T>, SortableValueRange<T> {

/**
* Certain optimizations can be applied if {@link Object#equals(Object)} can be relied upon
Expand All @@ -33,4 +38,10 @@ public boolean isEmpty() {
return getSize() == 0L;
}

@Override
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
// The sorting operation is not supported by default
// and must be explicitly implemented by the child classes if needed.
throw new UnsupportedOperationException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import ai.timefold.solver.core.api.domain.valuerange.CountableValueRange;
import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeFactory;
import ai.timefold.solver.core.impl.domain.valuerange.sort.SortableValueRange;
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;

/**
* Abstract superclass for {@link ValueRange} that is not a {@link CountableValueRange}).
Expand All @@ -14,6 +16,10 @@
* Use {@link CountableValueRange} instead, and configure a step.
*/
@Deprecated(forRemoval = true, since = "1.1.0")
public abstract class AbstractUncountableValueRange<T> implements ValueRange<T> {
public abstract class AbstractUncountableValueRange<T> implements ValueRange<T>, SortableValueRange<T> {

@Override
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
throw new UnsupportedOperationException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Random;
import java.util.Set;

import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.CachedListRandomIterator;
import ai.timefold.solver.core.impl.util.CollectionUtils;

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

private final List<Value_> valuesWithFastRandomAccess;
private final Set<Value_> valuesWithFastLookup;
private final boolean trustedValues;

private ValueRangeCache(int size, Set<Value_> emptyCacheSet) {
private ValueRangeCache(int size, Set<Value_> emptyCacheSet, boolean trustedValues) {
this.valuesWithFastRandomAccess = new ArrayList<>(size);
this.valuesWithFastLookup = emptyCacheSet;
this.trustedValues = trustedValues;
}

private ValueRangeCache(Collection<Value_> collection, Set<Value_> emptyCacheSet) {
private ValueRangeCache(Collection<Value_> collection, Set<Value_> emptyCacheSet, boolean trustedValues) {
this.valuesWithFastRandomAccess = new ArrayList<>(collection);
this.valuesWithFastLookup = emptyCacheSet;
this.valuesWithFastLookup.addAll(valuesWithFastRandomAccess);
this.trustedValues = trustedValues;
}

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

/**
* Creates a copy of the cache and apply a sorting operation.
*
* @param sorter never null, the sorter
*/
public ValueRangeCache<Value_> sort(ValueRangeSorter<Value_> sorter) {
var valuesWithFastRandomAccessSorted = sorter.sort(valuesWithFastRandomAccess);
if (trustedValues) {
return Builder.FOR_TRUSTED_VALUES.buildCache(valuesWithFastRandomAccessSorted);
} else {
return Builder.FOR_USER_VALUES.buildCache(valuesWithFastRandomAccessSorted);
}
}

public enum Builder {

/**
Expand All @@ -80,12 +98,12 @@ public enum Builder {
FOR_USER_VALUES {
@Override
public <Value_> ValueRangeCache<Value_> buildCache(int size) {
return new ValueRangeCache<>(size, CollectionUtils.newIdentityHashSet(size));
return new ValueRangeCache<>(size, CollectionUtils.newIdentityHashSet(size), false);
}

@Override
public <Value_> ValueRangeCache<Value_> buildCache(Collection<Value_> collection) {
return new ValueRangeCache<>(collection, CollectionUtils.newIdentityHashSet(collection.size()));
return new ValueRangeCache<>(collection, CollectionUtils.newIdentityHashSet(collection.size()), false);
}

},
Expand All @@ -100,12 +118,12 @@ public <Value_> ValueRangeCache<Value_> buildCache(Collection<Value_> collection
FOR_TRUSTED_VALUES {
@Override
public <Value_> ValueRangeCache<Value_> buildCache(int size) {
return new ValueRangeCache<>(size, CollectionUtils.newHashSet(size));
return new ValueRangeCache<>(size, CollectionUtils.newHashSet(size), true);
}

@Override
public <Value_> ValueRangeCache<Value_> buildCache(Collection<Value_> collection) {
return new ValueRangeCache<>(collection, CollectionUtils.newHashSet(collection.size()));
return new ValueRangeCache<>(collection, CollectionUtils.newHashSet(collection.size()), true);
}

};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import java.util.NoSuchElementException;
import java.util.Random;

import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
import ai.timefold.solver.core.impl.domain.valuerange.AbstractCountableValueRange;
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
Expand Down Expand Up @@ -43,6 +45,12 @@ public long getSize() {
return (Iterator<T>) EmptyIterator.INSTANCE;
}

@Override
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
// Sorting operation ignored
return this;
}

@Override
public boolean contains(@Nullable T value) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import java.util.List;
import java.util.Random;

import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
import ai.timefold.solver.core.impl.domain.valuerange.AbstractCountableValueRange;
import ai.timefold.solver.core.impl.domain.valuerange.ValueRangeCache;
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.CachedListRandomIterator;

import org.jspecify.annotations.NullMarked;
Expand Down Expand Up @@ -55,6 +57,12 @@ public boolean contains(@Nullable T value) {
return cache.contains(value);
}

@Override
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
var sortedList = sorter.sort(list);
return new ListValueRange<>(sortedList, isValueImmutable);
}

@Override
public Iterator<T> createOriginalIterator() {
return list.iterator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import java.util.Set;
import java.util.stream.Collectors;

import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
import ai.timefold.solver.core.impl.domain.valuerange.AbstractCountableValueRange;
import ai.timefold.solver.core.impl.domain.valuerange.ValueRangeCache;
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;

import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
Expand Down Expand Up @@ -59,6 +61,12 @@ public boolean contains(@Nullable T value) {
return set.contains(value);
}

@Override
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
var sortedSet = sorter.sort(set);
return new SetValueRange<>(sortedSet);
}

@Override
public Iterator<T> createOriginalIterator() {
return set.iterator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import java.util.List;
import java.util.Random;

import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
import ai.timefold.solver.core.impl.domain.valuerange.AbstractCountableValueRange;
import ai.timefold.solver.core.impl.domain.valuerange.ValueRangeCache;
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;

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

public CompositeCountableValueRange(List<? extends AbstractCountableValueRange<T>> childValueRangeList) {
var maximumSize = 0L;
var isValueImmutable = true;
var isImmutable = true;
for (AbstractCountableValueRange<T> childValueRange : childValueRangeList) {
isValueImmutable &= childValueRange.isValueImmutable();
isImmutable &= childValueRange.isValueImmutable();
maximumSize += childValueRange.getSize();
}
// To eliminate duplicates, we immediately expand the child value ranges into a cache.
var cacheBuilder = isValueImmutable ? ValueRangeCache.Builder.FOR_TRUSTED_VALUES
var cacheBuilder = isImmutable ? ValueRangeCache.Builder.FOR_TRUSTED_VALUES
: ValueRangeCache.Builder.FOR_USER_VALUES;
this.cache = cacheBuilder.buildCache((int) maximumSize);
for (var childValueRange : childValueRangeList) {
Expand All @@ -35,6 +37,11 @@ public CompositeCountableValueRange(List<? extends AbstractCountableValueRange<T
}
childValueRange.createOriginalIterator().forEachRemaining(cache::add);
}
this.isValueImmutable = isImmutable;
}

private CompositeCountableValueRange(ValueRangeCache<T> cache, boolean isValueImmutable) {
this.cache = cache;
this.isValueImmutable = isValueImmutable;
}

Expand All @@ -53,6 +60,12 @@ public T get(long index) {
return cache.get((int) index);
}

@Override
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
var sortedCache = this.cache.sort(sorter);
return new CompositeCountableValueRange<>(sortedCache, isValueImmutable);
}

@Override
public boolean contains(@Nullable T value) {
return cache.contains(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import java.util.Random;

import ai.timefold.solver.core.api.domain.valuerange.CountableValueRange;
import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
import ai.timefold.solver.core.impl.domain.valuerange.AbstractCountableValueRange;
import ai.timefold.solver.core.impl.domain.valuerange.sort.ValueRangeSorter;
import ai.timefold.solver.core.impl.domain.valuerange.util.ValueRangeIterator;
import ai.timefold.solver.core.impl.solver.random.RandomUtils;

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

@Override
public boolean isValueImmutable() {
return super.isValueImmutable();
}

AbstractCountableValueRange<T> getChildValueRange() {
return childValueRange;
}
Expand All @@ -56,6 +53,11 @@ public boolean contains(T value) {
return childValueRange.contains(value);
}

@Override
public ValueRange<T> sort(ValueRangeSorter<T> sorter) {
return childValueRange.sort(sorter);
}

@Override
public @NonNull Iterator<T> createOriginalIterator() {
return new OriginalNullValueRangeIterator(childValueRange.createOriginalIterator());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package ai.timefold.solver.core.impl.domain.valuerange.sort;

import java.util.List;
import java.util.Set;
import java.util.SortedSet;

import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter;

import org.jspecify.annotations.NullMarked;

@NullMarked
public record SelectionSorterAdapter<Solution_, T>(Solution_ solution,
SelectionSorter<Solution_, T> innerSelectionSorter) implements ValueRangeSorter<T> {

public static <Solution_, T> ValueRangeSorter<T> of(Solution_ solution, SelectionSorter<Solution_, T> selectionSorter) {
return new SelectionSorterAdapter<>(solution, selectionSorter);
}

@Override
public List<T> sort(List<T> selectionList) {
return innerSelectionSorter.sort(solution, selectionList);
}

@Override
public SortedSet<T> sort(Set<T> selectionSet) {
return innerSelectionSorter.sort(solution, selectionSet);
}

@Override
public SelectionSorter<?, T> getInnerSorter() {
return innerSelectionSorter;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ai.timefold.solver.core.impl.domain.valuerange.sort;

import ai.timefold.solver.core.api.domain.valuerange.ValueRange;

import org.jspecify.annotations.NullMarked;

@NullMarked
@FunctionalInterface
public interface SortableValueRange<T> {

/**
* The sorting operation copies the current value range and sorts it using the provided sorter.
*
* @param sorter never null, the value range sorter
* @return A new instance of the value range, with the data sorted.
*/
ValueRange<T> sort(ValueRangeSorter<T> sorter);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ai.timefold.solver.core.impl.domain.valuerange.sort;

import java.util.List;
import java.util.Set;
import java.util.SortedSet;

import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter;

import org.jspecify.annotations.NullMarked;

/**
* Basic contract for sorting a range of elements.
*
* @param <T> the value range type
*/
@NullMarked
public interface ValueRangeSorter<T> {

/**
* Creates a copy of the provided list and sort the data.
*
* @param selectionList never null, a {@link List} of values that will be used as input for sorting.
*/
List<T> sort(List<T> selectionList);

/**
* Creates a copy of the provided set and sort the data.
*
* @param selectionSet never null, a {@link Set} of values that will be used as input for sorting.
*/
SortedSet<T> sort(Set<T> selectionSet);

/**
* @return the inner sorter class that will be used to sort the data.
*/
<S> SelectionSorter<S, T> getInnerSorter();
}
Loading
Loading