Skip to content

Commit 813259a

Browse files
feat: enable the contain* family of joiners
- Move UnfinishedJoiners to Joiners - Score corruption was caused when a duplicate was encounter, causing the forEach iterator to skip the remaining elements in its current downstream iterator and proceed to the next downstream iterator instead. It now exhausts the current downstream iterator beforce proceeding to the next one. - Due to the existance of duplicates, the implemented randomIterator is not completely fair; to create a fair random iterator, all the elements must be put into a set first.
1 parent 6c99a5b commit 813259a

15 files changed

Lines changed: 654 additions & 345 deletions

File tree

core/src/main/java/ai/timefold/solver/core/api/score/stream/Joiners.java

Lines changed: 240 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package ai.timefold.solver.core.api.score.stream;
22

3+
import java.util.SequencedCollection;
34
import java.util.function.BiFunction;
45
import java.util.function.BiPredicate;
56
import java.util.function.Function;
@@ -256,6 +257,93 @@ public final class Joiners {
256257
.and(Joiners.greaterThan(leftEndMapping, rightStartMapping));
257258
}
258259

260+
/**
261+
* Joins every A and B where a value of property on B is contained in the collection of properties on A.
262+
* <p>
263+
* For example:
264+
* <ul>
265+
* <li>{@code ["A", "B"]} containing {@code "A"} is {@code true}</li>
266+
* <li>{@code ["A"]} containing {@code "A"} is {@code true}</li>
267+
* <li>{@code ["X", "Y"]} containing {@code "A"} is {@code false}</li>
268+
* <li>{@code []} containing {@code "A"} is {@code false}</li>
269+
* <li>{@code ["A", "B"]} containing {@code null} is {@code false}</li>
270+
* <li>{@code []} containing {@code null} is {@code false}</li>
271+
* </ul>
272+
*
273+
* @param leftMapping mapping function to apply to A
274+
* @param rightMapping mapping function to apply to B
275+
* @param <A> the type of object on the left
276+
* @param <B> the type of object on the right
277+
* @param <Property_> the type of the property to compare
278+
*/
279+
public static <A, B, Property_> @NonNull BiJoiner<A, B> containing(
280+
@NonNull Function<A, SequencedCollection<Property_>> leftMapping,
281+
@NonNull Function<B, Property_> rightMapping) {
282+
return new DefaultBiJoiner<>(leftMapping, JoinerType.CONTAINING, rightMapping);
283+
}
284+
285+
/**
286+
* Joins every A and B where a value of property on A is contained in the collection of properties on B.
287+
* <p>
288+
* For example:
289+
* <ul>
290+
* <li>{@code "A"} contained in {@code ["A", "B"]} is {@code true}</li>
291+
* <li>{@code "A"} contained in {@code ["A"]} is {@code true}</li>
292+
* <li>{@code "A"} contained in {@code ["X", "Y"]} is {@code false}</li>
293+
* <li>{@code "A"} contained in {@code []} is {@code false}</li>
294+
* <li>{@code null} contained in {@code ["A", "B"]} is {@code false}</li>
295+
* <li>{@code null} contained in {@code []} is {@code false}</li>
296+
* </ul>
297+
*
298+
* @param leftMapping mapping function to apply to A
299+
* @param rightMapping mapping function to apply to B
300+
* @param <A> the type of object on the left
301+
* @param <B> the type of object on the right
302+
* @param <Property_> the type of the property to compare
303+
*/
304+
public static <A, B, Property_> @NonNull BiJoiner<A, B> containedIn(@NonNull Function<A, Property_> leftMapping,
305+
@NonNull Function<B, SequencedCollection<Property_>> rightMapping) {
306+
return new DefaultBiJoiner<>(leftMapping, JoinerType.CONTAINED_IN, rightMapping);
307+
}
308+
309+
/**
310+
* As defined by {@link #containingAnyOf(Function, Function)} with both arguments using the same mapping.
311+
*
312+
* @param mapping mapping function to apply to both A and B
313+
* @param <A> the type of both objects
314+
* @param <Property_> the type of the property to compare
315+
*/
316+
public static <A, Property_> @NonNull BiJoiner<A, A> containingAnyOf(
317+
@NonNull Function<A, SequencedCollection<Property_>> mapping) {
318+
return containingAnyOf(mapping, mapping);
319+
}
320+
321+
/**
322+
* Joins every A and B where a collection of properties on A overlaps with a collection of properties on B.
323+
* <p>
324+
* For example:
325+
* <ul>
326+
* <li>{@code ["A", "B"]} intersecting {@code ["A", "B"]} is {@code true}</li>
327+
* <li>{@code ["A", "B"]} intersecting {@code ["A"]} is {@code true}</li>
328+
* <li>{@code ["A"]} intersecting {@code ["A", "B"]} is {@code true}</li>
329+
* <li>{@code ["A", "B"]} intersecting {@code ["X", "Y"]} is {@code false}</li>
330+
* <li>{@code ["A", "B"]} intersecting {@code []} is {@code false}</li>
331+
* <li>{@code []} intersecting {@code ["A", "B"]} is {@code false}</li>
332+
* <li>{@code []} intersecting {@code []} is {@code false}</li>
333+
* </ul>
334+
*
335+
* @param leftMapping mapping function to apply to A
336+
* @param rightMapping mapping function to apply to B
337+
* @param <A> the type of object on the left
338+
* @param <B> the type of object on the right
339+
* @param <Property_> the type of the property to compare
340+
*/
341+
public static <A, B, Property_> @NonNull BiJoiner<A, B> containingAnyOf(
342+
@NonNull Function<A, SequencedCollection<Property_>> leftMapping,
343+
@NonNull Function<B, SequencedCollection<Property_>> rightMapping) {
344+
return new DefaultBiJoiner<>(leftMapping, JoinerType.CONTAINING_ANY_OF, rightMapping);
345+
}
346+
259347
// ************************************************************************
260348
// TriJoiner
261349
// ************************************************************************
@@ -366,6 +454,53 @@ public final class Joiners {
366454
.and(Joiners.greaterThan(leftEndMapping, rightStartMapping));
367455
}
368456

