use std::cmp::Ordering;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct TemplateCoordKey {
pub tid1: i32,
pub tid2: i32,
pub pos1: i64,
pub pos2: i64,
pub neg1: bool,
pub neg2: bool,
pub mid: String,
pub name: String,
pub is_upper_of_pair: bool,
}
impl TemplateCoordKey {
pub fn for_f1r2_pair(
tid: i32,
pos1: usize,
insert_size: usize,
mid: String,
name: String,
) -> Self {
let pos2 = (pos1 + insert_size).saturating_sub(1);
Self {
tid1: tid,
tid2: tid,
pos1: pos1 as i64,
pos2: pos2 as i64,
neg1: false, neg2: true, mid,
name,
is_upper_of_pair: false, }
}
}
fn compare_mid(mid1: &str, mid2: &str) -> Ordering {
let mid1 = if mid1.len() >= 2 && mid1.as_bytes()[mid1.len() - 2] == b'/' {
&mid1[..mid1.len() - 2]
} else {
mid1
};
let mid2 = if mid2.len() >= 2 && mid2.as_bytes()[mid2.len() - 2] == b'/' {
&mid2[..mid2.len() - 2]
} else {
mid2
};
mid1.len().cmp(&mid2.len()).then_with(|| mid1.cmp(mid2))
}
impl Ord for TemplateCoordKey {
fn cmp(&self, other: &Self) -> Ordering {
self.tid1
.cmp(&other.tid1)
.then_with(|| self.tid2.cmp(&other.tid2))
.then_with(|| self.pos1.cmp(&other.pos1))
.then_with(|| self.pos2.cmp(&other.pos2))
.then_with(|| match (self.neg1, other.neg1) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => Ordering::Equal,
})
.then_with(|| match (self.neg2, other.neg2) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => Ordering::Equal,
})
.then_with(|| compare_mid(&self.mid, &other.mid))
.then_with(|| self.name.cmp(&other.name))
.then_with(|| self.is_upper_of_pair.cmp(&other.is_upper_of_pair))
}
}
impl PartialOrd for TemplateCoordKey {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(clippy::too_many_arguments)]
fn key(
tid1: i32,
tid2: i32,
pos1: i64,
pos2: i64,
neg1: bool,
neg2: bool,
mid: &str,
name: &str,
is_upper: bool,
) -> TemplateCoordKey {
TemplateCoordKey {
tid1,
tid2,
pos1,
pos2,
neg1,
neg2,
mid: mid.to_string(),
name: name.to_string(),
is_upper_of_pair: is_upper,
}
}
#[test]
fn test_f1r2_pair_key() {
let key =
TemplateCoordKey::for_f1r2_pair(0, 1000, 300, String::new(), "mol001".to_string());
assert_eq!(key.tid1, 0);
assert_eq!(key.tid2, 0);
assert_eq!(key.pos1, 1000);
assert_eq!(key.pos2, 1299); assert!(!key.neg1);
assert!(key.neg2);
assert!(!key.is_upper_of_pair);
}
#[test]
fn test_f1r2_pair_key_with_mi() {
let key =
TemplateCoordKey::for_f1r2_pair(1, 500, 250, "42/A".to_string(), "read1".to_string());
assert_eq!(key.tid1, 1);
assert_eq!(key.tid2, 1);
assert_eq!(key.pos1, 500);
assert_eq!(key.pos2, 749); assert_eq!(key.mid, "42/A");
assert_eq!(key.name, "read1");
}
#[test]
fn test_f1r2_pair_key_zero_insert() {
let key = TemplateCoordKey::for_f1r2_pair(0, 100, 1, String::new(), "x".to_string());
assert_eq!(key.pos1, 100);
assert_eq!(key.pos2, 100); }
#[test]
fn test_sort_by_tid1() {
let key1 = key(0, 0, 1000, 1100, false, true, "", "a", false);
let key2 = key(1, 0, 500, 600, false, true, "", "a", false);
assert!(key1 < key2);
}
#[test]
fn test_sort_by_tid2_when_tid1_equal() {
let key1 = key(0, 0, 1000, 1100, false, true, "", "a", false);
let key2 = key(0, 1, 1000, 1100, false, true, "", "a", false);
assert!(key1 < key2);
}
#[test]
fn test_sort_by_pos1() {
let key1 = TemplateCoordKey::for_f1r2_pair(0, 1000, 300, String::new(), "a".to_string());
let key2 = TemplateCoordKey::for_f1r2_pair(0, 2000, 300, String::new(), "b".to_string());
assert!(key1 < key2);
}
#[test]
fn test_sort_by_pos2_when_pos1_equal() {
let key1 = TemplateCoordKey::for_f1r2_pair(0, 1000, 200, String::new(), "a".to_string());
let key2 = TemplateCoordKey::for_f1r2_pair(0, 1000, 300, String::new(), "a".to_string());
assert!(key1 < key2);
}
#[test]
fn test_sort_by_pos1_negative_positions() {
let key1 = key(0, 0, -100, 0, false, true, "", "a", false);
let key2 = key(0, 0, 0, 100, false, true, "", "a", false);
assert!(key1 < key2);
}
#[test]
fn test_sort_by_neg1_reverse_before_forward() {
let key_rev = key(0, 0, 100, 200, true, false, "", "a", false);
let key_fwd = key(0, 0, 100, 200, false, false, "", "a", false);
assert!(
key_rev < key_fwd,
"reverse strand (neg1=true) should sort before forward (neg1=false)"
);
}
#[test]
fn test_sort_by_neg2_reverse_before_forward() {
let key_rev = key(0, 0, 100, 200, false, true, "", "a", false);
let key_fwd = key(0, 0, 100, 200, false, false, "", "a", false);
assert!(
key_rev < key_fwd,
"reverse strand (neg2=true) should sort before forward (neg2=false)"
);
}
#[test]
fn test_sort_neg1_takes_precedence_over_neg2() {
let key1 = key(0, 0, 100, 200, true, false, "", "a", false); let key2 = key(0, 0, 100, 200, false, true, "", "a", false); assert!(key1 < key2, "neg1=true should sort before neg1=false regardless of neg2");
}
#[test]
fn test_compare_mid_strips_suffix() {
assert_eq!(compare_mid("123/A", "123/B"), Ordering::Equal);
assert_eq!(compare_mid("123/A", "123"), Ordering::Equal);
assert_eq!(compare_mid("123/B", "123"), Ordering::Equal);
}
#[test]
fn test_compare_mid_strips_various_suffixes() {
assert_eq!(compare_mid("42/A", "42/B"), Ordering::Equal);
assert_eq!(compare_mid("42/X", "42/Y"), Ordering::Equal);
assert_eq!(compare_mid("42/1", "42/2"), Ordering::Equal);
}
#[test]
fn test_compare_mid_length_first() {
assert_eq!(compare_mid("1", "12"), Ordering::Less);
assert_eq!(compare_mid("99", "100"), Ordering::Less);
assert_eq!(compare_mid("9", "10"), Ordering::Less);
}
#[test]
fn test_compare_mid_lexicographic() {
assert_eq!(compare_mid("abc", "abd"), Ordering::Less);
assert_eq!(compare_mid("123", "124"), Ordering::Less);
assert_eq!(compare_mid("aaa", "aab"), Ordering::Less);
}
#[test]
fn test_compare_mid_empty_strings() {
assert_eq!(compare_mid("", ""), Ordering::Equal);
assert_eq!(compare_mid("", "1"), Ordering::Less);
assert_eq!(compare_mid("1", ""), Ordering::Greater);
}
#[test]
fn test_compare_mid_single_char() {
assert_eq!(compare_mid("1", "2"), Ordering::Less);
assert_eq!(compare_mid("a", "b"), Ordering::Less);
assert_eq!(compare_mid("0", "9"), Ordering::Less);
}
#[test]
fn test_compare_mid_with_suffix_vs_without() {
assert_eq!(compare_mid("1/A", "1"), Ordering::Equal);
assert_eq!(compare_mid("12/B", "1"), Ordering::Greater);
}
#[test]
fn test_compare_mid_numeric_ordering() {
let mids = ["1", "2", "9", "10", "11", "99", "100", "101"];
for i in 0..mids.len() - 1 {
assert_eq!(
compare_mid(mids[i], mids[i + 1]),
Ordering::Less,
"{} should sort before {}",
mids[i],
mids[i + 1]
);
}
}
#[test]
fn test_sort_by_mid() {
let mut key1 =
TemplateCoordKey::for_f1r2_pair(0, 1000, 300, "1".to_string(), "a".to_string());
let mut key2 =
TemplateCoordKey::for_f1r2_pair(0, 1000, 300, "12".to_string(), "a".to_string());
assert!(key1 < key2);
key1.mid = "12".to_string();
key2.mid = "13".to_string();
assert!(key1 < key2);
}
#[test]
fn test_sort_by_name_when_positions_equal() {
let key1 =
TemplateCoordKey::for_f1r2_pair(0, 1000, 300, String::new(), "mol001".to_string());
let key2 =
TemplateCoordKey::for_f1r2_pair(0, 1000, 300, String::new(), "mol002".to_string());
assert!(key1 < key2);
}
#[test]
fn test_sort_by_name_lexicographic() {
let key1 = key(0, 0, 100, 200, false, true, "1", "aaa", false);
let key2 = key(0, 0, 100, 200, false, true, "1", "aab", false);
assert!(key1 < key2);
}
#[test]
fn test_sort_by_name_empty_vs_nonempty() {
let key1 = key(0, 0, 100, 200, false, true, "", "", false);
let key2 = key(0, 0, 100, 200, false, true, "", "a", false);
assert!(key1 < key2);
}
#[test]
fn test_sort_by_is_upper_of_pair() {
let key_lower = key(0, 0, 100, 200, false, true, "1", "read", false);
let key_upper = key(0, 0, 100, 200, false, true, "1", "read", true);
assert!(key_lower < key_upper, "is_upper_of_pair=false should sort before true");
}
#[test]
fn test_sort_by_mi_then_name_fgbio_style() {
let mut keys = [
key(0, 0, 200, 209, false, true, "0/A", "ab0", false),
key(0, 0, 100, 109, false, true, "1/A", "ab1", false),
key(0, 0, 100, 109, false, true, "1/A", "ab2", false),
key(0, 0, 100, 109, false, true, "2/A", "ab3", false),
key(0, 0, 200, 209, true, false, "0/B", "ba0", false),
key(0, 0, 100, 109, true, false, "1/B", "ba1", false),
key(0, 0, 100, 109, true, false, "1/B", "ba2", false),
key(0, 0, 100, 109, true, false, "2/B", "ba3", false),
];
keys.sort();
let names: Vec<&str> = keys.iter().map(|k| k.name.as_str()).collect();
let expected = vec!["ba1", "ba2", "ba3", "ab1", "ab2", "ab3", "ba0", "ab0"];
assert_eq!(names, expected);
}
#[test]
fn test_position_takes_precedence() {
let key_pos100 = key(0, 0, 100, 200, false, true, "999", "zzz", false);
let key_pos200 = key(0, 0, 200, 300, true, false, "1", "aaa", false);
assert!(
key_pos100 < key_pos200,
"lower position should sort first regardless of other fields"
);
}
#[test]
fn test_tid_takes_precedence_over_position() {
let key_tid0 = key(0, 0, 99999, 100_000, false, true, "", "a", false);
let key_tid1 = key(1, 1, 1, 2, false, true, "", "a", false);
assert!(key_tid0 < key_tid1, "tid=0 should sort before tid=1 regardless of position");
}
#[test]
fn test_complete_sort_order_hierarchy() {
let baseline = key(0, 0, 100, 200, false, true, "1", "name", false);
let diff_tid1 = key(1, 0, 100, 200, false, true, "1", "name", false);
assert!(baseline < diff_tid1);
let diff_tid2 = key(0, 1, 100, 200, false, true, "1", "name", false);
assert!(baseline < diff_tid2);
let diff_pos1 = key(0, 0, 200, 200, false, true, "1", "name", false);
assert!(baseline < diff_pos1);
let diff_pos2 = key(0, 0, 100, 300, false, true, "1", "name", false);
assert!(baseline < diff_pos2);
let baseline_rev = key(0, 0, 100, 200, true, true, "1", "name", false);
assert!(baseline_rev < baseline);
let diff_neg2 = key(0, 0, 100, 200, false, false, "1", "name", false);
assert!(baseline < diff_neg2);
let diff_mid = key(0, 0, 100, 200, false, true, "2", "name", false);
assert!(baseline < diff_mid);
let diff_name = key(0, 0, 100, 200, false, true, "1", "zzzz", false);
assert!(baseline < diff_name);
let diff_upper = key(0, 0, 100, 200, false, true, "1", "name", true);
assert!(baseline < diff_upper);
}
#[test]
fn test_sort_collection() {
let mut keys = [
key(1, 1, 100, 200, false, true, "2", "b", false),
key(0, 0, 100, 200, false, true, "1", "a", false),
key(0, 0, 50, 150, false, true, "1", "a", false),
key(0, 0, 100, 200, true, false, "1", "a", false), key(0, 0, 100, 200, false, true, "1", "b", false),
];
keys.sort();
assert_eq!(keys[0].pos1, 50);
assert!(keys[1].neg1);
assert_eq!(keys[2].name, "a");
assert_eq!(keys[3].name, "b");
assert_eq!(keys[4].tid1, 1);
}
#[test]
fn test_equal_keys() {
let key1 = key(0, 0, 100, 200, false, true, "1", "name", false);
let key2 = key(0, 0, 100, 200, false, true, "1", "name", false);
assert_eq!(key1.cmp(&key2), Ordering::Equal);
assert_eq!(key1, key2);
}
#[test]
fn test_unmapped_reads_use_max_tid() {
let mapped = key(0, 0, 100, 200, false, true, "", "a", false);
let unmapped = key(i32::MAX, i32::MAX, 0, 0, false, false, "", "a", false);
assert!(mapped < unmapped, "mapped reads should sort before unmapped");
}
#[test]
fn test_compare_mid_does_not_strip_short_strings() {
assert_eq!(compare_mid("", ""), Ordering::Equal);
assert_eq!(compare_mid("a", "a"), Ordering::Equal);
assert_eq!(compare_mid("/", "/"), Ordering::Equal);
}
#[test]
fn test_compare_mid_requires_slash_at_correct_position() {
assert_eq!(compare_mid("/A", ""), Ordering::Equal);
assert_eq!(compare_mid("A/", "A/"), Ordering::Equal); }
}