Skip to content

Commit 6a8d0ee

Browse files
committed
Do the same for joins
1 parent 58b61ad commit 6a8d0ee

File tree

4 files changed

+75
-47
lines changed

4 files changed

+75
-47
lines changed

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractIndexedJoinNode.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public final void updateLeft(LeftTuple_ leftTuple) {
8484
} else {
8585
indexerLeft.remove(oldIndexKeys, leftTuple);
8686
IndexedSet<OutTuple_> outTupleSetLeft = leftTuple.getStore(inputStoreIndexLeftOutTupleSet);
87-
outTupleSetLeft.forEach(this::retractOutTuple);
87+
outTupleSetLeft.clear(this::retractOutTupleByLeft);
8888
// outTupleSetLeft is now empty, no need for leftTuple.setStore(...);
8989
indexAndPropagateLeft(leftTuple, newIndexKeys);
9090
}
@@ -105,7 +105,7 @@ public final void retractLeft(LeftTuple_ leftTuple) {
105105
}
106106
IndexedSet<OutTuple_> outTupleSetLeft = leftTuple.removeStore(inputStoreIndexLeftOutTupleSet);
107107
indexerLeft.remove(indexKeys, leftTuple);
108-
outTupleSetLeft.forEach(this::retractOutTuple);
108+
outTupleSetLeft.clear(this::retractOutTupleByLeft);
109109
}
110110

111111
@Override
@@ -137,7 +137,7 @@ public final void updateRight(UniTuple<Right_> rightTuple) {
137137
} else {
138138
IndexedSet<OutTuple_> outTupleSetRight = rightTuple.getStore(inputStoreIndexRightOutTupleSet);
139139
indexerRight.remove(oldIndexKeys, rightTuple);
140-
outTupleSetRight.forEach(this::retractOutTuple);
140+
outTupleSetRight.clear(this::retractOutTupleByRight);
141141
// outTupleSetRight is now empty, no need for rightTuple.setStore(...);
142142
indexAndPropagateRight(rightTuple, newIndexKeys);
143143
}
@@ -158,7 +158,7 @@ public final void retractRight(UniTuple<Right_> rightTuple) {
158158
}
159159
IndexedSet<OutTuple_> outTupleSetRight = rightTuple.removeStore(inputStoreIndexRightOutTupleSet);
160160
indexerRight.remove(indexKeys, rightTuple);
161-
outTupleSetRight.forEach(this::retractOutTuple);
161+
outTupleSetRight.clear(this::retractOutTupleByRight);
162162
}
163163

164164
}

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractJoinNode.java

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,22 @@ private void findAndProcessOutTuple(LeftTuple_ leftTuple, UniTuple<Right_> right
128128
}
129129
}
130130

131+
private void retractOutTuple(OutTuple_ outTuple) {
132+
removeFromLeftOutSet(outTuple);
133+
removeFromRightOutSet(outTuple);
134+
propagateRetract(outTuple);
135+
}
136+
137+
private void removeFromLeftOutSet(OutTuple_ outTuple) {
138+
IndexedSet<OutTuple_> outSetLeft = outTuple.removeStore(outputStoreIndexLeftOutSet);
139+
outSetLeft.remove(outTuple);
140+
}
141+
142+
private void removeFromRightOutSet(OutTuple_ outTuple) {
143+
IndexedSet<OutTuple_> outSetRight = outTuple.removeStore(outputStoreIndexRightOutSet);
144+
outSetRight.remove(outTuple);
145+
}
146+
131147
private static <OutTuple_ extends AbstractTuple> OutTuple_ findOutTuple(IndexedSet<OutTuple_> sourceSet,
132148
IndexedSet<OutTuple_> referenceSet, int outputStoreIndex) {
133149
// Hack: outTuple has no left/right input tuple reference, use the left/right outSet reference instead.
@@ -184,11 +200,13 @@ protected final void innerUpdateRight(UniTuple<Right_> rightTuple, Consumer<Cons
184200
}
185201
}
186202

