🏃

Rust 競プロ チートシート集

2023/02/12に公開
4

Rustで競プロしているときに、これやりたいときどうやるだっけ??となりがちなので、チートシートを作りました。
適宜更新・修正します。
AtCoderにおける入出力方法は以下の記事を参照にしてください。

https://zenn.dev/tipstar0125/articles/f9c4626cdd4d5b

Vec

よく使うメソッド

- enumerate

forで配列をindexと一緒に回す。

let A = vec![3, 4, 5];
for (i, &a) in A.iter().enumerate() {
    println!("{} {}", i, a);
}
// 0 3
// 1 4
// 2 5

- rev

forで逆回し

let N = 3;
for i in (0..N).rev() {
    println!("{} ", i);
}
// 2 1 0
let A = vec![3, 4, 5];
for &a in A.iter().rev() {
    println!("{} ", a);
}
// 5 4 3

- enumerate + rev

let A = vec![3, 5, 2, 1, 4];
for (i, &a) in A.iter().enumerate().rev() {
    println!("{} {}", i, a);
}
// 4 4
// 3 1
// 2 2
// 1 5
// 0 3
let A = vec![3, 5, 2, 1, 4];
for (i, &a) in A.iter().rev().enumerate() {
    println!("{} {}", i, a);
}
// 0 4
// 1 1
// 2 2
// 3 5
// 4 3

- max/min

unwrap()忘れがち。空のときpanic

let vec = vec![1, 2, 3];
println!("{}", vec.iter().max().unwrap());  // 3
println!("{}", vec.iter().min().unwrap());  // 1

- sum

型指定忘れがち。

let vec = vec![1, 2, 3];
println!("{}", vec.iter().sum::<usize>());  // 6

- swap

indexを指定して入れ替え。

let mut vec = vec![1, 2, 3];
vec.swap(0, 2);
println!("{:?}", vec);  // [3, 2, 1]

変数同士のswapは以下の通り。

use std::mem::swap;  // 自動補完で入力されるように設定しておくと便利
let mut a = 0;
let mut b = 1;
swap(&mut a, &mut b);
println!("a:{}, b:{}", a, b);  // a: 1, b: 0

- extend

複数の配列を連結する。元の配列を再利用する場合は、cloneする。

let A = vec![1, 2, 3];
let B = vec![4, 5, 6];
let mut C = A;
C.extend(B);
println!("{:?}", C);  // [1, 2, 3, 4, 5, 6]

- join

配列を指定文字区切りで出力する。

A.iter().join(" ");  // スペース区切り
A.iter().join("\n");  // 改行区切り

itertoolsのCrateを使用できない環境(CF or yukicoderなど)のときは、foldで代用する。

let s = vec![1, 2, 3, 4, 5];
let s = s
    .iter()
    .fold("".to_string(), |ss, &x| ss + x.to_string().as_str() + " ");  // スペース区切り
println!("{}", s);  // 1 2 3 4 5

- permutations

順列全探索

let n = 3;
for p in (0..n).permutations(n) {
    println!("{:?}", p);
}
// [0, 1, 2]
// [0, 2, 1]
// [1, 0, 2]
// [1, 2, 0]
// [2, 0, 1]
// [2, 1, 0]

// 以下でもOK
let vec = vec![0, 1, 2];
for p in vec.iter().permutations(vec.len()) {
    println!("{:?}", p);
}

next_permutationprev_permutationもある。

let mut vec = vec![0, 1, 2];
vec.next_permutation();
println!("{:?}", vec);  // [0, 2, 1]
vec.prev_permutation();
println!("{:?}", vec);  // [0, 1, 2]

- combinations

組合せの全パターンを列挙

for c in (0..3).combinations(2) {
    println!("{:?}", c);
}
// [0, 1]
// [0, 2]
// [1, 2]
let vec = vec![1, 2, 3, 3];
for c in vec.iter().combinations(2) {
    println!("{:?}", c);
}
// [1, 2]
// [1, 3]
// [1, 3]
// [2, 3]
// [2, 3]
// [3, 3]

