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