1130 字
6 分钟
如何愉快的使用rust写oj
2024-11-19

编写模板#

读取一行字符串#

在Python中,我们一般使用如下代码来读取输入。

a = input()

带提示的读取

a = input("input sth.")

在rust中,我们应该这样获取输入。

fn main() {
    use std::io;
    // 导入io操作需要的库

    let mut tmp = String::new();
    // 先声明并初始化一个名为 tmp 的 可变String

    io::stdin().read_line(&mut tmp).unwrap();
    // 从stdin读取一行输入并通过可变引用绑定到tmp
    // 注意这种方法读取的字符串尾会带上\r\n
    println!("{:?}", tmp.chars());
    println!("{:?}", tmp.trim().chars());
}

结果为

abc
Chars(['a', 'b', 'c', '\r', '\n'])
Chars(['a', 'b', 'c'])

唔,好啰嗦的感觉。我们甚至没有处理输入错误,而是使用的unwrap

好消息,我们可以使用rust中的声明宏实现类似的效果。

在 Rust 中宏分为两大类:声明式宏( declarative macros ) macro_rules! 和三种过程宏( procedural macros )1

我们先定义一个名为input的声明宏。

macro_rules! input {
    () => {
    };
}

然后把上面读取输入的代码粘贴进去。

macro_rules! input {
    () => {
        {
            let mut tmp = String::new();
            io::stdin().read_line(&mut tmp).unwrap();
            tmp.trim().to_string()
        }
    };
}

我们的基础款就完成了。

WARNING

我们应该在宏的上面引入std::io

fn main() {
    let a = input!();
    println!("{}", a);
}
Hello world!
Hello world!

我觉得这样很酷😎,不觉得吗?

很方便有木有。

但是我们还有一个功能,输入之前提示一下用户。

应该不会有人盯着终端半天等结果但是忘记输入吧😎👌😭。

首先添加另一个分支。它应该接收一个参数作为提示。

大概应该长这样

    ($prompt:expr) => {
        {
        }
    };

宏里面的参数都应该使用美元符号开头,具体请参看脚注的详细教程。

然后我们先把上面的实现粘贴进去,毕竟我们还是得读取输入的。

添加输出到stdout并且刷新(不刷新不会实时显示)。

最终结果

use std::io::{ self, Write };

macro_rules! input {
    () => {
        {
            let mut tmp = String::new();
            io::stdin().read_line(&mut tmp).unwrap();
            tmp.trim().to_string()
        }
    };
    ($prompt:expr) => {
        {
            print!($prompt);
            io::stdout().flush().unwrap();
            let mut tmp = String::new();
            io::stdin().read_line(&mut tmp).unwrap();
            tmp.trim().to_string()
        }
    };
}

使用

fn main() {
    let a = input!("input sth:");
    println!("{}", a);
}
input sth:sth
sth

读取一行数字#

在Python中只需要一行。

ls = list(map(int, input().split()))
print(ls)
1 2 3 4
[1, 2, 3, 4]

可恶,确实好方便。

但是,我们也不是没有办法。

我们的目标是将字符串分割成Vector,然后将字符串转换为具体的类型。

再怎么说,我们也是函数式的。😍

我的回合,抽卡!!!我发动魔术手

首先定义一个特型。

trait OJ {
    fn to_vec<T>(&self, p: &str) -> Result<Vec<T>, ParseIntError>
        where T: FromStr<Err = ParseIntError>;
}

为了方便,我们使用的泛型。 只要是能从字符串转为某类型的(实现了FromStr trait的类型),我们就接受。

没啥含金量的实现。

impl OJ for str {
    fn to_vec<T>(&self, separator: &str) -> Result<Vec<T>, ParseIntError>
        where T: FromStr<Err = ParseIntError>
    {
        self.split(separator)
            .map(|s| s.parse::<T>())
            .collect()
    }
}

但是,我们的代码里有一个奇怪的东西str

???

这是什么情况

没错,宛如神奇的魔术一般,不用像Java一般继承基类,我们向位于标准库中的str类型添加了自己的方法。

因为String底层是str,所以String也有了这个方法。

use std::{ io::{ self, Write }, num::ParseIntError, str::FromStr };

macro_rules! input {
    () => {
        {
            let mut tmp = String::new();
            io::stdin().read_line(&mut tmp).unwrap();
            tmp.trim().to_string()
        }
    };
    ($prompt:expr) => {
        {
            print!($prompt);
            io::stdout().flush().unwrap();
            let mut tmp = String::new();
            io::stdin().read_line(&mut tmp).unwrap();
            tmp.trim().to_string()
        }
    };
}

trait OJ {
    fn to_vec<T>(&self, p: &str) -> Result<Vec<T>, ParseIntError>
        where T: FromStr<Err = ParseIntError>;
}

impl OJ for str {
    fn to_vec<T>(&self, separator: &str) -> Result<Vec<T>, ParseIntError>
        where T: FromStr<Err = ParseIntError>
    {
        self.split(separator)
            .map(|s| s.parse::<T>())
            .collect()
    }
}

跑一下测试

fn main() {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_string() {
        assert_eq!(vec![1, 2, 3], String::from("1 2 3").to_vec(" ").unwrap());
    }

    #[test]
    fn test_str() {
        assert_eq!(vec![1, 2, 3], "1 2 3".to_vec(" ").unwrap());
    }
}

顺利通过测试。到这里,我们的模板基本成型了。

好,就叫他天上天下天地无双模板

实战#

两数之和#

如果你没有听过两数之和的鼎鼎大名,那我在这里介绍一下,两数之和犹如英语四级词汇中的abandon一样的地位。

题目链接 洛谷 A+B

// 上面为模板,这里省略,提交的时候应该有。
fn main() {
    if let Ok(ls) = input!().to_vec::<i32>(" ") {
        let (a, b) = (ls[0], ls[1]);
        println!("{}", solve(a, b))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_1() {
        assert_eq!(50, solve(20, 30));
    }
}

评测结果:

编程语言代码长度用时内存
Rust O21.22KB30ms576.00KB

如果您有更好的方案和实现,请告知我。本质是为了抛砖引玉。

Footnotes#

  1. 引用自 rust圣经

如何愉快的使用rust写oj
https://blog.900803.xyz/posts/rust-oj/
作者
jhq223
发布于
2024-11-19
许可协议
CC BY-NC-SA 4.0