Rust 競プロ チートシート集
Rustで競プロしているときに、これやりたいときどうやるだっけ??となりがちなので、チートシートを作りました。
適宜更新・修正します。
AtCoderにおける入出力方法は以下の記事を参照にしてください。
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_permutation
やprev_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)));
- 部分ソート
reverse
やsort
は以下のようにスライスして、部分的に行うことが可能。
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_right
とrotate_left
を使用して回転できる。
Vec
とVecDeque
両方で使用可能。
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乗まで、インクリメントしてループを回す方法は、for
でtake_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
}
proconio
のChars
は、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
u8
をchar
に戻す場合は、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
同じようなことができるHashSet
とHashMap
があるが、デバッグするときに、昇順に並んでいた方が分かりやすい、二分探索ができる、というメリットがあるので、BTreeSet
とBTreeMap
を使用する。
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)
リセット
BTreeSet
やBTreeMap
を空にするにはclear()
を使用する。
set.clear();
map.clear();
二分探索
HashSet
ではlower_bound
が使用できない。代わりにBTreeSet
のrange
を使用する。以下は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}
以下は計算後の型がHashSet
やBTreeSet
ではなく、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
f64
でmin/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))]
以下のようにf64
とNotNan<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_add
やwrapping_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足したりしたいときに、bool
をusize
にキャストすることができます。
ちなみに、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
インタラクティブ入力
proconio
のinput
マクロをそのまま使用することができません。
以下のように、input
マクロを置き換えて使います。また、インタラクティブ入力時はproconio
のfastout
は使用できないので、コメントアウトすることを忘れないようにしましょう!
置き換える方法は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);
}
入力の一部をグローバル変数
proconio
のinput
マクロの置き換えは、インタラクティブ入力の方法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
Discussion
ここって出力1になりませんか?
ご指摘ありがとうございます。
修正します。正しくは、以下のつもりでした。
let s = 'B';
println!("{}", s as u8 - b'A' + 1); // 2
試してみたところどちらも存在しないようなのですが、何らかの crate を使っていますか?
自己解決しました。↑を使えば出来そうですね!AtCoder のジャッジにも採用されていてよいですね。