23个版本
0.5.0 | 2024年6月30日 |
---|---|
0.4.5 | 2023年6月22日 |
0.4.3 | 2021年4月21日 |
0.4.2 | 2021年3月21日 |
0.3.0 | 2019年7月6日 |
#108 in 命令行界面
3,555 下载量/每月
在 8 crates 中使用
61KB
854 行代码(不包括注释)
proconio
竞技编程的简单I/O库。
proconio
提供了一种从stdin(或其他源)读取值的方法。该库的主要功能是 input!
宏。
该宏的用户界面基本上与 tanakh的input宏 相同。
use proconio::input;
input! {
n: u8,
m: u32,
l: i32,
}
// now you can use n, m and l as variable.
println!("{} {} {}", n, m, l);
更多详情请参阅文档。
lib.rs
:
竞技编程的简单I/O库。
proconio
提供了一种从stdin(或其他源)读取值的方法。该crate提供的主要功能是 input!
宏(及其家族,包括 input_interactive!
,read_value!
,和 read_value_interactive!
)。
示例
该宏的用户界面基本上与 tanakh的input宏 相同。
use proconio::input;
input! {
n: u8,
m: u32,
l: i32,
}
// now you can use n, m and l as variable.
println!("{} {} {}", n, m, l);
在上面的代码中,声明了变量n,m和l,并从stdin读取了它们的值。
您可以声明可变变量,如下所示
use proconio::input;
input! {
n: u32,
mut m: u32,
}
m += n; // OK: m is mutable
您可以像这样读取数组或矩阵
use proconio::input;
input! {
n: usize,
m: usize,
a: [[i32; n]; m] // `a` is Vec<Vec<i32>>, (m, n)-matrix.
}
如果第一个输入是数组的长度,则可以省略长度。这是一次性读取锯齿形数组(其成员数组可以有不同的大小的数组)的唯一方法。(当然,您可以在for循环中多次使用 input!
来读取这样的数组,因为 input!
可以多次使用。)
use proconio::input;
input! {
n: usize,
a: [[i32]; n],
}
// if you enter "3 3 1 2 3 0 2 1 2" to the stdin, the result is as follows.
assert_eq!(
a,
vec![
vec![1, 2, 3],
vec![],
vec![1, 2],
]
);
字符串可以以 Vec<u8>
或 Vec<char>
的形式读取。使用 Bytes
和 Chars
来实现
use proconio::input;
use proconio::marker::{Bytes, Chars};
input! {
string: String, // read as String
chars: Chars, // read as Vec<char>
bytes: Bytes, // read as Vec<u8>
}
// if you enter "string chars bytes" to the stdin, they are like this.
assert_eq!(string, "string");
assert_eq!(chars, ['c', 'h', 'a', 'r', 's']);
assert_eq!(bytes, b"bytes");
您可以读取元组
use proconio::input;
input! {
t: (i32, i32, i32, i32, i32),
}
// if you enter "1 2 3 4 5" to the stdin, `t` is like this.
assert_eq!(t, (1, 2, 3, 4, 5));
并且可以自由地组合这些类型。
use proconio::input;
input! {
n: usize,
m: usize,
t: [([u32; m], i32); n],
}
您可以使用 input!
宏多次。对于第二次使用,input!
宏将读取剩余的输入。即使第一次输入在行中间停止,它也能正常工作。后续的读取将从行的剩余部分开始。这可能在一次性给出多个数据集的问题中很有用。
use proconio::input;
input! {
n: usize,
}
for i in 0..n {
input! {
m: usize,
a: [i32; m],
}
}
除了 Chars
和 Bytes
之外,Usize1
和 Isize1
也是特殊类型。它们分别被读取为 usize
和 isize
,但读取的值会减一。这使我们能够自动将1索引的顶点数字转换为0索引的数组索引。
use proconio::input;
use proconio::marker::Usize1;
input! {
n: usize,
edges: [(Usize1, Usize1); n],
}
// if you enter "4 1 3 3 4 6 1 5 3", the decremented value is stored.
assert_eq!(edges[0], (0, 2));
assert_eq!(edges[1], (2, 3));
assert_eq!(edges[2], (5, 0));
assert_eq!(edges[3], (4, 2));
Usize1
和 Isize1
不存储实际值,因此您不能拥有该类型的值。因此,它们仅在 input!
或 #[derive_readable]
中有用。您可以将这些类型存在的理由视为“如何读取值”。这个如何做可以通过 Readable
特性来定义。这个特性不要求输出类型与实现者相同。Usize1
实现了 Readable
特性,在那里读取值的类型被定义为 usize
。您可以为您的自定义类型实现 Readable
来以自定义方式读取值。
最后,您可以使用 #[derive_readable]
属性使您的自定义类型成为 Readable
。在结构体中使用的类型会自动转换为它们的输出类型,因此声明为 Usize1
的成员在真实结构体中具有 usize
类型。
注意:使用 #[derive_readable]
需要启用 derive
功能。要这样做,请打开您的 Cargo.toml 并修改 proconio 的行
proconio = "=(version)"
到
proconio = { version = "=(version)", features = ["derive"] }
#[derive_readable]
宏的示例
use proconio::input;
use proconio::derive_readable;
// Unit struct can derive readable. This generates a no-op for the reading. Not ignoring
// the read value, but simply skip reading process. You cannot use it to discard the input.
#[derive_readable]
#[derive(PartialEq, Debug)]
struct Weight;
#[derive_readable]
#[derive(PartialEq, Debug)]
struct Cost(i32);
#[derive_readable]
#[derive(Debug)]
struct Edge {
from: usize,
to: proconio::marker::Usize1, // The real Edge::to has type usize.
weight: Weight,
cost: Cost,
}
fn main() {
input! {
edge: Edge,
}
// if you enter "12 32 35" to the stdin, the values are as follows.
assert_eq!(edge.from, 12);
assert_eq!(edge.to, 31);
assert_eq!(edge.weight, Weight);
assert_eq!(edge.cost, Cost(35));
}
read_value!
宏
read_value!
宏是在 input!
宏内部使用的宏,但直接使用它也有用,在某些情况下非常有用。通常当您不需要将值存储到变量中时。
use proconio::source::auto::AutoSource;
use proconio::read_value;
let mut source = AutoSource::from("2 3 4");
let mut sum = 0;
for _ in 0..read_value!(from &mut source, usize) {
sum += read_value!(from &mut source, u32);
}
assert_eq!(sum, 7);
交互式版本
正常的 input!
和 read_value!
宏在评测环境中一次性读取整个输入以优化 I/O 性能。然而,这对于交互式问题来说效果不佳,因为这些问题需要通过交替写入和读取与评测器进行通信。
在这种情况下,您可以手动为 stdin 创建 LineSource。这些宏的交互式版本正是这样做的。它们是 input_interactive!
和 read_value_interactive!
。
这些宏的使用方法与正常宏完全相同。有关更多信息,请参阅 input! 和 read_value! 的文档。
#[fastout]
如果您导入 proconio::fastout
,您可以使用 #[fastout]
属性。将此属性添加到您的 main()
,您的 print!
和 println!
将变得更快速。
注意: 使用 #[proconio::fastout]
需要启用 derive
功能。要做到这一点,打开您的 Cargo.toml 并将 proconio 的行从
proconio = "=(version)"
到
proconio = { version = "=(version)", features = ["derive"] }
#[fastout]
的示例
use proconio::fastout;
#[fastout]
fn main() {
print!("{}{}, ", 'h', "ello"); // "hello" (no newline)
println!("{}!", "world"); // "world!\n"
println!("{}", 123456789); // "123456789\n"
}
在 #[fastout]
函数中的具有 print!
或 println!
的闭包
您不能在 #[fastout]
函数中创建包含 print!
或 println!
的闭包。这是因为闭包由于引用了由 #[fastout]
属性引入的未锁定的 stdout 而变得线程不安全。如果不禁止这种情况,那么这种闭包的不当使用将产生非常复杂的错误消息。例如,std::thread::spawn()
,它要求其闭包参数是线程安全的,会导致令人困惑的错误。
是的,让所有这样的闭包都成为编译错误过于保守,因为实际上在单个线程内部使用这样的闭包实际上没有问题。这与 #[fastout]
实现的局限性有关。
有关更多技术细节,请参阅 proconio-derive
中 #[fastout]
的文档。
如何解决此错误
假设您想运行此代码
use proconio::fastout;
#[fastout]
fn main() {
let thread = std::thread::spawn(|| {
let x = 3;
let y = x * x;
println!("{}", y);
});
thread.join().unwrap();
}
您将得到如下错误。
error: Closures in a #[fastout] function cannot contain `print!` or `println!` macro
note: If you want to run your entire logic in a thread having extended size of stack, you can
define a new function instead. See documentation (https://.....) for more details.
note: This is because if you use this closure with `std::thread::spawn()` or any other
functions requiring `Send` for an argument closure, the compiler emits an error about thread
unsafety for our internal implementations. If you are using the closure just in a single
thread, it's actually no problem, but we cannot check the trait bounds at the macro-expansion
time. So for now, all closures having `print!` or `println!` is prohibited regardless of the
`Send` requirements.
--> src/test.rs:10:9
|
10 | println!("{}", y);
| ^^^^^^^
如果您的 print!
依赖于线程中的计算,您可以从线程返回结果。
use proconio::fastout;
#[fastout]
fn main() {
let thread = std::thread::spawn(|| {
let x = 3;
x * x
});
let y = thread.join().unwrap();
println!("{}", y);
}
如果您正在执行过于复杂的任务,以至于从闭包中返回结果变得很困难...
use proconio::fastout;
#[fastout]
fn main() {
let context = "some context".to_string();
let thread = std::thread::spawn(move || {
// Use many println! and the order is very important
// It's possible to aggregate the result and print it later, but it's not easy to read
// and seems ugly.
println!("this is header.");
for (i, item) in some_function(context).enumerate() {
print!("Item #{}: ", i);
print!("{}", some_proc(&item));
println!("({})", item);
}
});
thread.join().unwrap();
}
...您可以使用函数代替。
use proconio::fastout;
// You can add #[fastout] here
#[fastout]
fn process(context: String) {
// It's completely OK since this #[fastout] is a thing inside `process()`
println!("this is header.");
for (i, item) in some_function(context).enumerate() {
print!("Item #{}: ", i);
print!("{}", some_proc(&item));
println!("({})", item);
}
}
// You must not add #[fastout] here! It causes deadlock.
// #[fastout]
fn main() {
let context = "some context".to_string();
let thread = std::thread::spawn(move || process(context));
thread.join().unwrap();
}
重要说明:如果您创建了一个新线程来运行另一个被#[fastout]
注解的函数,您不能在调用者中添加#[fastout]
。如果您在调用者中也添加了#[fastout]
,那么调用者将持有stdout的锁,因此被调用者将无法永久获取该锁——死锁。当调用者和被调用者在同一线程中执行时,这不是死锁的情况,因为stdout的锁是可重入的。由于我们不知道要调用的函数附带的注解,我们无法警告这种死锁。(在上面的例子中,我们无法知道函数process()
是否有#[fastout]
属性。)
如果您的代码非常复杂,以至于无法避免死锁,您应该放弃使用#[fastout]
,并简单地使用println!
或在常规Rust方式下手动处理您的stdout。
打印顺序问题
#[fastout]
启用了对stdout的缓冲,因此如果在main函数中的两次打印之间在其他函数中打印某些内容,打印顺序可能会不同。换句话说,下面的例子
fn foo() { println!("between"); }
#[fastout]
fn main() {
println!("hello");
foo();
println!("world");
}
可能打印如下
between
hello
world
如果您不喜欢这种行为,您可以从您的main()
中移除#[fastout]
。
依赖
~220KB