use core::num::NonZeroUsize;
use sashite_sanki_engine::domain::time::Timestamp;
pub const CANDIDATE_CAP: NonZeroUsize = match NonZeroUsize::new(8) {
Some(cap) => cap,
None => NonZeroUsize::MIN,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Candidate<Id> {
pub id: Id,
pub created_at: Timestamp,
}
impl<Id> Candidate<Id> {
#[inline]
#[must_use]
pub fn is_anterior(&self, boundary: Timestamp) -> bool {
self.created_at < boundary
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Selection<'a, Id> {
Applied(&'a Candidate<Id>),
Unfilled,
}
impl<Id> Selection<'_, Id> {
#[inline]
#[must_use]
pub const fn selected(&self) -> Option<&Candidate<Id>> {
match self {
Self::Applied(candidate) => Some(candidate),
Self::Unfilled => None,
}
}
}
#[must_use]
pub fn select_candidate<'a, Id: Ord>(
boundary: Timestamp,
candidates: &'a [Candidate<Id>],
cap: NonZeroUsize,
mut is_legal: impl FnMut(&Id) -> bool,
) -> Selection<'a, Id> {
let mut anterior: Vec<&'a Candidate<Id>> = candidates
.iter()
.filter(|candidate| candidate.is_anterior(boundary))
.collect();
anterior.sort_by(|a, b| {
b.created_at
.cmp(&a.created_at)
.then_with(|| b.id.cmp(&a.id))
});
if let Some(chosen) = anterior
.into_iter()
.take(cap.get())
.find(|candidate| is_legal(&candidate.id))
{
return Selection::Applied(chosen);
}
let mut informed: Vec<&'a Candidate<Id>> = candidates
.iter()
.filter(|candidate| !candidate.is_anterior(boundary))
.collect();
informed.sort_by(|a, b| {
a.created_at
.cmp(&b.created_at)
.then_with(|| a.id.cmp(&b.id))
});
if let Some(chosen) = informed
.into_iter()
.take(cap.get())
.find(|candidate| is_legal(&candidate.id))
{
return Selection::Applied(chosen);
}
Selection::Unfilled
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)]
use super::{select_candidate, Candidate, Selection, CANDIDATE_CAP};
use core::num::NonZeroUsize;
use sashite_sanki_engine::domain::time::Timestamp;
fn ts(secs: i64) -> Timestamp {
Timestamp::from_unix(secs)
}
fn cap(k: usize) -> NonZeroUsize {
NonZeroUsize::new(k).expect("a cap is at least 1")
}
fn cand(id: &'static str, created_at: i64) -> Candidate<&'static str> {
Candidate {
id,
created_at: ts(created_at),
}
}
fn probe(legal: &'static [&'static str]) -> impl FnMut(&&'static str) -> bool {
move |id| legal.contains(id)
}
fn recording_probe<'a>(
legal: &'static [&'static str],
seen: &'a mut Vec<&'static str>,
) -> impl FnMut(&&'static str) -> bool + 'a {
move |id| {
seen.push(*id);
legal.contains(id)
}
}
fn permutations<T: Clone>(items: &[T]) -> Vec<Vec<T>> {
if items.len() <= 1 {
return vec![items.to_vec()];
}
let mut orders = Vec::new();
for index in 0..items.len() {
let mut rest = items.to_vec();
let head = rest.remove(index);
for mut order in permutations(&rest) {
order.insert(0, head.clone());
orders.push(order);
}
}
orders
}
#[test]
fn single_informed_legal_applied() {
let cs = [cand("a1", 120)];
assert_eq!(
select_candidate(ts(100), &cs, CANDIDATE_CAP, probe(&["a1"])),
Selection::Applied(&cs[0])
);
}
#[test]
fn single_illegal_unfilled() {
let cs = [cand("a1", 120)];
assert_eq!(
select_candidate(ts(100), &cs, CANDIDATE_CAP, probe(&[])),
Selection::Unfilled
);
}
#[test]
fn anterior_latest_legal_wins() {
let cs = [cand("a1", 20), cand("a2", 60)];
assert_eq!(
select_candidate(ts(100), &cs, CANDIDATE_CAP, probe(&["a1", "a2"])),
Selection::Applied(&cs[1])
);
}
#[test]
fn anterior_skips_newest_illegal_to_next_legal() {
let cs = [cand("a1", 20), cand("a2", 60)];
assert_eq!(
select_candidate(ts(100), &cs, CANDIDATE_CAP, probe(&["a1"])),
Selection::Applied(&cs[0])
);
}
#[test]
fn informed_earliest_legal_wins() {
let cs = [cand("a1", 10), cand("a2", 20)];
assert_eq!(
select_candidate(ts(0), &cs, CANDIDATE_CAP, probe(&["a1", "a2"])),
Selection::Applied(&cs[0])
);
}
#[test]
fn informed_skips_earliest_illegal_to_next_legal() {
let cs = [cand("a1", 10), cand("a2", 20)];
assert_eq!(
select_candidate(ts(0), &cs, CANDIDATE_CAP, probe(&["a2"])),
Selection::Applied(&cs[1])
);
}
#[test]
fn legal_anterior_preferred_over_informed() {
let cs = [cand("p1", 50), cand("L1", 150)];
assert_eq!(
select_candidate(ts(100), &cs, CANDIDATE_CAP, probe(&["p1", "L1"])),
Selection::Applied(&cs[0])
);
}
#[test]
fn fallthrough_to_informed_when_no_legal_anterior() {
let cs = [cand("p1", 50), cand("L1", 150)];
assert_eq!(
select_candidate(ts(100), &cs, CANDIDATE_CAP, probe(&["L1"])),
Selection::Applied(&cs[1])
);
}
#[test]
fn all_illegal_both_windows_unfilled() {
let cs = [cand("p1", 50), cand("L1", 150)];
assert_eq!(
select_candidate(ts(100), &cs, CANDIDATE_CAP, probe(&[])),
Selection::Unfilled
);
}
#[test]
fn anterior_tie_breaks_by_largest_id_first() {
let cs = [cand("b1", 60), cand("b2", 60)];
assert_eq!(
select_candidate(ts(100), &cs, CANDIDATE_CAP, probe(&["b1", "b2"])),
Selection::Applied(&cs[1])
);
}
#[test]
fn informed_tie_breaks_by_smallest_id_first() {
let cs = [cand("b1", 20), cand("b2", 20)];
assert_eq!(
select_candidate(ts(0), &cs, CANDIDATE_CAP, probe(&["b1", "b2"])),
Selection::Applied(&cs[0])
);
}
#[test]
fn cap_anterior_most_recent_buries_older_legal() {
let cs = [cand("a1", 10), cand("a2", 800), cand("a3", 900)];
assert_eq!(
select_candidate(ts(1000), &cs, cap(2), probe(&["a1"])),
Selection::Unfilled
);
assert_eq!(
select_candidate(ts(1000), &cs, cap(3), probe(&["a1"])),
Selection::Applied(&cs[0])
);
}
#[test]
fn cap_informed_earliest_buries_later_legal() {
let cs = [cand("a1", 10), cand("a2", 20), cand("a3", 30)];
assert_eq!(
select_candidate(ts(0), &cs, cap(2), probe(&["a3"])),
Selection::Unfilled
);
assert_eq!(
select_candidate(ts(0), &cs, cap(3), probe(&["a3"])),
Selection::Applied(&cs[2])
);
}
#[test]
fn legality_probes_are_bounded_by_the_cap() {
let mut cs: Vec<Candidate<usize>> = Vec::new();
for i in 0..20 {
cs.push(Candidate {
id: i,
created_at: ts(i64::try_from(i).expect("small")),
}); cs.push(Candidate {
id: 100 + i,
created_at: ts(200 + i64::try_from(i).expect("small")),
}); }
let mut probed: Vec<usize> = Vec::new();
let selection = select_candidate(ts(100), &cs, cap(2), |id| {
probed.push(*id);
false });
assert_eq!(selection, Selection::Unfilled);
assert_eq!(probed, [19, 18, 100, 101]);
let mut probed: Vec<usize> = Vec::new();
let selection = select_candidate(ts(100), &cs, cap(2), |id| {
probed.push(*id);
*id == 19
});
assert_eq!(selection.selected().map(|chosen| chosen.id), Some(19));
assert_eq!(probed, [19]);
}
#[test]
fn first_slot_boundary_t0_is_informed() {
let legal = [cand("a1", 5)];
assert_eq!(
select_candidate(ts(0), &legal, CANDIDATE_CAP, probe(&["a1"])),
Selection::Applied(&legal[0])
);
let illegal = [cand("a1", 5)];
assert_eq!(
select_candidate(ts(0), &illegal, CANDIDATE_CAP, probe(&[])),
Selection::Unfilled
);
let at_t0 = [cand("a0", 0), cand("a1", 5)];
assert_eq!(
select_candidate(ts(0), &at_t0, CANDIDATE_CAP, probe(&["a0", "a1"]))
.selected()
.map(|chosen| chosen.id),
Some("a0")
);
}
#[test]
fn boundary_is_exclusive_below_and_inclusive_at() {
let cs = [cand("before", 99), cand("at_t", 100)];
assert_eq!(
select_candidate(ts(100), &cs, CANDIDATE_CAP, probe(&["before", "at_t"]))
.selected()
.map(|chosen| chosen.id),
Some("before")
);
assert_eq!(
select_candidate(ts(100), &cs, CANDIDATE_CAP, probe(&["at_t"]))
.selected()
.map(|chosen| chosen.id),
Some("at_t")
);
}
#[test]
fn anterior_tie_skips_the_larger_id_when_illegal() {
let cs = [cand("b1", 60), cand("b2", 60)];
let mut probed = Vec::new();
let chosen = select_candidate(
ts(100),
&cs,
CANDIDATE_CAP,
recording_probe(&["b1"], &mut probed),
)
.selected()
.map(|chosen| chosen.id);
assert_eq!(chosen, Some("b1"));
assert_eq!(probed, ["b2", "b1"]);
}
#[test]
fn anterior_cap_admits_exactly_k_and_no_more() {
let four = [
cand("a1", 10),
cand("a2", 20),
cand("a3", 30),
cand("a4", 40),
];
let mut probed = Vec::new();
assert_eq!(
select_candidate(
ts(100),
&four,
cap(4),
recording_probe(&["a1"], &mut probed)
)
.selected()
.map(|chosen| chosen.id),
Some("a1")
);
assert_eq!(probed, ["a4", "a3", "a2", "a1"]);
let five = [
cand("a1", 10),
cand("a2", 20),
cand("a3", 30),
cand("a4", 40),
cand("a5", 50),
];
let mut probed = Vec::new();
assert_eq!(
select_candidate(
ts(100),
&five,
cap(4),
recording_probe(&["a1"], &mut probed)
),
Selection::Unfilled
);
assert_eq!(probed, ["a5", "a4", "a3", "a2"]);
}
#[test]
fn informed_cap_admits_exactly_k_and_no_more() {
let four = [
cand("a1", 10),
cand("a2", 20),
cand("a3", 30),
cand("a4", 40),
];
let mut probed = Vec::new();
assert_eq!(
select_candidate(ts(0), &four, cap(4), recording_probe(&["a4"], &mut probed))
.selected()
.map(|chosen| chosen.id),
Some("a4")
);
assert_eq!(probed, ["a1", "a2", "a3", "a4"]);
let five = [
cand("a1", 10),
cand("a2", 20),
cand("a3", 30),
cand("a4", 40),
cand("a5", 50),
];
let mut probed = Vec::new();
assert_eq!(
select_candidate(ts(0), &five, cap(4), recording_probe(&["a5"], &mut probed)),
Selection::Unfilled
);
assert_eq!(probed, ["a1", "a2", "a3", "a4"]);
}
#[test]
fn empty_input_and_the_smallest_cap_are_handled_without_waste() {
let none: [Candidate<&'static str>; 0] = [];
let mut probed = Vec::new();
assert_eq!(
select_candidate(
ts(100),
&none,
CANDIDATE_CAP,
recording_probe(&[], &mut probed)
),
Selection::Unfilled
);
assert!(probed.is_empty());
let cs = [
cand("p1", 40),
cand("p2", 50),
cand("L1", 150),
cand("L2", 160),
];
let mut probed = Vec::new();
assert_eq!(
select_candidate(
ts(100),
&cs,
cap(1),
recording_probe(&["p1", "L2"], &mut probed)
),
Selection::Unfilled
);
assert_eq!(probed, ["p2", "L1"]);
let mut probed = Vec::new();
assert_eq!(
select_candidate(ts(100), &cs, cap(1), recording_probe(&["L1"], &mut probed))
.selected()
.map(|chosen| chosen.id),
Some("L1")
);
assert_eq!(probed, ["p2", "L1"]);
}
#[test]
fn the_window_split_has_one_definition() {
let boundary = ts(100);
assert!(cand("before", 99).is_anterior(boundary));
assert!(!cand("at", 100).is_anterior(boundary));
assert!(!cand("after", 101).is_anterior(boundary));
let cs = [cand("at", 100), cand("after", 101)];
assert_eq!(
select_candidate(boundary, &cs, CANDIDATE_CAP, probe(&["at", "after"]))
.selected()
.map(|chosen| chosen.id),
Some("at")
);
}
#[test]
fn extreme_timestamps_partition_without_saturating() {
let floor = [cand("m", i64::MIN)];
assert_eq!(
select_candidate(ts(i64::MIN), &floor, CANDIDATE_CAP, probe(&["m"]))
.selected()
.map(|chosen| chosen.id),
Some("m")
);
let ceiling = [cand("lo", i64::MIN), cand("hi", i64::MAX - 1)];
assert_eq!(
select_candidate(ts(i64::MAX), &ceiling, CANDIDATE_CAP, probe(&["lo", "hi"]))
.selected()
.map(|chosen| chosen.id),
Some("hi")
);
let negative = [cand("n1", -100), cand("n2", -50)];
assert_eq!(
select_candidate(ts(0), &negative, CANDIDATE_CAP, probe(&["n1", "n2"]))
.selected()
.map(|chosen| chosen.id),
Some("n2")
);
}
#[test]
fn selection_is_independent_of_the_input_order() {
let base = [
cand("p1", 40),
cand("p2", 40),
cand("p3", 90),
cand("L1", 100),
cand("L2", 100),
];
for order in permutations(&base) {
let mut probed = Vec::new();
let chosen = select_candidate(
ts(100),
&order,
cap(2),
recording_probe(&["p1", "L1"], &mut probed),
)
.selected()
.map(|chosen| chosen.id);
assert_eq!(chosen, Some("L1"), "input order {order:?}");
assert_eq!(probed, ["p3", "p2", "L1"], "input order {order:?}");
}
for order in permutations(&base) {
assert_eq!(
select_candidate(ts(100), &order, CANDIDATE_CAP, probe(&["p1", "L1"]))
.selected()
.map(|chosen| chosen.id),
Some("p1"),
"input order {order:?}"
);
}
}
}