44import java .util .Iterator ;
55import java .util .List ;
66import java .util .ListIterator ;
7+ import java .util .NoSuchElementException ;
78import java .util .Objects ;
89import java .util .Random ;
910import java .util .function .Supplier ;
1011
12+ import ai .timefold .solver .core .impl .constructionheuristic .placer .EntityPlacerFactory ;
13+ import ai .timefold .solver .core .impl .constructionheuristic .placer .QueuedValuePlacer ;
1114import ai .timefold .solver .core .impl .domain .entity .descriptor .EntityDescriptor ;
1215import ai .timefold .solver .core .impl .heuristic .selector .AbstractDemandEnabledSelector ;
1316import ai .timefold .solver .core .impl .heuristic .selector .common .ReachableValues ;
1417import ai .timefold .solver .core .impl .heuristic .selector .common .iterator .UpcomingSelectionIterator ;
1518import ai .timefold .solver .core .impl .heuristic .selector .entity .EntitySelector ;
19+ import ai .timefold .solver .core .impl .heuristic .selector .move .generic .list .ListChangeMoveSelectorFactory ;
1620import ai .timefold .solver .core .impl .heuristic .selector .value .IterableValueSelector ;
1721import ai .timefold .solver .core .impl .phase .scope .AbstractPhaseScope ;
1822import ai .timefold .solver .core .impl .solver .scope .SolverScope ;
1923
2024/**
2125 * The decorator returns a list of reachable entities for a specific value.
22- * It enables the creation of a filtering tier when using entity value selectors ,
26+ * It enables the creation of a filtering tier when using entity-provided value ranges ,
2327 * ensuring only valid and reachable entities are returned.
28+ * An entity is considered reachable to a value if its value range includes that value.
29+ * <p>
30+ * The decorator can only be applied to list variables.
31+ * <p>
32+ * <code>
2433 *
2534 * e1 = entity_range[v1, v2, v3]
35+ *
2636 * e2 = entity_range[v1, v4]
2737 *
2838 * v1 = [e1, e2]
39+ *
2940 * v2 = [e1]
41+ *
3042 * v3 = [e1]
43+ *
3144 * v4 = [e2]
3245 *
46+ * </code>
47+ * <p>
48+ * This node is currently used by the {@link QueuedValuePlacer} to build an initial solution.
49+ * To illustrate its usage, let’s assume how moves are generated.
50+ * First, a value is selected using a value selector.
51+ * Then,
52+ * a change move selector generates all possible moves for that value to the available entities
53+ * and selects the entity and position with the best score.
54+ * <p>
55+ * Considering the previous process and the current goal of this node,
56+ * we can observe that once a value is selected, only change moves to reachable entities will be generated.
57+ * This ensures that entities that do not accept the currently selected value will not produce any change moves.
58+ *
59+ * @see ListChangeMoveSelectorFactory
60+ * @see EntityPlacerFactory
61+ *
3362 * @param <Solution_> the solution type
3463 */
35- public final class FilteringEntityValueRangeSelector <Solution_ > extends AbstractDemandEnabledSelector <Solution_ >
64+ public final class FilteringEntityByValueSelector <Solution_ > extends AbstractDemandEnabledSelector <Solution_ >
3665 implements EntitySelector <Solution_ > {
3766
3867 private final IterableValueSelector <Solution_ > replayingValueSelector ;
@@ -43,7 +72,7 @@ public final class FilteringEntityValueRangeSelector<Solution_> extends Abstract
4372 private ReachableValues reachableValues ;
4473 private long entitiesSize ;
4574
46- public FilteringEntityValueRangeSelector (EntitySelector <Solution_ > childEntitySelector ,
75+ public FilteringEntityByValueSelector (EntitySelector <Solution_ > childEntitySelector ,
4776 IterableValueSelector <Solution_ > replayingValueSelector , boolean randomSelection ) {
4877 this .replayingValueSelector = replayingValueSelector ;
4978 this .childEntitySelector = childEntitySelector ;
@@ -65,7 +94,9 @@ public void phaseStarted(AbstractPhaseScope<Solution_> phaseScope) {
6594 super .phaseStarted (phaseScope );
6695 this .entitiesSize = childEntitySelector .getEntityDescriptor ().extractEntities (phaseScope .getWorkingSolution ()).size ();
6796 this .reachableValues = phaseScope .getScoreDirector ().getValueRangeManager ()
68- .getReachableValues (phaseScope .getScoreDirector ().getSolutionDescriptor ().getListVariableDescriptor ());
97+ .getReachableValues (Objects .requireNonNull (
98+ phaseScope .getScoreDirector ().getSolutionDescriptor ().getListVariableDescriptor (),
99+ "Impossible state: the list variable cannot be null." ));
69100 this .childEntitySelector .phaseStarted (phaseScope );
70101 }
71102
@@ -141,7 +172,7 @@ public ListIterator<Object> listIterator(int index) {
141172
142173 @ Override
143174 public boolean equals (Object other ) {
144- return other instanceof FilteringEntityValueRangeSelector <?> that
175+ return other instanceof FilteringEntityByValueSelector <?> that
145176 && Objects .equals (childEntitySelector , that .childEntitySelector )
146177 && Objects .equals (replayingValueSelector , that .replayingValueSelector );
147178 }
@@ -170,8 +201,7 @@ private void initialize() {
170201 if (currentUpcomingValue == null ) {
171202 valueIterator = Collections .emptyIterator ();
172203 } else {
173- var allValues = Objects .requireNonNull (reachableValues )
174- .extractEntitiesAsList (Objects .requireNonNull (currentUpcomingValue ));
204+ var allValues = reachableValues .extractEntitiesAsList (Objects .requireNonNull (currentUpcomingValue ));
175205 this .valueIterator = Objects .requireNonNull (allValues ).iterator ();
176206 }
177207 }
@@ -186,7 +216,7 @@ protected Object createUpcomingSelection() {
186216 }
187217 }
188218
189- private static class RandomFilteringValueRangeIterator extends UpcomingSelectionIterator <Object > {
219+ private static class RandomFilteringValueRangeIterator implements Iterator <Object > {
190220
191221 private final Supplier <Object > upcomingValueSupplier ;
192222 private final ReachableValues reachableValues ;
@@ -205,40 +235,29 @@ private void initialize() {
205235 if (entityList != null ) {
206236 return ;
207237 }
238+ var oldUpcomingValue = currentUpcomingValue ;
208239 currentUpcomingValue = upcomingValueSupplier .get ();
209240 if (currentUpcomingValue == null ) {
210241 entityList = Collections .emptyList ();
211- } else {
242+ } else if ( oldUpcomingValue != currentUpcomingValue ) {
212243 loadValues ();
213244 }
214245 }
215246
216247 private void loadValues () {
217- upcomingCreated = false ;
218248 this .entityList = reachableValues .extractEntitiesAsList (currentUpcomingValue );
219249 }
220250
221251 @ Override
222252 public boolean hasNext () {
223- if (currentUpcomingValue != null ) {
224- var updatedUpcomingValue = upcomingValueSupplier .get ();
225- if (updatedUpcomingValue != currentUpcomingValue ) {
226- // The iterator is reused in the ElementPositionRandomIterator,
227- // even if the value has changed.
228- // Therefore,
229- // we need to update the value list to ensure it is consistent.
230- currentUpcomingValue = updatedUpcomingValue ;
231- loadValues ();
232- }
233- }
234- return super .hasNext ();
253+ initialize ();
254+ return entityList != null && !entityList .isEmpty ();
235255 }
236256
237257 @ Override
238- protected Object createUpcomingSelection () {
239- initialize ();
258+ public Object next () {
240259 if (entityList .isEmpty ()) {
241- return noUpcomingSelection ();
260+ throw new NoSuchElementException ();
242261 }
243262 var index = workingRandom .nextInt (entityList .size ());
244263 return entityList .get (index );
0 commit comments