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}