solverforge_scoring/stream/joiner/overlapping.rs
1//! Overlapping joiner for interval overlap detection.
2
3use std::marker::PhantomData;
4
5use super::Joiner;
6
7/// Creates a joiner that matches when two intervals overlap.
8///
9/// Two intervals [start_a, end_a) and [start_b, end_b) overlap if:
10/// - start_a < end_b AND start_b < end_a
11///
12/// This uses half-open intervals: the start is inclusive, the end is exclusive.
13///
14/// # Example
15///
16/// ```
17/// use solverforge_scoring::stream::joiner::{Joiner, overlapping};
18///
19/// #[derive(Clone)]
20/// struct Shift { start: i64, end: i64 }
21///
22/// let overlap = overlapping(
23/// |s: &Shift| s.start,
24/// |s: &Shift| s.end,
25/// |s: &Shift| s.start,
26/// |s: &Shift| s.end
27/// );
28///
29/// // Overlapping shifts: [0, 10) and [5, 15) overlap at [5, 10)
30/// assert!(overlap.matches(
31/// &Shift { start: 0, end: 10 },
32/// &Shift { start: 5, end: 15 }
33/// ));
34///
35/// // Non-overlapping shifts: [0, 10) and [10, 20) touch but don't overlap
36/// assert!(!overlap.matches(
37/// &Shift { start: 0, end: 10 },
38/// &Shift { start: 10, end: 20 }
39/// ));
40///
41/// // Non-overlapping shifts: [0, 5) and [10, 15) are disjoint
42/// assert!(!overlap.matches(
43/// &Shift { start: 0, end: 5 },
44/// &Shift { start: 10, end: 15 }
45/// ));
46/// ```
47pub fn overlapping<A, B, T, Fsa, Fea, Fsb, Feb>(
48 start_a: Fsa,
49 end_a: Fea,
50 start_b: Fsb,
51 end_b: Feb,
52) -> OverlappingJoiner<Fsa, Fea, Fsb, Feb, T>
53where
54 T: Ord,
55 Fsa: Fn(&A) -> T + Send + Sync,
56 Fea: Fn(&A) -> T + Send + Sync,
57 Fsb: Fn(&B) -> T + Send + Sync,
58 Feb: Fn(&B) -> T + Send + Sync,
59{
60 OverlappingJoiner {
61 start_a,
62 end_a,
63 start_b,
64 end_b,
65 _phantom: PhantomData,
66 }
67}
68
69/// A joiner that matches when two intervals overlap.
70///
71/// Created by the [`overlapping()`] function.
72pub struct OverlappingJoiner<Fsa, Fea, Fsb, Feb, T> {
73 start_a: Fsa,
74 end_a: Fea,
75 start_b: Fsb,
76 end_b: Feb,
77 _phantom: PhantomData<fn() -> T>,
78}
79
80impl<A, B, T, Fsa, Fea, Fsb, Feb> Joiner<A, B> for OverlappingJoiner<Fsa, Fea, Fsb, Feb, T>
81where
82 T: Ord,
83 Fsa: Fn(&A) -> T + Send + Sync,
84 Fea: Fn(&A) -> T + Send + Sync,
85 Fsb: Fn(&B) -> T + Send + Sync,
86 Feb: Fn(&B) -> T + Send + Sync,
87{
88 #[inline]
89 fn matches(&self, a: &A, b: &B) -> bool {
90 let start_a = (self.start_a)(a);
91 let end_a = (self.end_a)(a);
92 let start_b = (self.start_b)(b);
93 let end_b = (self.end_b)(b);
94
95 // Half-open interval overlap: [start_a, end_a) ∩ [start_b, end_b) ≠ ∅
96 // iff start_a < end_b AND start_b < end_a
97 start_a < end_b && start_b < end_a
98 }
99}