use std::collections::BTreeSet;
pub fn parse_selection(input: &str, max: usize, names: &[String]) -> Vec<usize> {
let mut include = BTreeSet::new();
let mut exclude = BTreeSet::new();
let mut saw_include = false;
for token in input.split(|ch: char| ch.is_whitespace() || ch == ',') {
let token = token.trim();
if token.is_empty() {
continue;
}
let (is_exclude, token) = token
.strip_prefix('^')
.map(|value| (true, value))
.unwrap_or((false, token));
let target = if is_exclude {
&mut exclude
} else {
saw_include = true;
&mut include
};
if let Some((start, end)) = token.split_once('-') {
if let (Ok(start), Ok(end)) = (start.parse::<usize>(), end.parse::<usize>()) {
for index in start.min(end)..=start.max(end) {
if index > 0 && index <= max {
target.insert(index - 1);
}
}
}
continue;
}
if let Ok(index) = token.parse::<usize>() {
if index > 0 && index <= max {
target.insert(index - 1);
}
continue;
}
if let Some(index) = names.iter().position(|name| name == token) {
target.insert(index);
}
}
let mut selected = if saw_include {
include
} else if !exclude.is_empty() {
(0..max).collect()
} else {
BTreeSet::new()
};
for index in exclude {
selected.remove(&index);
}
selected.into_iter().collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_ranges_and_exclusions() {
let names = vec!["a".into(), "b".into(), "c".into(), "d".into()];
assert_eq!(parse_selection("1-3 ^2", 4, &names), vec![0, 2]);
assert_eq!(parse_selection("^4", 4, &names), vec![0, 1, 2]);
assert_eq!(parse_selection("b d", 4, &names), vec![1, 3]);
}
}