Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,13 @@ public BiasedRandomUnionMoveIterator(List<MoveSelector<Solution_>> childMoveSele

@Override
public boolean hasNext() {
if (stale) {
refreshMoveIteratorMap();
}
refreshMoveIteratorMap();
return !moveIteratorMap.isEmpty();
}

@Override
public Move<Solution_> next() {
if (stale) {
refreshMoveIteratorMap();
}
refreshMoveIteratorMap();
double randomOffset = RandomUtils.nextDouble(workingRandom, probabilityWeightTotal);
Map.Entry<Double, Iterator<Move<Solution_>>> entry = moveIteratorMap.floorEntry(randomOffset);
// The entry is never null because randomOffset < probabilityWeightTotal
Expand All @@ -64,6 +60,9 @@ public Move<Solution_> next() {
}

private void refreshMoveIteratorMap() {
if (!stale) {
return;
}
moveIteratorMap.clear();
double probabilityWeightOffset = 0.0;
for (ProbabilityItem<Solution_> probabilityItem : probabilityItemMap.values()) {
Expand All @@ -74,6 +73,7 @@ private void refreshMoveIteratorMap() {
}
}
probabilityWeightTotal = probabilityWeightOffset;
stale = false;
}

private static final class ProbabilityItem<Solution_> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ public boolean equals(Object o) {

@Override
public int hashCode() {
return Objects.hash(startSplitPointToConnectedRange, splitPointSet, startSplitPointToNextGap);
// splitPointSet excluded on purpose: see the comment on ConnectedRangeImpl.equals().
return Objects.hash(startSplitPointToConnectedRange, startSplitPointToNextGap);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ final class ConnectedRangeImpl<Range_, Point_ extends Comparable<Point_>, Differ
private int minimumOverlap;
private int maximumOverlap;
private boolean hasOverlap;
private int cachedHashCode;
private boolean hashCodeDirty = true;

ConnectedRangeImpl(NavigableSet<RangeSplitPoint<Range_, Point_>> splitPointSet,
BiFunction<? super Point_, ? super Point_, ? extends Difference_> differenceFunction,
Expand Down Expand Up @@ -67,6 +69,7 @@ void addRange(Range<Range_, Point_> range) {
}
minimumOverlap = -1;
maximumOverlap = -1;
hashCodeDirty = true;
count++;
}

Expand All @@ -85,6 +88,7 @@ void mergeConnectedRange(ConnectedRangeImpl<Range_, Point_, Difference_> laterCo
count += laterConnectedRange.count;
minimumOverlap = -1;
maximumOverlap = -1;
hashCodeDirty = true;
hasOverlap |= laterConnectedRange.hasOverlap;
}

Expand Down Expand Up @@ -153,22 +157,38 @@ public int getMaximumOverlap() {
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof ConnectedRangeImpl<?, ?, ?> that))
return false;
return count == that.count &&
getMinimumOverlap() == that.getMinimumOverlap()
&& getMaximumOverlap() == that.getMaximumOverlap()
&& hasOverlap == that.hasOverlap && Objects.equals(
splitPointSet, that.splitPointSet)
&& Objects.equals(startSplitPoint,
that.startSplitPoint)
&& Objects.equals(endSplitPoint, that.endSplitPoint);
// splitPointSet is the tracker's full, problem-wide split-point set,
// shared by reference across every ConnectedRangeImpl built from it.
// It's compared here for correctness, but deliberately excluded from hashCode():
// equals() gets it for free because both sides are almost always the same reference
// (Objects.equals short-circuits on ==),
// whereas hashCode() has no such shortcut
// and would have to walk and hash every split point in the whole problem,
// not just this range's.
// The checks are ordered from cheapest to most expensive.
// Min/max overlap are excluded as they are derived from the split points.
return o instanceof ConnectedRangeImpl<?, ?, ?> that
&& hasOverlap == that.hasOverlap
&& count == that.count
&& Objects.equals(startSplitPoint, that.startSplitPoint)
&& Objects.equals(endSplitPoint, that.endSplitPoint)
&& Objects.equals(splitPointSet, that.splitPointSet);
}

@Override
public int hashCode() {
return Objects.hash(splitPointSet, startSplitPoint, endSplitPoint, count,
getMinimumOverlap(), getMaximumOverlap(), hasOverlap);
if (hashCodeDirty) {
// Hand-rolled instead of Objects.hash(...):
// that boxes every argument and allocates a varargs array per call,
// on what is already a hot path.
var result = Boolean.hashCode(hasOverlap);
result = 31 * result + count;
result = 31 * result + startSplitPoint.hashCode();
result = 31 * result + endSplitPoint.hashCode();
cachedHashCode = result;
hashCodeDirty = false;
}
return cachedHashCode;
}

@Override
Expand All @@ -177,8 +197,6 @@ public String toString() {
"start=" + startSplitPoint +
", end=" + endSplitPoint +
", count=" + count +
", minimumOverlap=" + getMinimumOverlap() +
", maximumOverlap=" + getMaximumOverlap() +
", hasOverlap=" + hasOverlap +
'}';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,23 @@
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

public class RangeSplitPoint<Range_, Point_ extends Comparable<Point_>>
public final class RangeSplitPoint<Range_, Point_ extends Comparable<Point_>>
implements Comparable<RangeSplitPoint<Range_, Point_>> {

// Reuse these instances in createCollections().
@SuppressWarnings("rawtypes")
private static final Comparator COMPARATOR_START = comparator(Range::getStart);
@SuppressWarnings("rawtypes")
private static final Comparator COMPARATOR_END = comparator(Range::getEnd);

private static <Range_, Point_ extends Comparable<Point_>> Comparator<Range<Range_, Point_>>
comparator(Function<Range<Range_, Point_>, Point_> function) {
return Comparator.comparing(function)
.thenComparingInt(range -> System.identityHashCode(range.getValue()));
}

final Point_ splitPoint;
Map<Range_, Integer> startpointRangeToCountMap;
Map<Range_, Integer> endpointRangeToCountMap;
Expand All @@ -18,15 +32,13 @@
this.splitPoint = splitPoint;
}

protected void createCollections() {

Check warning on line 35 in core/src/main/java/ai/timefold/solver/core/impl/score/stream/collector/connected_ranges/RangeSplitPoint.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this "protected" modifier.

See more on https://sonarcloud.io/project/issues?id=ai.timefold%3Atimefold-solver&issues=AZ9GR1zk5nYxoG5Gp-LC&open=AZ9GR1zk5nYxoG5Gp-LC&pullRequest=2473
startpointRangeToCountMap = new IdentityHashMap<>();
endpointRangeToCountMap = new IdentityHashMap<>();
rangesStartingAtSplitPointSet = new TreeMultiSet<>(
Comparator.<Range<Range_, Point_>, Point_> comparing(Range::getEnd)
.thenComparingInt(range -> System.identityHashCode(range.getValue())));
rangesEndingAtSplitPointSet = new TreeMultiSet<>(
Comparator.<Range<Range_, Point_>, Point_> comparing(Range::getStart)
.thenComparingInt(range -> System.identityHashCode(range.getValue())));
// Almost always holds a single entry (one range starting/ending at this exact point);
// avoid IdentityHashMap's default 64-slot table for what's typically a 1-entry map.
startpointRangeToCountMap = new IdentityHashMap<>(1);
endpointRangeToCountMap = new IdentityHashMap<>(1);
rangesStartingAtSplitPointSet = new TreeMultiSet<>(COMPARATOR_END);
rangesEndingAtSplitPointSet = new TreeMultiSet<>(COMPARATOR_START);
}

public boolean addRangeStartingAtSplitPoint(Range<Range_, Point_> range) {
Expand Down Expand Up @@ -72,9 +84,18 @@
}

public Iterator<Range_> getValuesStartingFromSplitPointIterator() {
return rangesStartingAtSplitPointSet.stream()
.map(Range::getValue)
.iterator();
var iterator = rangesStartingAtSplitPointSet.iterator();
return new Iterator<>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}

@Override
public Range_ next() {
return iterator.next().getValue();
}
};
}

public boolean isEmpty() {
Expand All @@ -83,12 +104,11 @@

@Override
public boolean equals(Object o) {
if (this == o)
if (this == o) {
return true;
if (o == null || getClass() != o.getClass())
return false;
RangeSplitPoint<?, ?> that = (RangeSplitPoint<?, ?>) o;
return splitPoint.equals(that.splitPoint);
}
return o instanceof RangeSplitPoint<?, ?> other
Comment thread
triceo marked this conversation as resolved.
&& splitPoint.equals(other.splitPoint);
}

public boolean isBefore(RangeSplitPoint<Range_, Point_> other) {
Expand All @@ -101,7 +121,7 @@

@Override
public int hashCode() {
return Objects.hash(splitPoint);
return Objects.hashCode(splitPoint);
}

@Override
Expand Down
Loading
Loading