以下のようにするとbit全探索と同じことができる。

let n = 3;
for i in 1..=n {
    for c in (0..n).combinations(i) {
        println!("{:?}", c);
    }
}
// [0]
// [1]
// [2]
// [0, 1]
// [0, 2]
// [1, 2]
// [0, 1, 2]

ソート

  • 昇順、降順
let mut A = vec![3, 1, 2];
A.sort(); // 昇順
A.reverse();  // 降順
A.sort_by(|a, b| a.cmp(b)); // 昇順
A.sort_by(|a, b| b.cmp(a)); // 降順
  • キーを指定してソート
let mut A = vec![(1, 3), (2, 1), (3, 2)];
A.sort_by(|(_, a), (_, b)| b.cmp(a)); // 第2要素で降順

let mut B = vec![("a", 90, 80), ("d", 70, 80), ("c", 90, 60), ("b", 70, 80)];
B.sort_by(|(a0, a1, a2), (b0, b1, b2)| (-a1, a2, a0).cmp(&(-b1, b2, b0)));
// 第2要素で降順、第3要素で昇順、第1要素で昇順の優先度で並び替え
// [("c", 90, 60), ("a", 90, 80), ("b", 70, 80), ("d", 70, 80)]

// マイナスを使用すると、型をisizeにする必要があるが、
// 以下のようにReverseを使用することでusizeのまま降順にできる。
use std::cmp::Reverse;
B.sort_by(|(a0, a1, a2), (b0, b1, b2)| (Reverse(a1), a2, a0).cmp(&(Reverse(b1), b2, b0)));
  • 部分ソート

reversesortは以下のようにスライスして、部分的に行うことが可能。

let mut v = vec![0, 1, 2, 3, 4, 5];
v[2..=4].reverse();
println!("{:?}", v);
// [0, 1, 4, 3, 2, 5]

let mut v = vec![0, 1, 2, 3, 4, 5];
v[2..=4].sort_by(|a, b| b.cmp(a));
println!("{:?}", v);
// [0, 1, 4, 3, 2, 5]
  • 重複削除

Vecの重複を削除するときは事前にソートする。
ソートしないと、削除されない。。。

let mut A = vec![3, 1, 2];
A.sort();
A.dedup();
  • 浮動小数点のソート

浮動小数点はNaNの可能性があるのでsort()メソッドは使用できない。NaNにならない(0割りがない)ことを自分で保証することを前提として、以下の通りソートすることをができる。

let mut A = vec![10.5, 2.3, 3.5];
//A.sort(); NG
A.sort_by(|a, b| a.partial_cmp(b).unwrap());
println!("{:?}", A);  // [2.3, 3.5, 10.5]

Vecの変換

use std::collections::VecDeque;
let A = vec![1, 2, 3];
let mut A: VecDeque<_> = A.iter().cloned().collect();
let mut A: VecDeque<_> = A.into_iter().collect();  // same above
let mut A: VecDeque<_> = A.into();  // same above
let mut set: BTreeSet<_> = A.iter().cloned().collect();
let mut set: BTreeSet<_> = A.into_iter().collect();  //same above
let mut set: BTreeSet<_> = A.into();  // NG

// for 2D
let B = vec![vec![1, 2, 3], vec![4, 5, 6]];
let B: VecDeque<VecDeque<_>> = B.into_iter().map(|v| v.into_iter().collect()).collect();
let B: VecDeque<VecDeque<_>> = B.into_iter().map(|v| v.into()).collect();  // same above

スライス

例えば、答えを保存した配列を0からではなく、1からスペース区切りで出力したい場合配下の通り、Rangeで指定する。

let ans = vec![1, 2, 3, 4];  // 2 3 4
println!("{}", ans[1..].iter().join(" "));  // 2 3

