Rust有类似scanf的功能吗?

27
我需要解析一份每行都有的文件。
<string><space><int><space><float>

e.g.

abce 2 2.5

在C语言中,我会这样做:

scanf("%s%d%f", &s, &i, &f);

我该如何在Rust中轻松自然地完成这个操作?
5个回答

24

标准库没有提供这个功能。你可以使用宏来编写自己的代码。

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。

请注意,我很快就把它放在一起了,所以没有进行彻底的测试。


16
你可以使用text_iocrate进行类似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");
}

6

scan_fmt crate是另一个选择。它支持简单的模式匹配,将输出作为选项返回,并且其语法比text_io更易懂:

#[macro_use] extern crate scan_fmt;

fn main() {
    let (s, i, j) = scan_fmt!("abce 2 2.5", "{} {d} {f}\n", String, i32, f32);
    println!("{} {} {}", s.unwrap(), i.unwrap(), j.unwrap());
}

不是我的最爱,因为没有错误处理。 - Jamie Fristrom

2

除非你有某些原因需要精确复制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细节,因此一开始保守可能会更加划算。


2
关于这个解决方案的一个注意事项是,正则表达式在每次迭代中都会被编译。相反,您可能希望通过引用将已编译的正则表达式传递到函数中,或者使用类似lazy_static!宏的东西,在第一次函数调用时仅执行一次。我个人更喜欢前一种风格,并经常将我的正则表达式包装在一个具有new()的结构体中,该结构体编译它们并消耗任何编译错误。 - Kyle Smith

1

我喜欢使用https://docs.rs/scanf/latest/scanf/,因为它比正则表达式更易于理解(无需记忆,更加可读),并且提供了其他答案所没有的功能,例如处理输入字符串"abce:2,3.5"(其他方法只能处理空格分隔符;顶部的示例(对我来说无法编译)理论上可以处理其他分隔符,但不能像scanf一样以字符为单位进行灵活处理。)


只是为了反驳一下,根据你所从事的工作类型,学习正则表达式并将其熟练掌握是一个极其有用的技能,值得获得。 - Kyle Smith
2
并不是说我不喜欢正则表达式 - 而是我认为那个正则表达式的例子比scanf难以阅读。也许这是因为我更熟悉scanf,但我相信实际上它看起来更像一串噪音而不是一种交流的尝试。它肯定不是“美得让我眼泪盈眶”。但我已经限定了我的条目。 - Jamie Fristrom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接