Skip to main content

solverforge_scoring/stream/joiner/
equal.rs

1// Equal joiner for matching on property equality.
2
3use std::marker::PhantomData;
4
5use super::Joiner;
6
7/* Creates a joiner that matches when a property is equal on both sides.
8
9This is the primary joiner for self-joins where you're matching
10entities from the same collection on a shared property.
11
12# Example
13
14```
15use solverforge_scoring::stream::joiner::{Joiner, equal};
16
17#[derive(Clone)]
18struct Shift { employee_id: Option<usize>, start: i64 }
19
20// Match shifts with the same employee (self-join)
21let same_employee = equal(|s: &Shift| s.employee_id);
22
23let a = Shift { employee_id: Some(5), start: 0 };
24let b = Shift { employee_id: Some(5), start: 8 };
25let c = Shift { employee_id: Some(3), start: 16 };
26
27assert!(same_employee.matches(&a, &b));
28assert!(!same_employee.matches(&a, &c));
29```
30*/
31pub fn equal<A, T, F>(key: F) -> EqualJoiner<F, F, T>
32where
33    T: PartialEq,
34    F: Fn(&A) -> T + Clone + Send + Sync,
35{
36    EqualJoiner {
37        left: key.clone(),
38        right: key,
39        _phantom: PhantomData,
40    }
41}
42
43/* Creates a joiner that matches when extracted values are equal.
44
45Use this for cross-joins between different entity types.
46
47# Example
48
49```
50use solverforge_scoring::stream::joiner::{Joiner, equal_bi};
51
52#[derive(Clone)]
53struct Employee { id: usize, department: String }
54#[derive(Clone)]
55struct Task { assigned_to: usize, name: String }
56
57// Match employees to their assigned tasks
58let by_id = equal_bi(
59|e: &Employee| e.id,
60|t: &Task| t.assigned_to
61);
62
63let emp = Employee { id: 5, department: "Engineering".into() };
64let task1 = Task { assigned_to: 5, name: "Review".into() };
65let task2 = Task { assigned_to: 3, name: "Test".into() };
66
67assert!(by_id.matches(&emp, &task1));
68assert!(!by_id.matches(&emp, &task2));
69```
70*/
71pub fn equal_bi<A, B, T, Fa, Fb>(left: Fa, right: Fb) -> EqualJoiner<Fa, Fb, T>
72where
73    T: PartialEq,
74    Fa: Fn(&A) -> T + Send + Sync,
75    Fb: Fn(&B) -> T + Send + Sync,
76{
77    EqualJoiner {
78        left,
79        right,
80        _phantom: PhantomData,
81    }
82}
83
84/* A joiner that matches when extracted values are equal.
85
86Created by the [`equal()`] or [`equal_bi()`] functions.
87*/
88pub struct EqualJoiner<Fa, Fb, T> {
89    left: Fa,
90    right: Fb,
91    _phantom: PhantomData<fn() -> T>,
92}
93
94impl<Fa, Fb, T> EqualJoiner<Fa, Fb, T> {
95    // Extracts the join key from an A entity.
96    #[inline]
97    pub fn key_a<A>(&self, a: &A) -> T
98    where
99        Fa: Fn(&A) -> T,
100    {
101        (self.left)(a)
102    }
103
104    // Extracts the join key from a B entity.
105    #[inline]
106    pub fn key_b<B>(&self, b: &B) -> T
107    where
108        Fb: Fn(&B) -> T,
109    {
110        (self.right)(b)
111    }
112
113    /* Consumes the joiner and returns the key extractors.
114
115    This is useful for zero-erasure constraint creation where
116    the key extractors need to be stored as concrete types.
117    */
118    #[inline]
119    pub fn into_keys(self) -> (Fa, Fb) {
120        (self.left, self.right)
121    }
122
123    // Returns references to the key extractors.
124    #[inline]
125    pub fn key_extractors(&self) -> (&Fa, &Fb) {
126        (&self.left, &self.right)
127    }
128}
129
130impl<A, B, T, Fa, Fb> Joiner<A, B> for EqualJoiner<Fa, Fb, T>
131where
132    T: PartialEq,
133    Fa: Fn(&A) -> T + Send + Sync,
134    Fb: Fn(&B) -> T + Send + Sync,
135{
136    #[inline]
137    fn matches(&self, a: &A, b: &B) -> bool {
138        (self.left)(a) == (self.right)(b)
139    }
140}