我需要解析一份每行都有的文件。
我该如何在Rust中轻松自然地完成这个操作?
<string><space><int><space><float>
e.g.
abce 2 2.5
在C语言中,我会这样做:
scanf("%s%d%f", &s, &i, &f);
我该如何在Rust中轻松自然地完成这个操作?
标准库没有提供这个功能。你可以使用宏来编写自己的代码。
macro_rules! scan {
( $string:expr, $sep:expr, $( $x:ty ),+ ) => {{
let mut iter = $string.split($sep);
($(iter.next().and_then(|word| word.parse::<$x>().ok()),)*)
}}
}
fn main() {
let output = scan!("2 false fox", char::is_whitespace, u8, bool, String);
println!("{:?}", output); // (Some(2), Some(false), Some("fox"))
}
宏的第二个输入参数可以是&str、char或适当的闭包/函数。指定的类型必须实现FromStr trait。
请注意,我很快就把它放在一起了,所以没有进行彻底的测试。
text_io
crate进行类似scanf的输入,其语法类似于print!
宏。#[macro_use] extern crate text_io;
fn main() {
// note that the whitespace between the {} is relevant
// placing any characters there will ignore them but require
// the input to have them
let (s, i, j): (String, i32, f32);
scan!("{} {} {}\n", s, i, j);
}
你也可以将其分成每个命令 3 步执行:
#[macro_use] extern crate text_io;
fn main() {
let a: String = read!("{} ");
let b: i32 = read!("{} ");
let c: f32 = read!("{}\n");
}
除非你有某些原因需要精确复制scanf
解析内容的方式,否则在大多数情况(和大多数语言)下,最好的答案是“只需使用regex
。”这里是一个 Rust 示例:
use regex::Regex;
use std::io::prelude::*;
fn parse_line(s: &str) -> Option<(String, i32, f32)> {
let r = Regex::new(r"(\w+) (-?\d+) (-?[0-9]*.?[0-9]*)").unwrap();
let caps = r.captures(s)?;
let a = caps.get(1)?.as_str().to_string();
let b = caps.get(2)?.as_str().parse().ok()?;
let c = caps.get(3)?.as_str().parse().ok()?;
Some((a, b, c))
}
fn main() {
let stdin = std::io::stdin();
let stdin = stdin.lock();
for line in stdin.lines() {
println!("{:?}", parse_line(&line.unwrap()));
}
}
使用正则表达式确实会引起一些问题,特别是在浮点数解析方面。您是否想支持负数?没有数字的小数点是否是有效的浮点数?指数表示法是否允许?在一个快速而肮脏的数据解析器中,您可能只支持您的数据所做的任何事情。在实际应用程序中,这个解析器决策可能会成为您的应用程序重要的API细节,因此一开始保守可能会更加划算。
我喜欢使用https://docs.rs/scanf/latest/scanf/,因为它比正则表达式更易于理解(无需记忆,更加可读),并且提供了其他答案所没有的功能,例如处理输入字符串"abce:2,3.5"
(其他方法只能处理空格分隔符;顶部的示例(对我来说无法编译)理论上可以处理其他分隔符,但不能像scanf一样以字符为单位进行灵活处理。)