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}