Skip to content

Commit 3a7d400

Browse files
committed
Revert "keep just one leaf"
This reverts commit 4ad849b.
1 parent ffce370 commit 3a7d400

4 files changed

Lines changed: 78 additions & 9 deletions

File tree

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,8 @@ public UniKeysExtractor<Right_> buildRightKeysExtractor() {
483483
}
484484

485485
public <T> Indexer<T> buildIndexer(boolean isLeftBridge) {
486-
Supplier<Indexer<T>> backendSupplier = RandomAccessLeafIndexer::new;
486+
Supplier<Indexer<T>> backendSupplier =
487+
requiresRandomAccess ? RandomAccessLeafIndexer::new : LinkedListLeafIndexer::new;
487488
if (!hasJoiners()) { // NoneJoiner results in a bare backend (NoneIndexer).
488489
return backendSupplier.get();
489490
}
@@ -516,7 +517,8 @@ private <T> Supplier<Indexer<T>> buildIndexerChain(boolean isLeftBridge, int fro
516517
// Leaf-most level whose index key equals the whole composite key: no KeyUnpacker indirection.
517518
if (joinerType == JoinerType.EQUAL) {
518519
// Fuse the leaf-most equal indexer with its backend.
519-
downstreamIndexerSupplier = () -> new EqualIndexer<>(KeyUnpacker.single(), RandomAccessLeafIndexer::new);
520+
downstreamIndexerSupplier = () -> new EqualIndexer<>(KeyUnpacker.single(),
521+
requiresRandomAccess ? RandomAccessLeafIndexer::new : LinkedListLeafIndexer::new);
520522
} else {
521523
KeyUnpacker<?> keyUnpacker = KeyUnpacker.single();
522524
downstreamIndexerSupplier = () -> buildIndexerPart(isLeftBridge, joinerType, keyUnpacker, backendSupplier);
@@ -559,12 +561,11 @@ public <L, R> FusedEqualIndex<L, R> buildFusedEqualIndex() {
559561
equalPrefixLength == joinerCount ? KeyUnpacker.single() : KeyUnpacker.composite(0);
560562
if (equalPrefixLength == joinerCount) {
561563
// Pure equal: the per-side downstream is just the tuple list; the bucket is the equal-key group.
562-
return new FusedEqualIndex<>(topEqualKeyUnpacker, false, RandomAccessLeafIndexer::new,
563-
RandomAccessLeafIndexer::new);
564+
return new FusedEqualIndex<>(topEqualKeyUnpacker, false, LinkedListLeafIndexer::new, LinkedListLeafIndexer::new);
564565
} else {
565566
// Equal prefix + suffix: build the per-side suffix sub-chain (the right side flips comparisons).
566-
var leftDownstreamSupplier = this.<L> buildIndexerChain(true, 1, RandomAccessLeafIndexer::new);
567-
var rightDownstreamSupplier = this.<R> buildIndexerChain(false, 1, RandomAccessLeafIndexer::new);
567+
var leftDownstreamSupplier = this.<L> buildIndexerChain(true, 1, LinkedListLeafIndexer::new);
568+
var rightDownstreamSupplier = this.<R> buildIndexerChain(false, 1, LinkedListLeafIndexer::new);
568569
return new FusedEqualIndex<>(topEqualKeyUnpacker, true, leftDownstreamSupplier, rightDownstreamSupplier);
569570
}
570571
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
@NullMarked
1313
public sealed interface LeafIndexer<T>
1414
extends Indexer<T>
15-
permits RandomAccessLeafIndexer {
15+
permits RandomAccessLeafIndexer, LinkedListLeafIndexer {
1616

1717
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package ai.timefold.solver.core.impl.bavet.common.index;
2+
3+
import java.util.Iterator;
4+
import java.util.function.Consumer;
5+
import java.util.function.Predicate;
6+
import java.util.random.RandomGenerator;
7+
8+
import ai.timefold.solver.core.impl.util.ElementAwareLinkedList;
9+
import ai.timefold.solver.core.impl.util.ListEntry;
10+
11+
import org.jspecify.annotations.NullMarked;
12+
13+
/**
14+
* Super-fast, but doesn't support random access.
15+
*
16+
* @param <T>
17+
*/
18+
@NullMarked
19+
public final class LinkedListLeafIndexer<T> implements LeafIndexer<T> {
20+
21+
private final ElementAwareLinkedList<T> tupleList = new ElementAwareLinkedList<>();
22+
23+
@Override
24+
public ListEntry<T> put(Object compositeKey, T tuple) {
25+
return tupleList.add(tuple);
26+
}
27+
28+
@Override
29+
public void remove(Object compositeKey, ListEntry<T> entry) {
30+
tupleList.remove((ElementAwareLinkedList.Entry<T>) entry);
31+
}
32+
33+
@Override
34+
public int size(Object compositeKey) {
35+
return tupleList.size();
36+
}
37+
38+
@Override
39+
public void forEach(Object compositeKey, Consumer<T> tupleConsumer) {
40+
tupleList.forEach(tupleConsumer);
41+
}
42+
43+
@Override
44+
public Iterator<T> iterator(Object queryCompositeKey) {
45+
return tupleList.iterator();
46+
}
47+
48+
@Override
49+
public Iterator<T> randomIterator(Object queryCompositeKey, RandomGenerator workingRandom) { // Neighborhoods will not get here.
50+
throw new UnsupportedOperationException("Impossible state: This backend does not support random access.");
51+
}
52+
53+
@Override
54+
public Iterator<T> randomIterator(Object queryCompositeKey, RandomGenerator workingRandom, Predicate<T> filter) { // Neighborhoods will not get here.
55+
throw new UnsupportedOperationException("Impossible state: This backend does not support random access.");
56+
}
57+
58+
@Override
59+
public boolean isRemovable() {
60+
return tupleList.size() == 0;
61+
}
62+
63+
@Override
64+
public String toString() {
65+
return "size = " + tupleList.size();
66+
}
67+
68+
}

core/src/test/java/ai/timefold/solver/core/impl/bavet/common/index/FusedEqualIndexTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,10 @@ void lazyAllocation_leftOnlyKey_rightNotAllocated() {
127127
var index = new FusedEqualIndex<String, String>(
128128
KeyUnpacker.single(), // identity: pure-equal, single-component key
129129
false, // hasSuffix
130-
RandomAccessLeafIndexer::new,
130+
LinkedListLeafIndexer::new,
131131
() -> {
132132
rightInitCount.incrementAndGet();
133-
return new RandomAccessLeafIndexer<>();
133+
return new LinkedListLeafIndexer<>();
134134
});
135135

136136
// right downstream must NOT be allocated on bucket creation

0 commit comments

Comments
 (0)