Skip to content

Commit db14e7f

Browse files
fix: score corruption caused by duplicate, implement randomIterator
- 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 c12d976 commit db14e7f

File tree

9 files changed

+416
-319
lines changed

9 files changed

+416
-319
lines changed

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

Lines changed: 239 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.Collection;
34
import java.util.function.BiFunction;
45
import java.util.function.BiPredicate;
56
import java.util.function.Function;
@@ -256,6 +257,92 @@ 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(@NonNull Function<A, Collection<Property_>> leftMapping,
280+
@NonNull Function<B, Property_> rightMapping) {
281+
return new DefaultBiJoiner<>(leftMapping, JoinerType.CONTAINING, rightMapping);
282+
}
283+
284+
/**
285+
* Joins every A and B where a value of property on A is contained in the collection of properties on B.
286+
* <p>
287+
* For example:
288+
* <ul>
289+
* <li>{@code "A"} contained in {@code ["A", "B"]} is {@code true}</li>
290+
* <li>{@code "A"} contained in {@code ["A"]} is {@code true}</li>
291+
* <li>{@code "A"} contained in {@code ["X", "Y"]} is {@code false}</li>
292+
* <li>{@code "A"} contained in {@code []} is {@code false}</li>
293+
* <li>{@code null} contained in {@code ["A", "B"]} is {@code false}</li>
294+
* <li>{@code null} contained in {@code []} is {@code false}</li>
295+
* </ul>
296+
*
297+
* @param leftMapping mapping function to apply to A
298+
* @param rightMapping mapping function to apply to B
299+
* @param <A> the type of object on the left
300+
* @param <B> the type of object on the right
301+
* @param <Property_> the type of the property to compare
302+
*/
303+
public static <A, B, Property_> @NonNull BiJoiner<A, B> containedIn(@NonNull Function<A, Property_> leftMapping,
304+
@NonNull Function<B, Collection<Property_>> rightMapping) {
305+
return new DefaultBiJoiner<>(leftMapping, JoinerType.CONTAINED_IN, rightMapping);
306+
}
307+
308+
/**
309+
* As defined by {@link #containingAnyOf(Function, Function)} with both arguments using the same mapping.
310+
*
311+
* @param mapping mapping function to apply to both A and B
312+
* @param <A> the type of both objects
313+
* @param <Property_> the type of the property to compare
314+
*/
315+
public static <A, Property_> @NonNull BiJoiner<A, A> containingAnyOf(
316+
@NonNull Function<A, Collection<Property_>> mapping) {
317+
return containingAnyOf(mapping, mapping);
318+
}
319+
320+
/**
321+
* Joins every A and B where a collection of properties on A overlaps with a collection of properties on B.
322+
* <p>
323+
* For example:
324+
* <ul>
325+
* <li>{@code ["A", "B"]} intersecting {@code ["A", "B"]} is {@code true}</li>
326+
* <li>{@code ["A", "B"]} intersecting {@code ["A"]} is {@code true}</li>
327+
* <li>{@code ["A"]} intersecting {@code ["A", "B"]} is {@code true}</li>
328+
* <li>{@code ["A", "B"]} intersecting {@code ["X", "Y"]} is {@code false}</li>
329+
* <li>{@code ["A", "B"]} intersecting {@code []} is {@code false}</li>
330+
* <li>{@code []} intersecting {@code ["A", "B"]} is {@code false}</li>
331+
* <li>{@code []} intersecting {@code []} is {@code false}</li>
332+
* </ul>
333+
*
334+
* @param leftMapping mapping function to apply to A
335+
* @param rightMapping mapping function to apply to B
336+
* @param <A> the type of object on the left
337+
* @param <B> the type of object on the right
338+
* @param <Property_> the type of the property to compare
339+
*/
340+
public static <A, B, Property_> @NonNull BiJoiner<A, B> containingAnyOf(
341+
@NonNull Function<A, Collection<Property_>> leftMapping,
342+
@NonNull Function<B, Collection<Property_>> rightMapping) {
343+
return new DefaultBiJoiner<>(leftMapping, JoinerType.CONTAINING_ANY_OF, rightMapping);
344+
}
345+
259346
// ************************************************************************
260347
// TriJoiner
261348
// ************************************************************************
@@ -366,6 +453,53 @@ public final class Joiners {
366453
.and(Joiners.greaterThan(leftEndMapping, rightStartMapping));
367454
}
368455

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

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

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

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

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
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
2525
final class ContainedInIndexer<T, Key_, KeyCollection_ extends Collection<Key_>> implements Indexer<T> {

0 commit comments

Comments
 (0)