11package 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+
37import java .util .ArrayList ;
48import java .util .Collections ;
59import java .util .List ;
610import java .util .Objects ;
711import java .util .function .Consumer ;
812import 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.
@@ -58,13 +57,6 @@ public IndexedSet(ElementPositionTracker<T> elementPositionTracker) {
5857 this .elementPositionTracker = Objects .requireNonNull (elementPositionTracker );
5958 }
6059
61- private List <@ Nullable T > getElementList () {
62- if (elementList == null ) {
63- elementList = new ArrayList <>();
64- }
65- return elementList ;
66- }
67-
6860 /**
6961 * Appends the specified element to the end of this collection.
7062 * If the element is already present,
@@ -76,30 +68,26 @@ public IndexedSet(ElementPositionTracker<T> elementPositionTracker) {
7668 * @param element element to be appended to this collection
7769 */
7870 public void add (T element ) {
79- var actualElementList = getElementList ();
80- actualElementList .add (element );
71+ if (elementList == null ) {
72+ elementList = new ArrayList <>();
73+ }
74+ elementList .add (element );
8175 elementPositionTracker .setPosition (element , ++lastElementPosition );
8276 }
8377
8478 /**
85- * Removes the first occurrence of the specified element from this collection, if it is present.
79+ * Removes the first occurrence of the specified element from this collection, if present.
8680 * Will use identity comparison to check for presence;
8781 * two different instances which {@link Object#equals(Object) equal} are considered different elements.
8882 *
8983 * @param element element to be removed from this collection
90- * @throws IllegalStateException if the element was not found in this collection
84+ * @throws IllegalStateException if the element wasn't found in this collection
9185 */
9286 public void remove (T element ) {
93- if (!innerRemove (element )) {
94- throw new IllegalStateException ("Impossible state: the element (%s) was not found in the IndexedSet."
95- .formatted (element ));
96- }
97- }
98-
99- private boolean innerRemove (T element ) {
10087 var insertionPosition = elementPositionTracker .clearPosition (element );
10188 if (insertionPosition < 0 ) {
102- return false ;
89+ throw new IllegalStateException ("Impossible state: the element (%s) was not found in the IndexedSet."
90+ .formatted (element ));
10391 }
10492 if (insertionPosition == lastElementPosition ) {
10593 // The element was the last one added; we can simply remove it.
@@ -111,7 +99,6 @@ private boolean innerRemove(T element) {
11199 gapCount ++;
112100 }
113101 clearIfPossible ();
114- return true ;
115102 }
116103
117104 private boolean clearIfPossible () {
@@ -139,21 +126,18 @@ public int size() {
139126 *
140127 * @param elementConsumer the action to be performed for each element;
141128 * may include removing elements from the collection,
142- * but additions or swaps are not allowed;
129+ * but additions or swaps aren't allowed;
143130 * undefined behavior will occur if that is attempted.
144131 */
145132 public void forEach (Consumer <T > elementConsumer ) {
146133 if (isEmpty ()) {
147134 return ;
148135 }
149- forEach (element -> {
150- elementConsumer .accept (element );
151- return false ; // Iterate until the end.
152- });
153- }
154-
155- private @ Nullable T forEach (Predicate <T > elementPredicate ) {
156- return shouldCompact () ? forEachCompacting (elementPredicate ) : forEachNonCompacting (elementPredicate );
136+ if (shouldCompact ()) {
137+ forEachCompacting (elementConsumer );
138+ } else {
139+ forEachNonCompacting (elementConsumer );
140+ }
157141 }
158142
159143 private boolean shouldCompact () {
@@ -165,42 +149,39 @@ private boolean shouldCompact() {
165149 return gapPercentage > GAP_RATIO_FOR_COMPACTION ;
166150 }
167151
168- private @ Nullable T forEachNonCompacting (Predicate <T > elementPredicate ) {
169- return forEachNonCompacting (elementPredicate , 0 );
152+ private void forEachNonCompacting (Consumer <T > elementConsumer ) {
153+ forEachNonCompacting (elementConsumer , 0 );
170154 }
171155
172- private @ Nullable T forEachNonCompacting (Predicate <T > elementPredicate , int startingIndex ) {
156+ private void forEachNonCompacting (Consumer <T > elementConsumer , int startingIndex ) {
173157 for (var i = startingIndex ; i <= lastElementPosition ; i ++) {
174158 var element = elementList .get (i );
175- if (element != null && elementPredicate . test ( element ) ) {
176- return element ;
159+ if (element != null ) {
160+ elementConsumer . accept ( element ) ;
177161 }
178162 }
179- return null ;
180163 }
181164
182- private @ Nullable T forEachCompacting (Predicate <T > elementPredicate ) {
165+ private void forEachCompacting (Consumer <T > elementConsumer ) {
183166 if (clearIfPossible ()) {
184- return null ;
167+ return ;
185168 }
186169 for (var i = 0 ; i <= lastElementPosition ; i ++) {
187170 var element = elementList .get (i );
188171 if (element == null ) {
189172 element = fillGap (i );
190173 if (element == null ) { // There was nothing to fill the gap with; we are done.
191- return null ;
174+ return ;
192175 }
193176 }
194- if (elementPredicate .test (element )) {
195- return element ;
196- }
177+ elementConsumer .accept (element );
197178 if (gapCount == 0 ) {
198179 // No more gaps to fill; we can continue without compacting.
199180 // This is an optimized loop which no longer checks for gaps.
200- return forEachNonCompacting (elementPredicate , i + 1 );
181+ forEachNonCompacting (elementConsumer , i + 1 );
182+ return ;
201183 }
202184 }
203- return null ;
204185 }
205186
206187 /**
@@ -243,7 +224,45 @@ private boolean shouldCompact() {
243224 if (isEmpty ()) {
244225 return null ;
245226 }
246- return forEach (elementPredicate );
227+ return shouldCompact () ? findFirstCompacting (elementPredicate ) : findFirstNonCompacting (elementPredicate );
228+ }
229+
230+ private @ Nullable T findFirstNonCompacting (Predicate <T > elementPredicate ) {
231+ return findFirstNonCompacting (elementPredicate , 0 );
232+ }
233+
234+ private @ Nullable T findFirstNonCompacting (Predicate <T > elementPredicate , int startingIndex ) {
235+ for (var i = startingIndex ; i <= lastElementPosition ; i ++) {
236+ var element = elementList .get (i );
237+ if (element != null && elementPredicate .test (element )) {
238+ return element ;
239+ }
240+ }
241+ return null ;
242+ }
243+
244+ private @ Nullable T findFirstCompacting (Predicate <T > elementPredicate ) {
245+ if (clearIfPossible ()) {
246+ return null ;
247+ }
248+ for (var i = 0 ; i <= lastElementPosition ; i ++) {
249+ var element = elementList .get (i );
250+ if (element == null ) {
251+ element = fillGap (i );
252+ if (element == null ) { // There was nothing to fill the gap with; we are done.
253+ return null ;
254+ }
255+ }
256+ if (elementPredicate .test (element )) {
257+ return element ;
258+ }
259+ if (gapCount == 0 ) {
260+ // No more gaps to fill; we can continue without compacting.
261+ // This is an optimized loop which no longer checks for gaps.
262+ return findFirstNonCompacting (elementPredicate , i + 1 );
263+ }
264+ }
265+ return null ;
247266 }
248267
249268 /**
0 commit comments