1111import ai .timefold .solver .core .impl .heuristic .selector .common .iterator .UpcomingSelectionIterator ;
1212import ai .timefold .solver .core .impl .heuristic .selector .value .EntityIndependentValueSelector ;
1313
14+ import org .jspecify .annotations .NullMarked ;
15+ import org .jspecify .annotations .Nullable ;
16+
17+ @ NullMarked
1418final class KOptListMoveIterator <Solution_ , Node_ > extends UpcomingSelectionIterator <Move <Solution_ >> {
1519
1620 private final Random workingRandom ;
@@ -96,7 +100,7 @@ private Iterator<Node_> getValuesOnSelectedEntitiesIterator(Node_[] pickedValues
96100 }
97101
98102 @ SuppressWarnings ("unchecked" )
99- private KOptDescriptor <Node_ > pickKOptMove (int k ) {
103+ private @ Nullable KOptDescriptor <Node_ > pickKOptMove (int k ) {
100104 // The code in the paper used 1-index arrays
101105 var pickedValues = (Node_ []) new Object [2 * k + 1 ];
102106 var originIterator = (Iterator <Node_ >) originSelector .iterator ();
@@ -135,18 +139,17 @@ private KOptDescriptor<Node_> pickKOptMove(int k) {
135139 }
136140 }
137141
138- private KOptDescriptor <Node_ > pickKOptMoveRec (Iterator <Node_ > valueIterator ,
139- EntityOrderInfo entityOrderInfo ,
140- Node_ [] pickedValues ,
141- int pickedSoFar ,
142- int k ,
143- boolean canSelectNewEntities ) {
142+ private @ Nullable KOptDescriptor <Node_ > pickKOptMoveRec (Iterator <Node_ > valueIterator , EntityOrderInfo entityOrderInfo ,
143+ Node_ [] pickedValues , int pickedSoFar , int k , boolean canSelectNewEntities ) {
144144 var previousRemovedEdgeEndpoint = pickedValues [2 * pickedSoFar - 2 ];
145145 Node_ nextRemovedEdgePoint , nextRemovedEdgeOppositePoint ;
146146
147147 var remainingAttempts = (k - pickedSoFar + 3 ) * 2 ;
148148 while (remainingAttempts > 0 ) {
149- nextRemovedEdgePoint = valueIterator .next ();
149+ nextRemovedEdgePoint = getNextNodeOrNull (valueIterator );
150+ if (nextRemovedEdgePoint == null ) {
151+ return null ;
152+ }
150153 var newEntityOrderInfo =
151154 entityOrderInfo .withNewNode (nextRemovedEdgePoint , listVariableStateSupply );
152155 while (nextRemovedEdgePoint == getNodePredecessor (newEntityOrderInfo , previousRemovedEdgeEndpoint ) ||
@@ -161,7 +164,10 @@ && isEdgeAlreadyDeleted(pickedValues, nextRemovedEdgePoint,
161164 if (remainingAttempts == 0 ) {
162165 return null ;
163166 }
164- nextRemovedEdgePoint = valueIterator .next ();
167+ nextRemovedEdgePoint = getNextNodeOrNull (valueIterator );
168+ if (nextRemovedEdgePoint == null ) {
169+ return null ;
170+ }
165171 newEntityOrderInfo =
166172 entityOrderInfo .withNewNode (nextRemovedEdgePoint , listVariableStateSupply );
167173 remainingAttempts --;
@@ -191,24 +197,19 @@ && isEdgeAlreadyDeleted(pickedValues, nextRemovedEdgePoint,
191197 }
192198
193199 if (pickedSoFar < k ) {
194- var descriptor = pickKOptMoveRec (valueIterator , newEntityOrderInfo , pickedValues ,
195- pickedSoFar + 1 , k , canSelectNewEntities );
200+ var descriptor = pickKOptMoveRec (valueIterator , newEntityOrderInfo , pickedValues , pickedSoFar + 1 , k ,
201+ canSelectNewEntities );
196202 if (descriptor != null && descriptor .isFeasible (minK , maxCyclesPatchedInInfeasibleMove )) {
197203 return descriptor ;
198204 }
199205 } else {
200- var descriptor = new KOptDescriptor <Node_ >(pickedValues ,
201- KOptUtils .getMultiEntitySuccessorFunction (pickedValues ,
202- listVariableStateSupply ),
203- KOptUtils .getMultiEntityBetweenPredicate (pickedValues ,
204- listVariableStateSupply ));
206+ var descriptor = new KOptDescriptor <>(pickedValues ,
207+ KOptUtils .getMultiEntitySuccessorFunction (pickedValues , listVariableStateSupply ),
208+ KOptUtils .getMultiEntityBetweenPredicate (pickedValues , listVariableStateSupply ));
205209 if (descriptor .isFeasible (minK , maxCyclesPatchedInInfeasibleMove )) {
206210 return descriptor ;
207211 } else {
208- descriptor = patchCycles (
209- descriptor ,
210- newEntityOrderInfo , pickedValues ,
211- pickedSoFar );
212+ descriptor = patchCycles (descriptor , newEntityOrderInfo , pickedValues , pickedSoFar );
212213 if (descriptor .isFeasible (minK , maxCyclesPatchedInInfeasibleMove )) {
213214 return descriptor ;
214215 }
@@ -218,6 +219,16 @@ && isEdgeAlreadyDeleted(pickedValues, nextRemovedEdgePoint,
218219 return null ;
219220 }
220221
222+ private @ Nullable Node_ getNextNodeOrNull (Iterator <Node_ > iterator ) {
223+ if (!iterator .hasNext ()) {
224+ return null ;
225+ }
226+ // This may still be null.
227+ // Either due to filtering the underlying iterator,
228+ // or due to the underlying iterator returning null.
229+ return iterator .next ();
230+ }
231+
221232 KOptDescriptor <Node_ > patchCycles (KOptDescriptor <Node_ > descriptor , EntityOrderInfo entityOrderInfo ,
222233 Node_ [] oldRemovedEdges , int k ) {
223234 Node_ s1 , s2 ;
0 commit comments