Skip to main content

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}