map

各配列に対して、同じ処理をする場合mapを使用する。
以下は、0-indexedで処理していたものを、1-indexedに変換して出力する例。

let v = vec![0, 1, 2, 3];
println!("{}", v.iter().map(|x| x + 1).join(" "));
// 1 2 3 4

その他高階関数

let v = vec![0, 1, 2, 3, 4, 5];

// for_each
let mut v2 = vec![];
v.iter().enumerate().for_each(|(i, x)| {
    if i % 2 == 0 {
        v2.push(x * 2)
    }
});
println!("{:?}", v2); // [0, 4, 8]

// filter
let v3 = v.iter().filter(|&x| x % 2 == 1).collect::<Vec<_>>();
let cnt = v.iter().filter(|&x| x % 2 == 1).count();
println!("{:?}", v3); // [1, 3, 5]
println!("{}", cnt); // 3

// fold(reduce相当)
let s = v.iter().fold(0, |sum, x| sum + x); // sumと同じ
println!("{}", s); // 15

flatten

let v1 = vec![vec![0, 1], vec![2, 3, 4]];
let v2 = v1.iter().flatten().collect::<Vec<_>>();
println!("{:?}", v2); // [0, 1, 2, 3, 4]

転置(transpose)

Vec用とVecDeque用を関数で用意した。
※ジェネリクスを使用して、統合したい。。。

fn transpose_vec<T>(v: Vec<Vec<T>>) -> Vec<Vec<T>> {
    assert!(!v.is_empty());
    let N = v[0].len();
    let mut iters: Vec<_> = v.into_iter().map(|n| n.into_iter()).collect();
    (0..N)
        .map(|_| {
            iters
                .iter_mut()
                .map(|n| n.next().unwrap())
                .collect::<Vec<T>>()
        })
        .collect()
}

fn transpose_vec_deque<T>(v: VecDeque<VecDeque<T>>) -> VecDeque<VecDeque<T>> {
    assert!(!v.is_empty());
    let N = v[0].len();
    let mut iters: VecDeque<_> = v.into_iter().map(|n| n.into_iter()).collect();
    (0..N)
        .map(|_| {
            iters
                .iter_mut()
                .map(|n| n.next().unwrap())
                .collect::<VecDeque<T>>()
        })
        .collect()
}

let A = vec![vec![1, 2, 3], vec![4, 5, 6]];
let A = transpose_vec(A);
println!("{:?}", A);
let A: VecDeque<VecDeque<_>> = A.into_iter().map(|v| v.into_iter().collect()).collect();
let A = transpose_vec_deque(A);
println!("{:?}", A);

rotate

rotate_rightrotate_leftを使用して回転できる。
VecVecDeque両方で使用可能。

let mut A = vec![1, 2, 3, 4, 5, 6];
A.rotate_right(3);
println!("{:?}", A);  // [4, 5, 6, 1, 2, 3]
A.rotate_left(3);
println!("{:?}", A);  // [1, 2, 3, 4, 5, 6]
let mut A: VecDeque<_> = A.into();
A.rotate_right(3);
println!("{:?}", A);  // [4, 5, 6, 1, 2, 3]
A.rotate_left(3);
println!("{:?}", A);  // [1, 2, 3, 4, 5, 6]

lower_bound, upper_bound

let v = vec![0, 1, 2, 3, 4, 5];
println!("{}", v.lower_bound(&2));  // 2
println!("{}", v.upper_bound(&2));  // 3
// vの各要素を2乗してlower_boundは以下のようにクロージャで指定できる
println!("{}", v.lower_bound_by_key(&4, |x| x * x));  // 2

take_while

whileを使用してiの2乗まで、インクリメントしてループを回す方法は、fortake_whileを使用することでも書くことができる。
条件はクロージャにて指定する。

let N = 16;
let mut i = 0;
while i * i <= N {
    print!("{} ", i);
    i += 1;
}
// 0 1 2 3 4
println!();