457+
/**
458+
* As defined by {@link #containing(Function, Function)}.
459+
*
460+
* @param <A> the type of the first object on the left
461+
* @param <B> the type of the second object on the left
462+
* @param <C> the type of the object on the right
463+
* @param <Property_> the type of the collection elements
464+
* @param leftMapping mapping function to apply to (A,B)
465+
* @param rightMapping mapping function to apply to C
466+
*/
467+
public static <A, B, C, Property_> @NonNull TriJoiner<A, B, C> containing(
468+
@NonNull BiFunction<A, B, SequencedCollection<Property_>> leftMapping,
469+
@NonNull Function<C, Property_> rightMapping) {
470+
return new DefaultTriJoiner<>(leftMapping, JoinerType.CONTAINING, rightMapping);
471+
}
472+
473+
/**
474+
* As defined by {@link #containedIn(Function, Function)}.
475+
*
476+
* @param <A> the type of the first object on the left
477+
* @param <B> the type of the second object on the left
478+
* @param <C> the type of the object on the right
479+
* @param <Property_> the type of the collection elements
480+
* @param leftMapping mapping function to apply to (A,B)
481+
* @param rightMapping mapping function to apply to C
482+
*/
483+
public static <A, B, C, Property_> @NonNull TriJoiner<A, B, C> containedIn(@NonNull BiFunction<A, B, Property_> leftMapping,
484+
@NonNull Function<C, SequencedCollection<Property_>> rightMapping) {
485+
return new DefaultTriJoiner<>(leftMapping, JoinerType.CONTAINED_IN, rightMapping);
486+
}
487+
488+
/**
489+
* As defined by {@link #containingAnyOf(Function, Function)}.
490+
*
491+
* @param <A> the type of the first object on the left
492+
* @param <B> the type of the second object on the left
493+
* @param <C> the type of the object on the right
494+
* @param <Property_> the type of the collection elements
495+
* @param leftMapping mapping function to apply to (A,B)
496+
* @param rightMapping mapping function to apply to C
497+
*/
498+
public static <A, B, C, Property_> @NonNull TriJoiner<A, B, C> containingAnyOf(
499+
@NonNull BiFunction<A, B, SequencedCollection<Property_>> leftMapping,
500+
@NonNull Function<C, SequencedCollection<Property_>> rightMapping) {
501+
return new DefaultTriJoiner<>(leftMapping, JoinerType.CONTAINING_ANY_OF, rightMapping);
502+
}
503+
369504
// ************************************************************************
370505
// QuadJoiner
371506
// ************************************************************************
@@ -483,6 +618,57 @@ public final class Joiners {
483618
.and(Joiners.greaterThan(leftEndMapping, rightStartMapping));
484619
}
485620

