Skip to main content

solverforge_scoring/stream/
join_target.rs

1/* JoinTarget trait for single `.join()` dispatch on UniConstraintStream.
2
3Three impls cover all join patterns:
41. `EqualJoiner<KA, KA, K>` — self-join, returns `BiConstraintStream`
52. `(EB, EqualJoiner<KA, KB, K>)` — keyed cross-join, returns `Bi`
63. `(UniConstraintStream<...>, P)` — predicate cross-join, returns `Bi`
7*/
8
9use std::hash::Hash;
10
11use solverforge_core::score::Score;
12
13use super::bi_stream::BiConstraintStream;
14use super::collection_extract::CollectionExtract;
15use super::cross_bi_stream::Bi;
16use super::filter::{PairFilter, UniBiFilter, UniFilter, UniLeftBiFilter};
17use super::joiner::{EqualJoiner, Symmetric};
18use super::key_extract::EntityKeyAdapter;
19use super::UniConstraintStream;
20
21/* Trait for single `.join()` dispatch.
22
23`E` is the extractor type of the left stream.
24`F` is the filter type of the left stream.
25Implementors consume `self` and receive the left stream's extractor and filter,
26producing the appropriate cross-stream type.
27*/
28pub trait JoinTarget<S, A, E, F, Sc: Score> {
29    // The resulting constraint stream type.
30    type Output;
31
32    // Applies the join, consuming both the target and the left stream's components.
33    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output;
34}
35
36// Self-join: `.join(equal(|a: &A| a.key))` — pairs same-collection entities.
37impl<S, A, E, F, K, KA, Sc> JoinTarget<S, A, E, F, Sc> for EqualJoiner<KA, KA, K, Symmetric>
38where
39    S: Send + Sync + 'static,
40    A: Clone + Hash + PartialEq + Send + Sync + 'static,
41    E: CollectionExtract<S, Item = A>,
42    F: UniFilter<S, A>,
43    K: Eq + Hash + Clone + Send + Sync,
44    KA: Fn(&A) -> K + Send + Sync,
45    Sc: Score + 'static,
46{
47    type Output = BiConstraintStream<S, A, K, E, EntityKeyAdapter<KA>, UniBiFilter<F, A>, Sc>;
48
49    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
50        let (key_fn, _) = self.into_keys();
51        let key_extractor = EntityKeyAdapter::new(key_fn);
52        let bi_filter = UniBiFilter::new(filter_a);
53        BiConstraintStream::new_self_join_with_filter(extractor_a, key_extractor, bi_filter)
54    }
55}
56
57// Keyed cross-join: `.join((extractor_b, equal_bi(ka, kb)))` — pairs two collections by key.
58impl<S, A, B, E, F, EB, K, KA, KB, Mode, Sc> JoinTarget<S, A, E, F, Sc>
59    for (EB, EqualJoiner<KA, KB, K, Mode>)
60where
61    S: Send + Sync + 'static,
62    A: Clone + Send + Sync + 'static,
63    B: Clone + Send + Sync + 'static,
64    E: CollectionExtract<S, Item = A>,
65    F: UniFilter<S, A>,
66    EB: CollectionExtract<S, Item = B>,
67    K: Eq + Hash + Clone + Send + Sync,
68    KA: Fn(&A) -> K + Send + Sync,
69    KB: Fn(&B) -> K + Send + Sync,
70    Sc: Score + 'static,
71{
72    type Output = Bi<S, A, B, K, E, EB, KA, KB, UniLeftBiFilter<F, B>, Sc>;
73
74    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
75        let (extractor_b, joiner) = self;
76        let (key_a, key_b) = joiner.into_keys();
77        let bi_filter = UniLeftBiFilter::new(filter_a);
78        Bi::new_with_filter(extractor_a, extractor_b, key_a, key_b, bi_filter)
79    }
80}
81
82// Predicate cross-join: `.join((other_stream, |a, b| predicate))` — O(n*m) nested loop.
83impl<S, A, B, E, F, EB, FB, P, Sc> JoinTarget<S, A, E, F, Sc>
84    for (UniConstraintStream<S, B, EB, FB, Sc>, P)
85where
86    S: Send + Sync + 'static,
87    A: Clone + Send + Sync + 'static,
88    B: Clone + Send + Sync + 'static,
89    E: CollectionExtract<S, Item = A>,
90    F: UniFilter<S, A>,
91    EB: CollectionExtract<S, Item = B>,
92    FB: UniFilter<S, B>,
93    P: Fn(&A, &B) -> bool + Send + Sync + 'static,
94    Sc: Score + 'static,
95{
96    type Output = Bi<S, A, B, u8, E, EB, fn(&A) -> u8, fn(&B) -> u8, PairFilter<F, FB, P>, Sc>;
97
98    fn apply(self, extractor_a: E, filter_a: F) -> Self::Output {
99        let (other_stream, predicate) = self;
100        let (extractor_b, filter_b) = other_stream.into_parts();
101        let combined_filter = PairFilter::new(filter_a, filter_b, predicate);
102        Bi::new_with_filter(
103            extractor_a,
104            extractor_b,
105            (|_: &A| 0u8) as fn(&A) -> u8,
106            (|_: &B| 0u8) as fn(&B) -> u8,
107            combined_filter,
108        )
109    }
110}