for i in (0..).take_while(|x| x * x <= N) {
    print!("{} ", i);
}
// 0 1 2 3 4
println!();

let v = vec![2, 3, 5, 7, 11, 13];
for (i, &p) in v.iter().enumerate().take_while(|x| x.1 * x.1 <= N) {
    println!("{} {}", i, p);
}
// 0 2
// 1 3

windows

配列の複数要素を処理したい場合に使用する。

let v = vec![0, 1, 2, 3, 4, 5];
for x in v.windows(2) {
    let x0 = x[0];
    let x1 = x[1];
    println!("x0: {}, x1: {}", x0, x1);
}
// x0: 0, x1: 1
// x0: 1, x1: 2
// x0: 2, x1: 3
// x0: 3, x1: 4
// x0: 4, x1: 5

let v2 = v.windows(2).map(|x| x[0] + x[1]).collect::<Vec<_>>();
println!("{:?}", v2);
// [1, 3, 5, 7, 9]

position

要素を左または右から探して、見つかったときのindexを返す。

let v = vec![1, 2, 3, 2, 3, 2, 1];
let pos = v.iter().position(|&x| x == 2).unwrap();
println!("{}", pos);
let pos = v.iter().rposition(|&x| x == 2).unwrap();
println!("{}", pos);

文字列

chars

Stringを1文字ずつ処理するには、chars()を使用する。

let S = "abc".to_string();
// let S = "abc"; &strのままでもOK
for c in S.chars() {
    print!("{} ", c);  // a b c
}

proconioCharsは、Rust標準のCharsとは異なり、Vec<char>である。
Vec<char>を生成する場合は以下の通りとする。

let s = "abc".chars();  // Chars
let ss = "abc".chars().collect_vec();  // Vec<char>

String or &str-> num

let s = "1";  // &str
let num: usize = s.parse().unwrap();
println!("{}", num);  // 1
let s = "2".to_string();  // String
let num: usize = s.parse().unwrap();
println!("{}", num);  // 2

Vec<char> -> String

let C = ['a', 'b', 'c'];
let S = C.iter().collect::<String>();  // "abc"

文字反転

let S = "abc".to_string();
let S_rev = S.chars().rev().collect::<String>();
println!("{}", S_rev);  // cba

スライス

let S = "abc".to_string();
println!("{}", &S[1..]);  // bc
println!("{}", &S[..2]);  // ab

文字をabc...の順に数値に変換

let s = 'a';
println!("{}", s as u8 - b'a' + 1);  // 1
let s = 'B';
println!("{}", s as u8 - b'A' + 1);  // 2, 2023/02/14 modified

u8charに戻す場合は、charでキャストすればよい。

let s = 'A';
let s_num = s as u8;
println!("{}", s_num as char);  // A

n進数文字列を数値に変換

let s = "1010";
let num = usize::from_str_radix(s, 2).unwrap();
println!("{}", num);  // 10

let s = vec!['1', 'F'];
let s = s
    .iter()
    .fold("".to_string(), |ss, &x| ss + x.to_string().as_str());
println!("{}", s);
let num = usize::from_str_radix(s.as_str(), 16).unwrap();
println!("{}", num);  // 31

BTreeSet/BTreeMap

同じようなことができるHashSetHashMapがあるが、デバッグするときに、昇順に並んでいた方が分かりやすい、二分探索ができる、というメリットがあるので、BTreeSetBTreeMapを使用する。

BTreeMapの初期化

初期化の際、型を定義しなくても、類推してくれるが、Pythonでいうdefaultdictのような使い方をするために、型定義する。

let mut map: BTreeMap<usize, usize> = BTreeMap::new();
for &a in &A {
    *map.entry(a).or_default() += 1;  // デリファレンスが必要, 初期値:0
}
let mut map: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
for (i, &a) in A.iter().enumerate() {
    map.entry(a).or_default().push(i + 1);  // 初期値:空配列
}

