Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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,67 @@
package ai.timefold.solver.core.impl.bavet.common.index;

import java.util.Collection;
import java.util.List;
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 ContainIndexer<T, Key_, CollectionKey extends Collection<Key_>> implements Indexer<T> {

private final KeyRetriever<Key_> keyRetriever;
private final Supplier<Indexer<T>> downstreamIndexerSupplier;

/**
* @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.keyRetriever = Objects.requireNonNull(keyRetriever);
this.downstreamIndexerSupplier = Objects.requireNonNull(downstreamIndexerSupplier);
}

@Override
public ListEntry<T> put(Object compositeKey, T tuple) {
throw new UnsupportedOperationException();
}

@Override
public void remove(Object compositeKey, ListEntry<T> entry) {
throw new UnsupportedOperationException();
}

private Indexer<T> getDownstreamIndexer(Object compositeKey, Key_ indexerKey, ListEntry<T> entry) {
throw new UnsupportedOperationException();
}

@Override
public int size(Object compositeKey) {
throw new UnsupportedOperationException();
}

@Override
public void forEach(Object compositeKey, Consumer<T> tupleConsumer) {
throw new UnsupportedOperationException();
}

@Override
public boolean isEmpty() {
throw new UnsupportedOperationException();
}

@Override
public List<? extends ListEntry<T>> asList(Object compositeKey) {
throw new UnsupportedOperationException();
}

@Override
public String toString() {
throw new UnsupportedOperationException();
}

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

import java.util.Collection;
import java.util.List;
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_, CollectionKey extends Collection<Key_>> implements Indexer<T> {

private final KeyRetriever<Key_> keyRetriever;
private final Supplier<Indexer<T>> downstreamIndexerSupplier;

/**
* @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.keyRetriever = Objects.requireNonNull(keyRetriever);
this.downstreamIndexerSupplier = Objects.requireNonNull(downstreamIndexerSupplier);
}

@Override
public ListEntry<T> put(Object compositeKey, T tuple) {
throw new UnsupportedOperationException();
}

@Override
public void remove(Object compositeKey, ListEntry<T> entry) {
throw new UnsupportedOperationException();
}

private Indexer<T> getDownstreamIndexer(Object compositeKey, Key_ indexerKey, ListEntry<T> entry) {
throw new UnsupportedOperationException();
}

@Override
public int size(Object compositeKey) {
throw new UnsupportedOperationException();
}

@Override
public void forEach(Object compositeKey, Consumer<T> tupleConsumer) {
throw new UnsupportedOperationException();
}

@Override
public boolean isEmpty() {
throw new UnsupportedOperationException();
}

@Override
public List<? extends ListEntry<T>> asList(Object compositeKey) {
throw new UnsupportedOperationException();
}

@Override
public String toString() {
throw new UnsupportedOperationException();
}

}
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
*/
@NullMarked
public sealed interface Indexer<T>
permits ComparisonIndexer, EqualsIndexer, IndexerBackend {
permits EqualIndexer, ComparisonIndexer, ContainIndexer, ContainedInIndexer, IndexerBackend {

ListEntry<T> put(Object compositeKey, T tuple);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public final class IndexerFactory<Right_> {

public IndexerFactory(AbstractJoiner<Right_> joiner) {
this.joiner = joiner;
// TODO Code encapsulation: remove the field requiresRandomAccess and call joiner.requireRandomAccess() instead?
// TODO It also impacts the flip(). Is requiresRandomAccess a good name?
this.requiresRandomAccess = joiner instanceof DefaultBiEnumeratingJoiner<?, Right_>;
var joinerCount = joiner.getJoinerCount();
if (joinerCount < 2) {
Expand Down Expand Up @@ -497,48 +499,60 @@ public <T> Indexer<T> buildIndexer(boolean isLeftBridge) {
return backendSupplier.get();
} else if (joiner.getJoinerCount() == 1) { // Single joiner maps directly to EqualsIndexer or ComparisonIndexer.
var joinerType = joiner.getJoinerType(0);
if (joinerType == JoinerType.EQUAL) {
return new EqualsIndexer<>(backendSupplier);
} else {
// Note that if creating indexer for a right bridge node, the joiner type has to be flipped.
// (<A, B> becomes <B, A>.)
// This does not apply if random access is required,
// because in that case we create a right bridge only,
// and we query it from the left.
var actualJoinerType = isLeftBridge || requiresRandomAccess ? joinerType : joinerType.flip();
return new ComparisonIndexer<>(actualJoinerType, backendSupplier);
}
KeyRetriever<?> keyRetriever = new SingleKeyRetriever<>();
return buildIndexerPart(isLeftBridge, joinerType, keyRetriever, backendSupplier);
}
// The following code builds the children first, so it needs to iterate over the joiners in reverse order.
// The following code builds the children first, so it iterates over the joiners in reverse order.
var descendingJoinerTypeMap = joinerTypeMap.descendingMap();
Supplier<Indexer<T>> downstreamIndexerSupplier = backendSupplier;
var indexPropertyId = descendingJoinerTypeMap.size() - 1;
for (var entry : descendingJoinerTypeMap.entrySet()) {
var joinerType = entry.getValue();
if (downstreamIndexerSupplier == backendSupplier && indexPropertyId == 0) {
if (joinerType == JoinerType.EQUAL) {
downstreamIndexerSupplier = () -> new EqualsIndexer<>(backendSupplier);
} else {
var actualJoinerType = isLeftBridge ? joinerType : joinerType.flip();
downstreamIndexerSupplier = () -> new ComparisonIndexer<>(actualJoinerType, backendSupplier);
}
KeyRetriever<?> keyRetriever = new SingleKeyRetriever<>();
downstreamIndexerSupplier = () -> buildIndexerPart(isLeftBridge, joinerType, keyRetriever, backendSupplier);
} else {
KeyRetriever<?> keyRetriever = new CompositeKeyRetriever<>(indexPropertyId);
var actualDownstreamIndexerSupplier = downstreamIndexerSupplier;
var effectivelyFinalIndexPropertyId = indexPropertyId;
if (joinerType == JoinerType.EQUAL) {
downstreamIndexerSupplier =
() -> new EqualsIndexer<>(effectivelyFinalIndexPropertyId, actualDownstreamIndexerSupplier);
} else {
var actualJoinerType = isLeftBridge ? joinerType : joinerType.flip();
downstreamIndexerSupplier = () -> new ComparisonIndexer<>(actualJoinerType, effectivelyFinalIndexPropertyId,
actualDownstreamIndexerSupplier);
}
downstreamIndexerSupplier = () -> buildIndexerPart(isLeftBridge, joinerType, keyRetriever,
actualDownstreamIndexerSupplier);
}
indexPropertyId--;
}
return downstreamIndexerSupplier.get();
}

private <T> Indexer<T> buildIndexerPart(boolean isLeftBridge, JoinerType joinerType, KeyRetriever<?> keyRetriever,
Supplier<Indexer<T>> backendSupplier) {
// Note that if creating indexer for a right bridge node, the joiner type has to be flipped.
// (<A, B> becomes <B, A>.)
// TODO Does the requiresRandomAccess check make sense?
// Shouldn't a right bridge always flip, even if there is no left bridge?
// TODO For neighborhoods, why create a left bridge index and keep it up to date at all?
// This does not apply if random access is required,
// because in that case we create a right bridge only,
// and we query it from the left.
if (!isLeftBridge && !requiresRandomAccess) {
joinerType = joinerType.flip();
}
switch (joinerType) {
case EQUAL -> {
return new EqualIndexer<>(keyRetriever, backendSupplier);
}
case LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL -> {
return new ComparisonIndexer<>(joinerType, keyRetriever, backendSupplier);
}
case CONTAIN -> {
return new ContainIndexer<>(keyRetriever, backendSupplier);
}
case CONTAINED_IN -> {
return new ContainedInIndexer<>(keyRetriever, backendSupplier);
}
default -> throw new IllegalStateException(
"Impossible state: The joiner type (" + joinerType + ") is not implemented.");
}
}

/**
* Represents a function which extracts index keys from a tuple.
*
Expand Down
Loading
Loading