Skip to main content

solverforge_scoring/stream/
join_target.rs

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