先頭・末尾の取得

let mut set = BTreeSet::new();
set.insert(10);
set.insert(20);
set.insert(30);
println!("{}", set.iter().next().unwrap());  // 10
println!("{}", set.iter().next_back().unwrap());  // 30

let mut map: BTreeMap<usize, usize> = BTreeMap::new();
*map.entry(10).or_default() = 30;
*map.entry(20).or_default() = 20;
*map.entry(30).or_default() = 10;
println!("{:?}", map.iter().next().unwrap());  // (10, 30)
println!("{:?}", map.iter().next_back().unwrap());  // (30, 10)

リセット

BTreeSetBTreeMapを空にするにはclear()を使用する。

set.clear();
map.clear();

二分探索

HashSetではlower_boundが使用できない。代わりにBTreeSetrangeを使用する。以下はBTreeSetの例だが、BTreeMapのキーでも同じことができる。

let mut set = BTreeSet::new();
let value = set.range(x..).next().unwrap(); // lower_bound相当
let value = set.range(..x).next_back().unwrap();  // xより小さい要素の中で最大の要素
let iter = set.range(x..=y);  // x以上y以下

let v = vec![0, 1, 2, 3, 4, 5];
let mut set: BTreeSet<usize> = v.into_iter().collect();
// 3以下の逆順、3番目(0-indexed)
let a = set.range(..=3).rev().nth(3).unwrap();
println!("{}", a);  // 0
// 2以上、2番目(0-indexed)
let b = set.range(2..).nth(2).unwrap();
println!("{}", b);  // 4

集合の計算

計算後の型が変わらず、使いまわせる以下を使用する。

let mut set = BTreeSet::new();
let mut set1 = BTreeSet::new();
set1.insert(1);
set1.insert(2);
set1.insert(3);
let mut set2 = BTreeSet::new();
set2.insert(1);
set2.insert(2);
set2.insert(4);
set = &set1 | &set2;  // 和
println!("{:?}", set);  // {1, 2, 3, 4}
set = &set1 - &set2;  // 差
println!("{:?}", set);  // {3}
set = &set1 & &set2;  // 積
println!("{:?}", set);  // {1, 2}
set = &set1 ^ &set2;  // 対称差
println!("{:?}", set);  // {3, 4}

以下は計算後の型がHashSetBTreeSetではなく、Unionとかになるので、使い道なさそう??
計算結果を使いまわしたい場合に都合が悪い。into_iter().copied().collect()で変換できるけど、面倒。

a.union(&b)  // 和
a.difference(&b)  // 差
a.intersection(&b)  // 積
a.symmetric_difference(&b)  // 対称差

グラフ

グラフの頂点が文字列や大きな数値のときの変換

グラフの頂点が文字列や大きな数値のときは、隣接リストを配列ではなく、HashMapで作ることで処理できるが、以下のように変換するHashMapをつくり、隣接リストを配列で作ることができる。キーはユニークなので、同じキーのものは削除されるが、ソートして、ユニークにしておく。

let mut x = vec![];
let ST = vec![
    ("a".to_string(), "b".to_string()),
    ("b".to_string(), "c".to_string()),
    ("d".to_string(), "a".to_string()),
];
for (s, t) in &ST {
    x.push(s.as_str());
    x.push(t.as_str());
}
x.sort();
x.dedup();

let map: BTreeMap<_, _> = x.iter().enumerate().map(|(i, &s)| (s, i)).collect();
println!("{:?}", map);
// {"a": 0, "b": 1, "c": 2, "d": 3}

浮動小数点

min/max

f64min/maxを使うと、以下のようなエラーに遭遇することがある。

can't call method min on ambiguous numeric type {float}

以下がエラーになる例。

let a = 1.0;
let b = 2.0;
let c = a.min(b);  // Error
let v = vec![1.0, 2.0, 3.0];
let a = v[0].min(2.0);  // Error

