use std::collections::BTreeMap;
fn runs(numbers: &[i32]) -> Vec<(i32, i32)> {
let mut out: Vec<(i32, i32)> = Vec::new();
for &number in numbers {
match out.last_mut() {
Some((_, end)) if end.checked_add(1) == Some(number) => *end = number,
_ => out.push((number, number)),
}
}
out
}
fn sorted_distinct(numbers: &[i32]) -> Vec<i32> {
let mut sorted = numbers.to_vec();
sorted.sort_unstable();
sorted.dedup();
sorted
}
#[must_use]
pub fn compact_ranges(numbers: &[i32]) -> String {
runs(&sorted_distinct(numbers))
.into_iter()
.map(|(start, end)| {
if start == end {
start.to_string()
} else {
format!("{start}-{end}")
}
})
.collect::<Vec<_>>()
.join(",")
}
#[must_use]
pub fn resid_query(residues: &[(String, i32)]) -> Option<(String, usize)> {
let mut by_chain: BTreeMap<&str, Vec<i32>> = BTreeMap::new();
for (chain, number) in residues {
by_chain.entry(chain).or_default().push(*number);
}
let mut parts = Vec::new();
let mut count = 0;
for (chain, numbers) in &by_chain {
let numbers = sorted_distinct(numbers);
count += numbers.len();
let joined = runs(&numbers)
.into_iter()
.map(|(start, end)| {
if start == end {
format!("resid {start}")
} else {
format!("resid {start}:{end}")
}
})
.collect::<Vec<_>>()
.join(" or ");
parts.push(if chain.is_empty() {
format!("({joined})")
} else {
format!("(chain {chain} and ({joined}))")
});
}
(count > 0).then(|| (parts.join(" or "), count))
}
#[cfg(test)]
#[path = "residue_ranges_tests.rs"]
mod tests;