Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ai.timefold.solver.core.api.score.stream;

import java.util.Collection;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
Expand Down Expand Up @@ -35,7 +36,6 @@
public final class Joiners {

// TODO Support using non-natural comparators, such as lessThan(leftMapping, rightMapping, comparator).
// TODO Support collection-based joiners, such as containing(), intersecting() and disjoint().

// ************************************************************************
// BiJoiner
Expand Down Expand Up @@ -78,6 +78,18 @@ public final class Joiners {
return new DefaultBiJoiner<>(leftMapping, JoinerType.EQUAL, rightMapping);
}

// TODO javadoc
public static <A, B, Property_> @NonNull BiJoiner<A, B> contain(Function<A, Collection<Property_>> leftMapping,
Function<B, Property_> rightMapping) {
return new DefaultBiJoiner<>(leftMapping, JoinerType.CONTAIN, rightMapping);
}

// TODO javadoc
public static <A, B, Property_> @NonNull BiJoiner<A, B> containedIn(Function<A, Property_> leftMapping,
Function<B, Collection<Property_>> rightMapping) {
return new DefaultBiJoiner<>(leftMapping, JoinerType.CONTAINED_IN, rightMapping);
}

/**
* As defined by {@link #lessThan(Function, Function)} with both arguments using the same mapping.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,16 @@ public final class DefaultBiJoiner<A, B> extends AbstractJoiner<B> implements Bi

private static final DefaultBiJoiner NONE = new DefaultBiJoiner(new Function[0], new JoinerType[0], new Function[0]);

private final Function<A, ?>[] leftMappings;
private final Function<A, Object>[] leftMappings;

public <Property_> DefaultBiJoiner(Function<A, Property_> leftMapping, JoinerType joinerType,
Function<B, Property_> rightMapping) {
public DefaultBiJoiner(Function<A, ?> leftMapping, JoinerType joinerType, Function<B, ?> rightMapping) {
super(rightMapping, joinerType);
this.leftMappings = new Function[] { leftMapping };
}

public <Property_> DefaultBiJoiner(Function<A, Property_>[] leftMappings, JoinerType[] joinerTypes,
Function<B, Property_>[] rightMappings) {
public DefaultBiJoiner(Function<A, ?>[] leftMappings, JoinerType[] joinerTypes, Function<B, ?>[] rightMappings) {
super(rightMappings, joinerTypes);
this.leftMappings = leftMappings;
this.leftMappings = (Function<A, Object>[]) Objects.requireNonNull(leftMappings);
}

public static <A, B> DefaultBiJoiner<A, B> merge(List<DefaultBiJoiner<A, B>> joinerList) {
Expand Down Expand Up @@ -55,7 +53,7 @@ public static <A, B> DefaultBiJoiner<A, B> merge(List<DefaultBiJoiner<A, B>> joi
}

public Function<A, Object> getLeftMapping(int index) {
return (Function<A, Object>) leftMappings[index];
return leftMappings[index];
}

public boolean matches(A a, B b) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,13 @@ final class ComparisonIndexer<T, Key_ extends Comparable<Key_>>
private final NavigableMap<Key_, Indexer<T>> comparisonMap;

/**
* Construct an {@link ComparisonIndexer} which immediately ends in the backend.
* This means {@code compositeKey} must be a single key.
*
* @param comparisonJoinerType the type of comparison to use
* @param keyRetriever determines if it immediately goes to a {@link IndexerBackend} or if it uses a {@link CompositeKey}.
* @param downstreamIndexerSupplier the supplier of the downstream indexer
*/
public ComparisonIndexer(JoinerType comparisonJoinerType, Supplier<Indexer<T>> downstreamIndexerSupplier) {
this(comparisonJoinerType, new SingleKeyRetriever<>(), downstreamIndexerSupplier);
}

/**
* Construct an {@link ComparisonIndexer} which does not immediately go to a {@link IndexerBackend}.
* This means {@code compositeKey} must be an instance of {@link CompositeKey}.
*
* @param comparisonJoinerType the type of comparison to use
* @param keyIndex the index of the key to use within {@link CompositeKey}.
* @param downstreamIndexerSupplier the supplier of the downstream indexer
*/
public ComparisonIndexer(JoinerType comparisonJoinerType, int keyIndex, Supplier<Indexer<T>> downstreamIndexerSupplier) {
this(comparisonJoinerType, new CompositeKeyRetriever<>(keyIndex), downstreamIndexerSupplier);
}

private ComparisonIndexer(JoinerType comparisonJoinerType, KeyRetriever<Key_> keyRetriever,
public ComparisonIndexer(JoinerType comparisonJoinerType, KeyRetriever<?> keyRetriever,
Supplier<Indexer<T>> downstreamIndexerSupplier) {
this.keyRetriever = Objects.requireNonNull(keyRetriever);
this.keyRetriever = Objects.requireNonNull((KeyRetriever<Key_>) keyRetriever);
this.downstreamIndexerSupplier = Objects.requireNonNull(downstreamIndexerSupplier);
// For GT/GTE, the iteration order is reversed.
// This allows us to iterate over the entire map from the start, stopping when the threshold is reached.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* TriCompositeKey and higher are rare enough for {@link MegaCompositeKey} to suffice.
*/
public sealed interface CompositeKey
permits MegaCompositeKey, BiCompositeKey {
permits BiCompositeKey, MegaCompositeKey {

static CompositeKey none() {
return MegaCompositeKey.EMPTY;
Expand Down Expand Up @@ -53,7 +53,7 @@ static CompositeKey ofMany(Object... keys) {
* @param id Maps to a single {@link Indexer} instance in the indexer chain.
* @return May be null if the key is null.
* @param <Key_> {@link ComparisonIndexer} will expect this to implement {@link Comparable}.
* {@link EqualsIndexer} will treat items as the same if they are equal.
* {@link EqualIndexer} will treat items as the same if they are equal.
*/
<Key_> Key_ get(int id);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package ai.timefold.solver.core.impl.bavet.common.index;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;

import ai.timefold.solver.core.impl.util.CompositeListEntry;
import ai.timefold.solver.core.impl.util.ListEntry;
import ai.timefold.solver.core.impl.util.Pair;

import org.jspecify.annotations.NullMarked;

@NullMarked
final class ContainIndexer<T, Key_, KeyCollection_ extends Collection<Key_>> implements Indexer<T> {

private final KeyRetriever<KeyCollection_> modifyKeyRetriever;
private final KeyRetriever<Key_> queryKeyRetriever;
private final Supplier<Indexer<T>> downstreamIndexerSupplier;
private final Map<Key_, Indexer<T>> downstreamIndexerMap = new HashMap<>();

/**
* @param keyRetriever determines if it immediately goes to a {@link IndexerBackend} or if it uses a {@link CompositeKey}.
* @param downstreamIndexerSupplier the supplier of the downstream indexer
*/
public ContainIndexer(KeyRetriever<Key_> keyRetriever, Supplier<Indexer<T>> downstreamIndexerSupplier) {
this.modifyKeyRetriever = Objects.requireNonNull((KeyRetriever<KeyCollection_>) keyRetriever);
this.queryKeyRetriever = Objects.requireNonNull(keyRetriever);
this.downstreamIndexerSupplier = Objects.requireNonNull(downstreamIndexerSupplier);
}

@Override
public ListEntry<T> put(Object modifyCompositeKey, T tuple) {
KeyCollection_ indexKeyCollection = modifyKeyRetriever.apply(modifyCompositeKey);
List<Pair<Key_, ListEntry<T>>> children = new ArrayList<>(indexKeyCollection.size());
for (Key_ indexKey : indexKeyCollection) {
// Avoids computeIfAbsent in order to not create lambdas on the hot path.
Indexer<T> downstreamIndexer = downstreamIndexerMap.get(indexKey);
if (downstreamIndexer == null) {
downstreamIndexer = downstreamIndexerSupplier.get();
downstreamIndexerMap.put(indexKey, downstreamIndexer);
}
// Even though this method puts a tuple in multiple downstreamIndexers, it does not break size() or forEach()
// because at most one of those downstreamIndexers matches for a particular compositeKey
ListEntry<T> childListEntry = downstreamIndexer.put(modifyCompositeKey, tuple);
children.add(new Pair<>(indexKey, childListEntry));
}
return new CompositeListEntry<>(tuple, children);
}

@Override
public void remove(Object modifyCompositeKey, ListEntry<T> entry) {
KeyCollection_ indexKeyCollection = modifyKeyRetriever.apply(modifyCompositeKey);
List<Pair<Key_, ListEntry<T>>> children = ((CompositeListEntry<Key_, T>) entry).getChildren();
if (indexKeyCollection.size() != children.size()) {
throw new IllegalStateException(
("Impossible state: the tuple (%s) with composite key (%s) has a different number of children (%d)" +
" than the index key collection size (%d).")
.formatted(entry, modifyCompositeKey, children.size(), indexKeyCollection.size()));
}
for (Pair<Key_, ListEntry<T>> child : children) {
Key_ indexKey = child.key();
ListEntry<T> childListEntry = child.value();
// Avoids removeIfAbsent in order to not create lambdas on the hot path.
Indexer<T> downstreamIndexer = getDownstreamIndexer(modifyCompositeKey, indexKey);
downstreamIndexer.remove(modifyCompositeKey, childListEntry);
if (downstreamIndexer.isEmpty()) {
downstreamIndexerMap.remove(indexKey);
}
}
}

private Indexer<T> getDownstreamIndexer(Object compositeKey, Key_ indexerKey) {
Indexer<T> downstreamIndexer = downstreamIndexerMap.get(indexerKey);
if (downstreamIndexer == null) {
throw new IllegalStateException(
"Impossible state: the composite key (%s) doesn't exist in the indexer %s."
.formatted(compositeKey, this));
}
return downstreamIndexer;
}

@Override
public int size(Object queryCompositeKey) {
Key_ indexKey = queryKeyRetriever.apply(queryCompositeKey);
Indexer<T> downstreamIndexer = downstreamIndexerMap.get(indexKey);
if (downstreamIndexer == null) {
return 0;
}
return downstreamIndexer.size(queryCompositeKey);
}

@Override
public void forEach(Object queryCompositeKey, Consumer<T> tupleConsumer) {
Key_ indexKey = queryKeyRetriever.apply(queryCompositeKey);
Indexer<T> downstreamIndexer = downstreamIndexerMap.get(indexKey);
if (downstreamIndexer == null) {
return;
}
downstreamIndexer.forEach(queryCompositeKey, tupleConsumer);
}

@Override
public boolean isEmpty() {
return downstreamIndexerMap.isEmpty();
}

@Override
public List<? extends ListEntry<T>> asList(Object queryCompositeKey) {
Key_ indexKey = queryKeyRetriever.apply(queryCompositeKey);
Indexer<T> downstreamIndexer = downstreamIndexerMap.get(indexKey);
if (downstreamIndexer == null) {
return Collections.emptyList();
}
return downstreamIndexer.asList(queryCompositeKey);
}

@Override
public String toString() {
return "size = " + downstreamIndexerMap.size();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package ai.timefold.solver.core.impl.bavet.common.index;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;

import ai.timefold.solver.core.impl.util.ListEntry;

import org.jspecify.annotations.NullMarked;

@NullMarked
final class ContainedInIndexer<T, Key_, KeyCollection_ extends Collection<Key_>> implements Indexer<T> {

private final KeyRetriever<Key_> modifyKeyRetriever;
private final KeyRetriever<KeyCollection_> queryKeyRetriever;
private final Supplier<Indexer<T>> downstreamIndexerSupplier;
private final Map<Key_, Indexer<T>> downstreamIndexerMap = new HashMap<>();

/**
* @param keyRetriever determines if it immediately goes to a {@link IndexerBackend} or if it uses a {@link CompositeKey}.
* @param downstreamIndexerSupplier the supplier of the downstream indexer
*/
public ContainedInIndexer(KeyRetriever<Key_> keyRetriever, Supplier<Indexer<T>> downstreamIndexerSupplier) {
this.modifyKeyRetriever = Objects.requireNonNull(keyRetriever);
this.queryKeyRetriever = Objects.requireNonNull((KeyRetriever<KeyCollection_>) keyRetriever);
this.downstreamIndexerSupplier = Objects.requireNonNull(downstreamIndexerSupplier);
}

@Override
public ListEntry<T> put(Object modifyCompositeKey, T tuple) {
Key_ indexKey = modifyKeyRetriever.apply(modifyCompositeKey);
// Avoids computeIfAbsent in order to not create lambdas on the hot path.
Indexer<T> downstreamIndexer = downstreamIndexerMap.get(indexKey);
if (downstreamIndexer == null) {
downstreamIndexer = downstreamIndexerSupplier.get();
downstreamIndexerMap.put(indexKey, downstreamIndexer);
}
return downstreamIndexer.put(modifyCompositeKey, tuple);
}

@Override
public void remove(Object modifyCompositeKey, ListEntry<T> entry) {
Key_ indexKey = modifyKeyRetriever.apply(modifyCompositeKey);
Indexer<T> downstreamIndexer = getDownstreamIndexer(modifyCompositeKey, indexKey, entry);
downstreamIndexer.remove(modifyCompositeKey, entry);
if (downstreamIndexer.isEmpty()) {
downstreamIndexerMap.remove(indexKey);
}
}

private Indexer<T> getDownstreamIndexer(Object compositeKey, Key_ indexerKey, ListEntry<T> entry) {
Indexer<T> downstreamIndexer = downstreamIndexerMap.get(indexerKey);
if (downstreamIndexer == null) {
throw new IllegalStateException(
"Impossible state: the tuple (%s) with composite key (%s) doesn't exist in the indexer %s."
.formatted(entry, compositeKey, this));
}
return downstreamIndexer;
}

@Override
public int size(Object queryCompositeKey) {
KeyCollection_ indexKeyCollection = queryKeyRetriever.apply(queryCompositeKey);
int size = 0;
for (Key_ indexKey : indexKeyCollection) {
Indexer<T> downstreamIndexer = downstreamIndexerMap.get(indexKey);
if (downstreamIndexer != null) {
size += downstreamIndexer.size(queryCompositeKey);
}
}
return size;
}

@Override
public void forEach(Object queryCompositeKey, Consumer<T> tupleConsumer) {
KeyCollection_ indexKeyCollection = queryKeyRetriever.apply(queryCompositeKey);
for (Key_ indexKey : indexKeyCollection) {
Indexer<T> downstreamIndexer = downstreamIndexerMap.get(indexKey);
if (downstreamIndexer != null) {
downstreamIndexer.forEach(queryCompositeKey, tupleConsumer);
}
}
}

@Override
public boolean isEmpty() {
return downstreamIndexerMap.isEmpty();
}

@Override
public List<? extends ListEntry<T>> asList(Object queryCompositeKey) {
KeyCollection_ indexKeyCollection = queryKeyRetriever.apply(queryCompositeKey);
List<ListEntry<T>> list = new ArrayList<>(downstreamIndexerMap.size() * 16);
for (Key_ indexKey : indexKeyCollection) {
Indexer<T> downstreamIndexer = downstreamIndexerMap.get(indexKey);
if (downstreamIndexer != null) {
list.addAll(downstreamIndexer.asList(queryCompositeKey));
}
}
return list;
}

@Override
public String toString() {
return "size = " + downstreamIndexerMap.size();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,18 @@
import org.jspecify.annotations.NullMarked;

@NullMarked
final class EqualsIndexer<T, Key_> implements Indexer<T> {
final class EqualIndexer<T, Key_> implements Indexer<T> {

private final KeyRetriever<Key_> keyRetriever;
private final Supplier<Indexer<T>> downstreamIndexerSupplier;
private final Map<Key_, Indexer<T>> downstreamIndexerMap = new HashMap<>();

/**
* Construct an {@link EqualsIndexer} which immediately ends in the backend.
* This means {@code compositeKey} must be a single key.
*/
public EqualsIndexer(Supplier<Indexer<T>> downstreamIndexerSupplier) {
this.keyRetriever = new SingleKeyRetriever<>();
this.downstreamIndexerSupplier = downstreamIndexerSupplier;
}

/**
* Construct an {@link EqualsIndexer} which does not immediately go to a {@link IndexerBackend}.
* This means {@code compositeKey} must be an instance of {@link CompositeKey}.
*
* @param keyIndex the index of the key to use within {@link CompositeKey}.
* @param keyRetriever determines if it immediately goes to a {@link IndexerBackend} or if it uses a {@link CompositeKey}.
* @param downstreamIndexerSupplier the supplier of the downstream indexer
*/
public EqualsIndexer(int keyIndex, Supplier<Indexer<T>> downstreamIndexerSupplier) {
this.keyRetriever = new CompositeKeyRetriever<>(keyIndex);
public EqualIndexer(KeyRetriever<Key_> keyRetriever, Supplier<Indexer<T>> downstreamIndexerSupplier) {
this.keyRetriever = Objects.requireNonNull(keyRetriever);
this.downstreamIndexerSupplier = Objects.requireNonNull(downstreamIndexerSupplier);
}

Expand Down
Loading
Loading