Skip to content

Commit 83d01f6

Browse files
committed
Separate path for forEach
1 parent 699cfb5 commit 83d01f6

File tree

1 file changed

+68
-49
lines changed
  • core/src/main/java/ai/timefold/solver/core/impl/bavet/common/index

1 file changed

+68
-49
lines changed

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

Lines changed: 68 additions & 49 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.
@@ -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

Comments
 (0)