621+
/**
622+
* As defined by {@link #containing(Function, Function)}.
623+
*
624+
* @param <A> the type of the first object on the left
625+
* @param <B> the type of the second object on the left
626+
* @param <C> the type of the third object on the left
627+
* @param <D> the type of the object on the right
628+
* @param <Property_> the type of the collection elements
629+
* @param leftMapping mapping function to apply to (A,B,C)
630+
* @param rightMapping mapping function to apply to D
631+
*/
632+
public static <A, B, C, D, Property_> @NonNull QuadJoiner<A, B, C, D> containing(
633+
@NonNull TriFunction<A, B, C, SequencedCollection<Property_>> leftMapping,
634+
@NonNull Function<D, Property_> rightMapping) {
635+
return new DefaultQuadJoiner<>(leftMapping, JoinerType.CONTAINING, rightMapping);
636+
}
637+
638+
/**
639+
* As defined by {@link #containedIn(Function, Function)}.
640+
*
641+
* @param <A> the type of the first object on the left
642+
* @param <B> the type of the second object on the left
643+
* @param <C> the type of the third object on the left
644+
* @param <D> the type of the object on the right
645+
* @param <Property_> the type of the collection elements
646+
* @param leftMapping mapping function to apply to (A,B,C)
647+
* @param rightMapping mapping function to apply to D
648+
*/
649+
public static <A, B, C, D, Property_> @NonNull QuadJoiner<A, B, C, D> containedIn(
650+
@NonNull TriFunction<A, B, C, Property_> leftMapping,
651+
@NonNull Function<D, SequencedCollection<Property_>> rightMapping) {
652+
return new DefaultQuadJoiner<>(leftMapping, JoinerType.CONTAINED_IN, rightMapping);
653+
}
654+
655+
/**
656+
* As defined by {@link #containingAnyOf(Function, Function)}.
657+
*
658+
* @param <A> the type of the first object on the left
659+
* @param <B> the type of the second object on the left
660+
* @param <C> the type of the third object on the left
661+
* @param <D> the type of the object on the right
662+
* @param <Property_> the type of the collection elements
663+
* @param leftMapping mapping function to apply to (A,B,C)
664+
* @param rightMapping mapping function to apply to D
665+
*/
666+
public static <A, B, C, D, Property_> @NonNull QuadJoiner<A, B, C, D> containingAnyOf(
667+
@NonNull TriFunction<A, B, C, SequencedCollection<Property_>> leftMapping,
668+
@NonNull Function<D, SequencedCollection<Property_>> rightMapping) {
669+
return new DefaultQuadJoiner<>(leftMapping, JoinerType.CONTAINING_ANY_OF, rightMapping);
670+
}
671+
486672
// ************************************************************************
487673
// PentaJoiner
488674
// ************************************************************************
@@ -608,7 +794,60 @@ public final class Joiners {
608794
.and(Joiners.greaterThan(leftEndMapping, rightStartMapping));
609795
}
610796