以下のように型指定をすると、エラーが解消される。もしくは、次で紹介しているNotNanを使用する。

let a: f64 = 1.0;
let b: f64 = 2.0;
let c = a.min(b);
let v: Vec<f64> = vec![1.0, 2.0, 3.0];
let a = v[0].min(2.0);

ordered float

f64は、NaNの可能性があり、std::cmp::Ordをトレイトに持たないため、sortしたり、BinaryHeapにpushできなかったり、不都合な場合がある。NaNにならないことを自分で保証することを条件に、以下のNotNanを使用することができる。

use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::ops;

use itertools::Itertools;
use ordered_float::NotNan;

let mut v = vec![];
let mut heap = BinaryHeap::new();
for i in (0..5).rev() {
    let x = NotNan::new(i as f64 + 0.1).unwrap();
    v.push(x);
    heap.push(Reverse(x));
}
println!("{}", v.iter().join(" "));  // 4.1 3.1 2.1 1.1 0.1
v.sort();
println!("{}", v.iter().join(" "));  // 0.1 1.1 2.1 3.1 4.1
println!("{:?}", heap);
// [Reverse(NotNan(0.1)), Reverse(NotNan(1.1)), Reverse(NotNan(3.1)), Reverse(NotNan(4.1)), Reverse(NotNan(2.1))]

以下のようにf64NotNan<f64>は演算可能なので、常にNotNan::new()はしなくても良い。

let a = NotNan::new(1.0).unwrap();
let b = 2.0;
let c = a + b;
println!("{}", c); // 3.0

三角関数

let theta = 180.0;
println!("{}", theta.to_radians());  // 3.141592653589793
let theta = theta.to_radians();
println!("{}", theta.to_degrees());  // 180

let theta = (180.0).to_radians();
println!("{}", theta.sin());  // 0.00000000000000012246467991473532
println!("{}", theta.cos());  // -1

その他

タプル分割代入

input! {
    N: usize,
    LR: [(usize, usize); N]
}

for i in 0..N {
    let l = LR[i].0;
    let r = LR[i].1;
    // 上述でも良いが、以下のように分割代入可能
    let (l, r) = LR[i];
    let (mut l, mut r) = LR[i]; // 値を変更する場合
}

パターンマッチ

// queryの第一要素でクエリのパターン(1 or 2 or 3)を指定し、その他の要素(引数)で処理
for &q in &query {
    match q {
        (1, x, y) => {
            // something
        }
        (2, _, _) => {
            // something
        }
        (3, x, _) => {
            // something
        }
        (_, _, _) => unreachable!(),
    }
}

大きい数の設定

1,000,000,007とかの0の数の大きい設定は以下の通り、省略して書ける。

let MOD = 1e9 as usize + 7;

eprint!/eprintln!

  • デバッグ

デバッグの際、print! or println!を使用すると、提出の際に削除する必要があるが、標準エラー出力マクロであるeprint! or eprintln!を使用すれば、そのまま提出することが可能。

  • マルチテストケース

マルチテストケースの問題の際、ターミナルが標準入力と標準出力で混ざってしまい、どれが標準出力かわからなくなる場合がある。
標準出力の前に以下のように標準エラー出力で目印をつけておけば、簡単に確認できる。

eprint!("ans: ");
println!("{}", ans);

is_aresの判定

二次元盤面において、盤面の範囲内かどうかを判定するとき、型がusizeのままだと、subtractエラーが発生してしまうため、isizeに変更して判定する必要があるが、配列の指定はusizeで指定する必要があるので、範囲内であったら、再度usizeに戻す、というように処理が面倒。
そこで、オーバーフローを無視できるwrapping_addwrapping_mulを用いて、usizeのままで、!0(最大数を加算=1減算)の処理が可能。

const DIJ4: [(usize, usize); 4] = [(!0, 0), (0, !0), (1, 0), (0, 1)];

