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 命令行界面

Download history 1539/week @ 2024-05-03 1199/week @ 2024-05-10 1262/week @ 2024-05-17 1516/week @ 2024-05-24 1482/week @ 2024-05-31 780/week @ 2024-06-07 1276/week @ 2024-06-14 1087/week @ 2024-06-21 819/week @ 2024-06-28 470/week @ 2024-07-05 834/week @ 2024-07-12 929/week @ 2024-07-19 840/week @ 2024-07-26 736/week @ 2024-08-02 1121/week @ 2024-08-09 775/week @ 2024-08-16

3,555 下载量/每月
8 crates 中使用

MIT/Apache

61KB
854 行代码(不包括注释)

proconio

crates.io docs.rs

竞技编程的简单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> 的形式读取。使用 BytesChars 来实现

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],
    }
}

除了 CharsBytes 之外,Usize1Isize1 也是特殊类型。它们分别被读取为 usizeisize,但读取的值会减一。这使我们能够自动将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));

Usize1Isize1 不存储实际值,因此您不能拥有该类型的值。因此,它们仅在 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