611-
private Joiners() {
797+
/**
798+
* As defined by {@link #containing(Function, Function)}.
799+
*
800+
* @param <A> the type of the first object on the left
801+
* @param <B> the type of the second object on the left
802+
* @param <C> the type of the third object on the left
803+
* @param <D> the type of the fourth object on the left
804+
* @param <E> the type of the object on the right
805+
* @param <Property_> the type of the collection elements
806+
* @param leftMapping mapping function to apply to (A,B,C,D)
807+
* @param rightMapping mapping function to apply to E
808+
*/
809+
public static <A, B, C, D, E, Property_> @NonNull PentaJoiner<A, B, C, D, E> containing(
810+
@NonNull QuadFunction<A, B, C, D, SequencedCollection<Property_>> leftMapping,
811+
@NonNull Function<E, Property_> rightMapping) {
812+
return new DefaultPentaJoiner<>(leftMapping, JoinerType.CONTAINING, rightMapping);
813+
}
814+
815+
/**
816+
* As defined by {@link #containedIn(Function, Function)}.
817+
*
818+
* @param <A> the type of the first object on the left
819+
* @param <B> the type of the second object on the left
820+
* @param <C> the type of the third object on the left
821+
* @param <D> the type of the fourth object on the left
822+
* @param <E> the type of the object on the right
823+
* @param <Property_> the type of the collection elements
824+
* @param leftMapping mapping function to apply to (A,B,C,D)
825+
* @param rightMapping mapping function to apply to E
826+
*/
827+
public static <A, B, C, D, E, Property_> @NonNull PentaJoiner<A, B, C, D, E> containedIn(
828+
@NonNull QuadFunction<A, B, C, D, Property_> leftMapping,
829+
@NonNull Function<E, SequencedCollection<Property_>> rightMapping) {
830+
return new DefaultPentaJoiner<>(leftMapping, JoinerType.CONTAINED_IN, rightMapping);
612831
}
613832

833+
/**
834+
* As defined by {@link #containingAnyOf(Function, Function)}.
835+
*
836+
* @param <A> the type of the first object on the left
837+
* @param <B> the type of the second object on the left
838+
* @param <C> the type of the third object on the left
839+
* @param <D> the type of the fourth object on the left
840+
* @param <E> the type of the object on the right
841+
* @param <Property_> the type of the collection elements
842+
* @param leftMapping mapping function to apply to (A,B,C,D)
843+
* @param rightMapping mapping function to apply to E
844+
*/
845+
public static <A, B, C, D, E, Property_> @NonNull PentaJoiner<A, B, C, D, E> containingAnyOf(
846+
@NonNull QuadFunction<A, B, C, D, SequencedCollection<Property_>> leftMapping,
847+
@NonNull Function<E, SequencedCollection<Property_>> rightMapping) {
848+
return new DefaultPentaJoiner<>(leftMapping, JoinerType.CONTAINING_ANY_OF, rightMapping);
849+
}
850+
851+
private Joiners() {
852+
}
614853
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
package ai.timefold.solver.core.impl.bavet.common.index;
22

3-
import java.util.Collection;
43
import java.util.Collections;
54
import java.util.HashMap;
65
import java.util.Iterator;
76
import java.util.Map;
87
import java.util.NoSuchElementException;
98
import java.util.Objects;
9+
import java.util.SequencedCollection;
1010
import java.util.function.Function;
1111
import java.util.function.Predicate;
1212
import java.util.function.Supplier;
1313
import java.util.random.RandomGenerator;
1414

15-
import ai.timefold.solver.core.impl.score.stream.UnfinishedJoiners;
15+
import ai.timefold.solver.core.api.score.stream.Joiners;
1616
import ai.timefold.solver.core.impl.util.ListEntry;
1717

1818
import org.jspecify.annotations.NullMarked;
1919
import org.jspecify.annotations.Nullable;
2020

2121
/**
22-
* As defined by {@link UnfinishedJoiners#containedIn(Function, Function)}
22+
* As defined by {@link Joiners#containedIn(Function, Function)}
2323
*/
2424
@NullMarked
25-
final class ContainedInIndexer<T, Key_, KeyCollection_ extends Collection<Key_>> implements Indexer<T> {
25+
final class ContainedInIndexer<T, Key_, KeyCollection_ extends SequencedCollection<Key_>> implements Indexer<T> {
2626

2727
private final KeyUnpacker<Key_> modifyKeyUnpacker;
2828
private final KeyUnpacker<KeyCollection_> queryKeyUnpacker;

0 commit comments

Comments
 (0)