fn main() {

    let is_area = |row: usize, col: usize, dr: usize, dc: usize| -> bool {
        let r = row.wrapping_add(dr);
        let c = col.wrapping_add(dc);
        r < H && c < W
    };

    for i in 0..H {
        for j in 0..W {
            for &(dr, dc) in &DIJ4 {
                if !is_area(i, j, dr, dc) {
                    continue;
                }
                // 処理
            }
        }
    }
}

複数回加算・減算する場合は、以下の通りwrapping_mulを使用する。以下は、numの数だけ加算・減算する。

let is_area = |row: usize, col: usize, dr: usize, dc: usize, num: usize| -> bool {
    let r = row.wrapping_add(dr.wrapping_mul(num));
    let c = col.wrapping_add(dc.wrapping_mul(num));
    r < H && c < W
};

boolを数値に変換

Trueのとき1足したりしたいときに、boolusizeにキャストすることができます。
ちなみに、usizeからboolへのキャストはできない。

let ok = true;
let mut cnt = 0;
cnt += ok as usize;
println!("{}", cnt); // 1
cnt += !ok as usize;
println!("{}", cnt); // 1

count_ones/zeros

数値が2進数の時の0 or 1の数を数える。

println!("{}", 10_u32.count_zeros());  // 30
println!("{}", 10_u32.count_ones());  // 2
println!("{}", 10_usize.count_zeros());  // 62
println!("{}", 10_usize.count_ones());  // 2

std::mem::take

配列の中身を削除しながら、使用する。特に2次元配列で、ある行を使用して、別の行に変更を加えたいときに、借用問題を解消するためにコピーしなくても良い。ただし、takeした方の行は空になる。

  • take使用しない場合
let mut v = vec![vec![1, 2, 3], vec![4, 5, 6]];
for (i, x) in v[0].clone().iter().enumerate() {
    v[1][i] += x;
}
v[0].clear();
println!("{:?}", v);  // [[], [5, 7, 9]]
  • take使用
let mut v = vec![vec![1, 2, 3], vec![4, 5, 6]];
for (i, x) in std::mem::take(&mut v[0]).iter().enumerate() {
    v[1][i] += x;
}
println!("{:?}", v);  // [[], [5, 7, 9]]

テキストファイルで入出力

マルチテストケースの標準出力の改善案として、標準エラー出力する以外に以下の通り、実行の際にコマンドでファイルへの書き出し指示をしてもよい。
outというファイルが出力される(既にある場合は上書きされる)。

cargo run > out

デバッグを何回も実施することがあり、入力指示が面倒な場合は、inファイルを予めつくっておき、その中に入力情報を保存し、以下のコマンドにより読み込む。

cargo run < in
cargo run < in > out

インタラクティブ入力

proconioinputマクロをそのまま使用することができません。
以下のように、inputマクロを置き換えて使います。また、インタラクティブ入力時はproconiofastoutは使用できないので、コメントアウトすることを忘れないようにしましょう!

置き換える方法は2つあります。

  • 方法1
macro_rules! input(($($tt:tt)*) => (
    let stdin = std::io::stdin();
    let mut stdin = proconio::source::line::LineSource::new(std::io::BufReader::new(stdin));
    proconio::input!(from &mut stdin, $($tt)*);
));

fn main() {
    input! {
        N: usize
    }

    while 式 {
        input {
            S: usize
        }
        // 処理
        println!("{}", query);
    }
    println!("{}", ans);
}
  • 方法2

入力ロックを解放する必要があるので、基本は方法1の使用を推奨。
方法2は、入力の一部をグローバル変数に使用する際にも流用できる利点がある。

macro_rules! input(($($tt:tt)*) => (
    let stdin = std::io::stdin();
    let mut stdin = proconio::source::line::LineSource::new(stdin.lock());
    proconio::input!(from &mut stdin, $($tt)*);
));