187-
protected final void retractOutTuple(OutTuple_ outTuple) {
188-
IndexedSet<OutTuple_> outSetLeft = outTuple.removeStore(outputStoreIndexLeftOutSet);
189-
outSetLeft.remove(outTuple);
190-
IndexedSet<OutTuple_> outSetRight = outTuple.removeStore(outputStoreIndexRightOutSet);
191-
outSetRight.remove(outTuple);
203+
protected final void retractOutTupleByLeft(OutTuple_ outTuple) {
204+
outTuple.removeStore(outputStoreIndexLeftOutSet); // The tuple will be removed from the set by the caller.
205+
removeFromRightOutSet(outTuple);
206+
propagateRetract(outTuple);
207+
}
208+
209+
private void propagateRetract(OutTuple_ outTuple) {
192210
var state = outTuple.state;
193211
if (!state.isActive()) { // Impossible because they shouldn't linger in the indexes.
194212
throw new IllegalStateException("Impossible state: The tuple (%s) in node (%s) is in an unexpected state (%s)."
@@ -197,6 +215,12 @@ protected final void retractOutTuple(OutTuple_ outTuple) {
197215
propagationQueue.retract(outTuple, state == TupleState.CREATING ? TupleState.ABORTING : TupleState.DYING);
198216
}
199217

218+
protected final void retractOutTupleByRight(OutTuple_ outTuple) {
219+
removeFromLeftOutSet(outTuple);
220+
outTuple.removeStore(outputStoreIndexRightOutSet); // The tuple will be removed from the set by the caller.
221+
propagateRetract(outTuple);
222+
}
223+
200224
@Override
201225
public Propagator getPropagator() {
202226
return propagationQueue;

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractUnindexedJoinNode.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public final void retractLeft(LeftTuple_ leftTuple) {
7171
}
7272
IndexedSet<OutTuple_> outTupleSetLeft = leftTuple.removeStore(inputStoreIndexLeftOutTupleSet);
7373
leftTupleSet.remove(leftTuple);
74-
outTupleSetLeft.forEach(this::retractOutTuple);
74+
outTupleSetLeft.clear(this::retractOutTupleByLeft);
7575
}
7676

7777
@Override
@@ -105,7 +105,7 @@ public final void retractRight(UniTuple<Right_> rightTuple) {
105105
}
106106
IndexedSet<OutTuple_> outTupleSetRight = rightTuple.removeStore(inputStoreIndexRightOutTupleSet);
107107
rightTupleSet.remove(rightTuple);
108-
outTupleSetRight.forEach(this::retractOutTuple);
108+
outTupleSetRight.clear(this::retractOutTupleByRight);
109109
}
110110

111111
}

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/index/IndexedSet.java

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
package ai.timefold.solver.core.impl.bavet.common.index;
22

3+
import ai.timefold.solver.core.impl.util.ElementAwareList;
4+
import org.jspecify.annotations.NullMarked;
5+
import org.jspecify.annotations.Nullable;
6+
37
import java.util.ArrayList;
48
import java.util.Collections;
59
import java.util.List;
610
import java.util.Objects;
711
import java.util.function.Consumer;
812
import java.util.function.Predicate;
913

10-
import ai.timefold.solver.core.impl.util.ElementAwareList;
11-
12-
import org.jspecify.annotations.NullMarked;
13-
import org.jspecify.annotations.Nullable;
14-
1514
/**
1615
* {@link ArrayList}-backed set which allows to {@link #remove(Object)} an element
1716
* without knowing its position and without an expensive lookup.
@@ -132,9 +131,7 @@ public int size() {
132131
* The order of iteration may change as elements are added and removed.
133132
*
134133
* @param elementConsumer the action to be performed for each element;
135-
* may include removing elements from the collection,
136-
* but additions or swaps aren't allowed;
137-
* undefined behavior will occur if that is attempted.
134+
* mustn't modify the collection
138135
*/
139136
public void forEach(Consumer<T> elementConsumer) {
140137
if (isEmpty()) {
@@ -227,7 +224,7 @@ private void forEachCompacting(Consumer<T> elementConsumer) {
227224
* As defined by {@link #forEach(Consumer)},
228225
* but stops when the predicate returns true for an element.
229226
*
230-
* @param elementPredicate the predicate to be tested for each element
227+
* @param elementPredicate the predicate to be tested for each element; mustn't modify the collection
231228
* @return the first element for which the predicate returned true, or null if none
232229
*/
233230
public @Nullable T findFirst(Predicate<T> elementPredicate) {
@@ -275,6 +272,39 @@ private void forEachCompacting(Consumer<T> elementConsumer) {
275272
return null;
276273
}
277274

275+
/**
276+
* Empties the collection.
277+
* For each element being removed, the given consumer is called.
278+
* This is a more efficient implementation than removing elements individually.
279+
*
280+
* @param elementConsumer the consumer to be called for each element being removed; mustn't modify the collection
281+
*/
282+
public void clear(Consumer<T> elementConsumer) {
283+
var nonGapCount = size();
284+
if (nonGapCount == 0) {
285+
forceClear();
286+
return;
287+
}
288+
var oldLastElementPosition = lastElementPosition;
289+
for (var i = 0; i <= oldLastElementPosition; i++) {
290+
var element = elementList.get(i);
291+
if (element == null) {
292+
continue;
293+
}
294+
elementConsumer.accept(element);
295+
if (lastElementPosition != oldLastElementPosition) {
296+
throw new IllegalStateException("Impossible state: the IndexedSet was modified while being cleared.");
297+
}
298+
elementPositionTracker.clearPosition(element);
299+
// We can stop early once all non-gap elements have been processed.
300+
nonGapCount--;
301+
if (nonGapCount == 0) {
302+
break;
303+
}
304+
}
305+
forceClear();
306+
}
307+
278308
/**
279309
* Returns a standard {@link List} view of this collection.
280310
* Users mustn't modify the returned list, as that'd also change the underlying data structure.
@@ -285,7 +315,7 @@ public List<T> asList() {
285315
if (elementList == null) {
286316
return Collections.emptyList();
287317
}
288-
if (gapCount > 0) { // The list must not return any nulls.
318+
if (gapCount > 0) { // The list mustn't return any nulls.
289319
forceCompaction();
290320
}
291321
return elementList.isEmpty() ? Collections.emptyList() : elementList;
@@ -306,30 +336,4 @@ private void forceCompaction() {
306336
}
307337
}
308338

309-
public void clear(Consumer<T> elementConsumer) {
310-
var nonGapCount = size();
311-
if (nonGapCount == 0) {
312-
forceClear();
313-
return;
314-
}
315-
var oldLastElementPosition = lastElementPosition;
316-
for (var i = 0; i <= oldLastElementPosition; i++) {
317-
var element = elementList.get(i);
318-
if (element == null) {
319-
continue;
320-
}
321-
elementConsumer.accept(element);
322-
if (lastElementPosition != oldLastElementPosition) {
323-
throw new IllegalStateException("Impossible state: the IndexedSet was modified while being cleared.");
324-
}
325-
elementPositionTracker.clearPosition(element);
326-
// We can stop early once all non-gap elements have been processed.
327-
nonGapCount--;
328-
if (nonGapCount == 0) {
329-
break;
330-
}
331-
}
332-
forceClear();
333-
}
334-
335339
}

0 commit comments

Comments
 (0)