Skip to content

Commit 058233b

Browse files
committed
Separate path for forEach
1 parent 699cfb5 commit 058233b

File tree

1 file changed

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

1 file changed

+69
-46
lines changed

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

Lines changed: 69 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,6 @@ public IndexedSet(ElementPositionTracker<T> elementPositionTracker) {
5858
this.elementPositionTracker = Objects.requireNonNull(elementPositionTracker);
5959
}
6060

61-
private List<@Nullable T> getElementList() {
62-
if (elementList == null) {
63-
elementList = new ArrayList<>();
64-
}
65-
return elementList;
66-
}
67-
6861
/**
6962
* Appends the specified element to the end of this collection.
7063
* If the element is already present,
@@ -76,30 +69,26 @@ public IndexedSet(ElementPositionTracker<T> elementPositionTracker) {
7669
* @param element element to be appended to this collection
7770
*/
7871
public void add(T element) {
79-
var actualElementList = getElementList();
80-
actualElementList.add(element);
72+
if (elementList == null) {
73+
elementList = new ArrayList<>();
74+
}
75+
elementList.add(element);
8176
elementPositionTracker.setPosition(element, ++lastElementPosition);
8277
}
8378

8479
/**
85-
* Removes the first occurrence of the specified element from this collection, if it is present.
80+
* Removes the first occurrence of the specified element from this collection, if present.
8681
* Will use identity comparison to check for presence;
8782
* two different instances which {@link Object#equals(Object) equal} are considered different elements.
8883
*
8984
* @param element element to be removed from this collection
90-
* @throws IllegalStateException if the element was not found in this collection
85+
* @throws IllegalStateException if the element wasn't found in this collection
9186
*/
9287
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) {
10088
var insertionPosition = elementPositionTracker.clearPosition(element);
10189
if (insertionPosition < 0) {
102-
return false;
90+
throw new IllegalStateException("Impossible state: the element (%s) was not found in the IndexedSet."
91+
.formatted(element));
10392
}
10493
if (insertionPosition == lastElementPosition) {
10594
// The element was the last one added; we can simply remove it.
@@ -111,7 +100,6 @@ private boolean innerRemove(T element) {
111100
gapCount++;
112101
}
113102
clearIfPossible();
114-
return true;
115103
}
116104

117105
private boolean clearIfPossible() {
@@ -139,68 +127,65 @@ public int size() {
139127
*
140128
* @param elementConsumer the action to be performed for each element;
141129
* may include removing elements from the collection,
142-
* but additions or swaps are not allowed;
130+
* but additions or swaps aren't allowed;
143131
* undefined behavior will occur if that is attempted.
144132
*/
145133
public void forEach(Consumer<T> elementConsumer) {
146134
if (isEmpty()) {
147135
return;
148136
}
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);
137+
if (shouldCompact()) {
138+
forEachCompacting(elementConsumer);
139+
} else {
140+
forEachNonCompacting(elementConsumer);
141+
}
157142
}
158143

159144
private boolean shouldCompact() {
160-
int elementCount = lastElementPosition + 1;
145+
if (gapCount == 0) {
146+
return false;
147+
}
148+
var elementCount = lastElementPosition + 1;
161149
if (elementCount < MINIMUM_ELEMENT_COUNT_FOR_COMPACTION) {
162150
return false;
163151
}
164152
var gapPercentage = gapCount / (double) elementCount;
165153
return gapPercentage > GAP_RATIO_FOR_COMPACTION;
166154
}
167155

168-
private @Nullable T forEachNonCompacting(Predicate<T> elementPredicate) {
169-
return forEachNonCompacting(elementPredicate, 0);
156+
private void forEachNonCompacting(Consumer<T> elementConsumer) {
157+
forEachNonCompacting(elementConsumer, 0);
170158
}
171159

172-
private @Nullable T forEachNonCompacting(Predicate<T> elementPredicate, int startingIndex) {
160+
private void forEachNonCompacting(Consumer<T> elementConsumer, int startingIndex) {
173161
for (var i = startingIndex; i <= lastElementPosition; i++) {
174162
var element = elementList.get(i);
175-
if (element != null && elementPredicate.test(element)) {
176-
return element;
163+
if (element != null) {
164+
elementConsumer.accept(element);
177165
}
178166
}
179-
return null;
180167
}
181168

182-
private @Nullable T forEachCompacting(Predicate<T> elementPredicate) {
169+
private void forEachCompacting(Consumer<T> elementConsumer) {
183170
if (clearIfPossible()) {
184-
return null;
171+
return;
185172
}
186173
for (var i = 0; i <= lastElementPosition; i++) {
187174
var element = elementList.get(i);
188175
if (element == null) {
189176
element = fillGap(i);
190177
if (element == null) { // There was nothing to fill the gap with; we are done.
191-
return null;
178+
return;
192179
}
193180
}
194-
if (elementPredicate.test(element)) {
195-
return element;
196-
}
181+
elementConsumer.accept(element);
197182
if (gapCount == 0) {
198183
// No more gaps to fill; we can continue without compacting.
199184
// This is an optimized loop which no longer checks for gaps.
200-
return forEachNonCompacting(elementPredicate, i + 1);
185+
forEachNonCompacting(elementConsumer, i + 1);
186+
return;
201187
}
202188
}
203-
return null;
204189
}
205190

206191
/**
@@ -210,7 +195,7 @@ private boolean shouldCompact() {
210195
* @return the element that now occupies position i, or null if no element further in the list can fill the gap
211196
*/
212197
private @Nullable T fillGap(int gapPosition) {
213-
T lastRemovedElement = removeLastNonGap(gapPosition);
198+
var lastRemovedElement = removeLastNonGap(gapPosition);
214199
if (lastRemovedElement == null) {
215200
return null;
216201
}
@@ -243,7 +228,45 @@ private boolean shouldCompact() {
243228
if (isEmpty()) {
244229
return null;
245230
}
246-
return forEach(elementPredicate);
231+
return shouldCompact() ? findFirstCompacting(elementPredicate) : findFirstNonCompacting(elementPredicate);
232+
}
233+
234+
private @Nullable T findFirstNonCompacting(Predicate<T> elementPredicate) {
235+
return findFirstNonCompacting(elementPredicate, 0);
236+
}
237+
238+
private @Nullable T findFirstNonCompacting(Predicate<T> elementPredicate, int startingIndex) {
239+
for (var i = startingIndex; i <= lastElementPosition; i++) {
240+
var element = elementList.get(i);
241+
if (element != null && elementPredicate.test(element)) {
242+
return element;
243+
}
244+
}
245+
return null;
246+
}
247+
248+
private @Nullable T findFirstCompacting(Predicate<T> elementPredicate) {
249+
if (clearIfPossible()) {
250+
return null;
251+
}
252+
for (var i = 0; i <= lastElementPosition; i++) {
253+
var element = elementList.get(i);
254+
if (element == null) {
255+
element = fillGap(i);
256+
if (element == null) { // There was nothing to fill the gap with; we are done.
257+
return null;
258+
}
259+
}
260+
if (elementPredicate.test(element)) {
261+
return element;
262+
}
263+
if (gapCount == 0) {
264+
// No more gaps to fill; we can continue without compacting.
265+
// This is an optimized loop which no longer checks for gaps.
266+
return findFirstNonCompacting(elementPredicate, i + 1);
267+
}
268+
}
269+
return null;
247270
}
248271

249272
/**

0 commit comments

Comments
 (0)