fn main() {
    // ブロックで囲んで入力ロックを解放
    let N = {
        input! {
            n: usize
        }
        n
    };

    // 複数の場合
    // let (N, M, T) = {
    //     input! { _n: usize, _m: usize, _t: usize, }
    //     (_n, _m, _t)
    // };

    while 式 {
        // whileのブロックで囲まれているので解放不要
        input {
            S: usize
        }
        // 処理
        println!("{}", query);
    }
    println!("{}", ans);
}

入力の一部をグローバル変数

proconioinputマクロの置き換えは、インタラクティブ入力の方法2を使用します。
lazy_staticマクロを使用して、遅延評価により、グローバル変数を実現しています。
処理の最初に入力制御の命令をするのを忘れないこと!

use lazy_static::lazy_static;

macro_rules! input(($($tt:tt)*) => (
    let stdin = std::io::stdin();
    let mut stdin = proconio::source::line::LineSource::new(stdin.lock());
    proconio::input!(from &mut stdin, $($tt)*);
));

lazy_static! {
    static ref N: usize = {
        input! { n: usize }
        n
    };
}

fn main() {
    // 入力制御命令
    lazy_static::initialize(&N);

    let A = {
        input! { a: [usize; *N] }
        a
    };

    while 式 {
        // whileのブロックで囲まれているので解放不要
        input {
            S: usize
        }
        // 処理
        println!("{}", query);
    }
    println!("{}", ans);
}

グローバル変数が複数の場合は、以下の通り。

lazy_static! {
    static ref _INPUT: (usize, usize) = {
        input! { n: usize, m: usize, }
        (n, m)
    };
    static ref N: usize = _INPUT.0;
    static ref M: usize = _INPUT.1;
}

fn main() {
    // 入力制御命令
    lazy_static::initialize(&INPUT);

    let (A, B) = {
        input! { a: [usize; *N], b: [usize; *M] }
        (a, b)
    };

    while 式 {
        // whileのブロックで囲まれているので解放不要
        input {
            S: usize
        }
        // 処理
        println!("{}", query);
    }
    println!("{}", ans);
}

嵌りがちなエラー

  • subtract with overflow

型がusizeのとき、引き算を使用した結果、負の値になるとエラーが発生する。isizeを使用するか、移項して引き算を使用しないように工夫する必要がある。

input! {
    N: usize,
    A: [usize; N]
}

for i in 0..N {
    // 負の値になるような計算をさせない
    if a - b > 0 {} // NG
    if a > b {} // OK
    
    // 負にならないことを確認してからindex指定する
    if A[a-b] > c {}  //NG
    if a > b && A[a-b] > c {}  //OK
    
    // 配列のindexを指定する変数の型がisizeの場合は、asでusizeに変更
    A[d as usize]
}

  • 型推論で、型がi32とかになる場合、オーバーフローにより、WAになるので、型を指定

i32が扱える数の上限を超えてしまう場合、アルゴリズムは正しくでもWAになってしまい、何が原因でWAが出ているのか分からなくなるのを防ぐために、意識しておいた方が良い。また、なぜWAになったのか原因不明な場合は、変数にホバーして、意図していない型になっていないか確認する。

// これだけだと型推論でi32になる場合がある
let mut ans = 0; // NG(厳密にはこれで問題ない場合もある)
let mut ans: usize = 0; // OK
let mut ans = 0_usize; // OK
GitHubで編集を提案

Discussion

eduidleduidl

let s = 'B';
println!("{}", s as u8 - b'B' + 1); // 2

ここって出力1になりませんか?

tipstar0125tipstar0125

ご指摘ありがとうございます。
修正します。正しくは、以下のつもりでした。

let s = 'B';
println!("{}", s as u8 - b'A' + 1); // 2

Yohei MaedaYohei Maeda

next_permutationやprev_permutationもある。

試してみたところどちらも存在しないようなのですが、何らかの crate を使っていますか?