У меня есть форма пользовательского ввода, которая позволяет им указывать либо одно значение n
, либо диапазон в формате start-end
. Я хочу сгенерировать вектор всех чисел в диапазоне (только с одной записью в случае n
).
У меня есть следующее, которое решает проблему:
fn parse_ports(ports: &str) -> Result<Vec<u32>, ParseError> {
let ports: Vec<&str> = ports.split("-").collect();
if ports.len() == 1 {
Ok(
ports.iter()
.map(|x| x.parse().unwrap())
.collect()
)
} else if ports.len() == 2 {
let start: u32 = ports[0].parse().unwrap();
let end: u32 = ports[1].parse().unwrap();
Ok((start..end + 1).collect::<Vec<u32>>())
} else {
// At some point this should probably error but that is a tomorrow problem
Ok(vec![])
}
}
Я уверен, что смогу сделать это более эффективно, но у меня совершенно нет опыта работы с Rust, поэтому буду признателен за любую помощь или отзыв.
Я чувствую, что смогу сделать это без if
. Я также ожидаю, что смогу выполнить некоторую проверку строки вместо того, чтобы иметь дело с случаем len != 1 and len != 2
в if.
Почему вы хотите собирать диапазоны портов в Vecs? В зависимости от вашего варианта использования может быть предпочтительнее просто вернуть объект RangeInclusive
.
Если у вас есть только одиночные и парные случаи, вы можете использовать Split_once().
Если вы всегда пытаетесь выполнить анализ в случае None
, то пустая строка (ports
) также неявно вернет ошибку:
use std::num::ParseIntError;
fn parse_ports(ports: &str) -> Result<Vec<u32>, ParseIntError> {
let (start, end) = if let Some((start, end)) = ports.split_once('-') {
let start = start.parse()?;
let end = end.parse()?;
(start, end)
} else {
let start = ports.parse()?;
(start, start)
};
Ok((start..end + 1).collect())
}
В зависимости от того, какая логика вам нужна между ними, вы можете структурировать эту логику различными способами:
fn parse_ports(ports: &str) -> Result<Vec<u32>, ParseIntError> {
let (start, end) = match ports.split_once('-') {
Some((start, end)) => (start, Some(end)),
None => (ports, None),
};
let start = start.parse()?;
let end = end.map(|end| end.parse()).transpose()?;
Ok(match end {
Some(end) => (start..=end).collect(),
None => vec![start],
})
}
Я почти уверен, что это имеет такое же/желаемое поведение и намного проще, чем оба примера здесь: play.rust-lang.org/…
@cdhowie Да, я согласен, но ОП сказал что-то о промежуточной проверке. Поэтому мне хотелось четко отделить парсинг от сбора
Возможно, будет предпочтительнее проанализировать порты в объекте диапазона и вернуть его, в зависимости от вашего варианта использования:
use std::num::ParseIntError;
use std::ops::RangeInclusive;
const PORTS_DELIMITER: char = '-';
/// Parse a port range.
///
/// # Errors
/// Returns a [`ParseIntError`] if the port numbers could not be parsed.
///
/// # Examples
/// ```
/// use parse_ports::parse_port_range;
///
/// assert_eq!(parse_port_range("123"), Ok(123..=123));
/// assert_eq!(parse_port_range("42-1337"), Ok(42..=1337));
///
/// for (index, port) in parse_port_range("0-65535").unwrap().enumerate() {
/// assert_eq!(index as u32, port);
/// }
///
/// assert!(parse_port_range("1-10").unwrap().contains(&7));
/// ```
pub fn parse_port_range(port_range: &str) -> Result<RangeInclusive<u32>, ParseIntError> {
let (start, end) = port_range
.split_once(PORTS_DELIMITER)
.unwrap_or((port_range, port_range));
Ok(start.parse()?..=end.parse()?)
}
Поскольку у вас уже есть рабочий код и вы ищете способы его улучшения, этот вопрос может больше подойти для codereview.stackexchange.com (однако обязательно прочтите Code Review для пользователей SO, прежде чем публиковать там информацию).