Rust用の入力マクロをカスタマイズ
カメラとは無関係の私的なコラムになります。
最近、競技プログラミングをお試ししています。競技プログラミングとは、何かしらの数学的なお題を提出されて、問題の解答をプログラムで求めるという感じのものです。
競技プログラミングを知らない人がいると思いますので、例えばどういう問題がでるのかというと、非常に簡単な問題ですと、「500円玉、100円玉、50円玉、10円玉、5円玉、1円玉を、それぞれ合計で5枚使用したとき、支払うことが可能な金額の数を求めよ」といった感じです。例えば1円玉5枚で5円の支払いができるので1つと数えます。10円玉4枚と1円玉1枚で41円の支払いができるので1つと数えます。これをすべての金額で支払い可能かどうか調べて、支払い可能な金額の数を求めるというのが、この問題です。
この競技プログラミングは世の中で利用されている様々な言語を利用できることが多いのです。最初はC#などで試してみたり、JavaベースのKotlinなどで試していたのですが、C++の後継言語と目されているRustで試してみたところ面白かったので、最近はこの言語を利用していました。
しかし、Rustで標準入力を取り扱うのは非常に面倒なのですよね。それで、何かいい方法はないかと思っていて、クレート(公認のアドオン的なもの。でも競プロを主催する団体によっては利用できないことが多い)のproconioを使用していました。しかしクレートの試用を認めていない競技プログラミングを主催している団体もあるため、残念ながらRustを諦めようとしていました。ですが、さらに調査したらマクロというものが使えるらしいということで、それならさらに自分のオリジナルの機能を実装しようということで作製したのが、このマクロです。
基本的には@tanakhさんのこちらのマクロに機能を追加したものになります。なので基本的な動作については、こちらのサイトの表記をご覧ください。
ちなみに読み込み速度とか、安全性は完全に無視しています。
基本的な書式
二つのデータを変数に読み込む場合には以下のようになります
//1 2
//とか
//1
//2
//を入力可能
input! {
n: usize,
m: String,
}
空白スペース、または改行で異なるデータと区別されて、変数に代入されます。この場合は、nに1がusizeで、mに2がStringで代入されます。標準入力は文字と数字の区別がないので、ここで指定された変数に変換されて格納され、もし変換できない場合はエラーになります。変数は変更不可能なイミュータブルです。
配列への代入
改行区切りまたは横のスペース区切りで要素数がわかっている数字の羅列を配列に代入する場合は以下のように記述できます。nで行数を受け取り、そのnを利用して配列の長さを指定してvecに格納します。
//3
//1 2 3
//や
//3
//1
//2
//3
//を配列に入れる
input! {
n: usize,
nums: [usize; n],
}
上記の場合はvec![1, 2, 3]になります。
ネストした配列への代入
配列をネストして代入することも可能です。
//3 4
//1 2 3 4
//5 6 7 8
//9 0 1 2
//のような3×4の配列をvecに格納
input! {
h: usize, w: usize,
nums: [[usize; w]; h],
}
タプルへの代入
タプルにも対応しています。
//1 2
//n.0に1をn.1に2を代入
input! {
n: (usize, usize),
}
タプルの配列にも対応。
input! {
n: usize,
data: [(usize, usize); n],
}
配列のインデックスの0ベースと1ベースの対応
プログラムで配列の順番を0から数えるのか、1から数えるのか表記の違いがありますが、それに対応することができます。
input! {
h: usize1,
}
このようにすると、実際に与えられた数値-1の数値がhに代入されます(1なら0)。
文字列を分解して配列に格納
変数の型にcharsを指定した場合、入力した文字列を1文字ずつ分解してベクターに格納されます。
input! {
h: chars,
}
上記では、Vec<char>として、abcdefが1文字ずつ代入されます。上記の例では、vec![‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’]になります。
機能追加した部分
ここまではオリジナルの機能とほぼ同じなのですが、その他にもあると便利だと思った機能を追加しています。
変更可能な変数に代入
input! {
n: usize,
mut m: String,
}
変数名の前にmutと記述すると、変数の内容を変更可能なミュータブルな変数になります。指定しない場合は変更不可能です。
1行をまるごと変数に代入
変数名をlineとすることで、1行をまるごとStringとして読み込むことができます。具体的には次の改行コードまでを読み込んでいます。
//1 2 3 4 5 6
//を1行としてまるごと取得
input! {
s: line,
}
行の空白スペースのいくつかを別変数で読み込み、1行の残りをまるごと変数に代入することもできます。
//1 2 3 4 5 6
//nに1がusizeで、"2 3 4 5 6"がsにStringで入る
input! {
n: usize, s: line,
}
1行をまるごと配列に格納
たまに要素数の指定がない空白スペース区切りの数字を配列に入れる必要がでてきます。そのときは要素数指定で配列に入れることができないので、1行の数字の羅列をまるごと配列に入れるように作ったのがこれです。
//1 2 3 4 5 6
//を1行としてまるごと配列に格納
input! {
nums: [usize; all],
}
要素数にallと記述すると1行まるごと配列にいれることができます。上記の例では、vec![1, 2, 3, 4, 5, 6]になります。もちろんusizeでもisizeでもStringも可能です。
1行をまるごと取得したときと同様に、最初の数値を別変数に格納することもできます。
//1 2 3 4 5 6
//nに1が、numsに2,3,4,5,6が配列で入る
input! {
n: usize, nums: [usize; all],
}
行ごとに要素数が異なる数値を配列に格納
行数はわかっているけど、行ごとの要素数がわからない半角スペース区切りの数字が入力される場合があります。その場合でも、以下のように記述すれば配列に格納することができます。
//2
//a b c
//d e
//配列a,b,c、配列d,eとしてdataに格納される
input! {
n: usize,
data: [[String; all]; n],
}
上記は下記と同じことをしています。
data.push(vec!["a".to_string(),"b".to_string(),"c".to_string()]);
data.push(vec!["d".to_string(),"e".to_string()]);
これを応用して、こんなことも可能です。
//2
//1 attack a
//2 defence
//タプルのusizeに数値が入り、残りの文字がdataに格納される
//勇者1がモンスターaに攻撃
//勇者2はディフェンス
//のような感じで
input! {
n: usize,
data: [(usize, [String; all]); n],
}
行数の分かっている行を行まるごと配列に格納
行数の分かっている行の1行をそれぞれまるごと配列に格納することができます。
//2
//9 9 9 9
//8 8 8 8
//"9 9 9 9"と"8 8 8 8"がそれぞれStringで配列に入る
input! {
n: usize,
data: [line; n],
}
全行をまるごと配列に格納
たまに行数の指定なしにすべての行を配列に入れたい場合があります。以下のようにするとstrにvec<String>で格納されます。
//abc
//def
//ghi
input! {
str: lines,
}
ただし、この場合、ローカル環境でテストケースをコピペした場合、どこで終了したのかがわからないので入力終了の合図としてctrl+dでEOFを入力してあげる必要があることに注意が必要です。
要素数の変数の型をusizeに強制変換
まれに比較の関連で配列の要素数をusizeではなくisizeなど負の値も代入できる変数の型に指定したい場合があります。しかし配列の要素数はusizeでなければならないので、isizeで数値を受け取るとエラーになります。このマクロでは数値を強制的にusizeに変換させるのでisizeなどでの数値の取得が可能です。
//3
//1 2 3
//のようなものを取得 nがisizeでもusizeに変換するので配列の要素数に指定できる
//nはもちろんisizeとして利用可能
input! {
n: isize,
nums: [usize; n],
}
static変数への代入
rustでは御法度とされているstatic変数ですが、競プロでは便利なこともあるので、事前に宣言したstatic変数に対して数値を代入できるようにしました。static変数を利用することで、関数内でも外部の変数の参照が簡単になります。
static mutであらかじめ変数を宣言しておくと、その変数をinputマクロ内でも利用することができます。識別子として変数名の前にstaticという表示をする必要があります。
static mut X: usize = 0;
static mut Y: usize = 0;
static mut Z: usize = 0;
fn main() {
unsafe {
input! {
static X: usize,
static Y: usize,
static Z: usize,
}
}
}
お題1000本ノック
お題 その1
1 2
数字、文字が1行の半角スペース区切りで2個(個数固定)与えられる場合。
実施例1-1
input! {
n: usize, m: usize,
}
assert_eq!(n, 1);
assert_eq!(m, 2);
usizeとして取得
実施例1-2
input! {
n: String, m: char,
}
assert_eq!(n, "1".to_string());
assert_eq!(m, '2');
それぞれStringやcharで取得。parseできるもので取得することができます。
実施例1-3
input! {
n: (usize, usize),
}
assert_eq!(n.0, 1);
assert_eq!(n.1, 2);
タプルとして取得。もちろんString、isizeなどparseできるものなら可能。
実施例1-4
input! {
v: [usize; 2],
}
assert_eq!(v, vec![1, 2]);
数字(文字)が2個固定なので、配列の要素数を直接していして配列に格納することができる。
お題 その2
4
5
数字、文字が複数行にわたり改行区切りで2個(個数固定)与えられる場合。
実施例2-1
input! {
n: usize,
m: usize,
}
assert_eq!(n, 4);
assert_eq!(m, 5);
改行の区切りになるので単独の変数で取得できる。実施例1-2、実施例1-3のように変数の型の指定が可能。
実施例2-2
input! {
n: (usize, usize),
}
assert_eq!(n, (4, 5));
改行区切りでもタプルで取得できる。parseできる変数を指定可能。
実施例2-3
input! {
v: [usize; 2],
}
assert_eq!(v, vec![4, 5]);
個数固定なので要素数指定で配列に取得することができる。parseできる変数の型を指定可能。
お題 その3
1 2 3 4 5
数字(文字)が1行で半角スペース区切りで要素数してい無しで与えられる場合。
実施例3-1
input! {
v: [usize; all],
}
assert_eq!(v, vec![1, 2, 3, 4, 5]);
要素数にallを指定することで、行末までの数字(文字)を配列で取得できる。
実施例3-2
input! {
s: line,
}
assert_eq!(s, "1 2 3 4 5".to_string());
変数の型名にlineと指定することで1行をまるごとStringとして取得できる。String固定。
お題 その4
1
2
3
4
5
要素数の決まっていない数字(文字)が改行区切りで与えられる場合。
実施例4-1
input! {
s: lines,
}
assert_eq!(s, vec!["1".to_string(), "2".to_string(), "3".to_string(), "4".to_string(), "5".to_string()]);
行数がわからない入力の場合は変数の型名をlinesとすることで最後の行まで配列に取得できる。String固定。
ローカル環境でコピペして標準入力させる場合にはctrl+d(EOF)を最後に入力する必要がある。
お題 その5
4
1 2 3 4
実施例5-1
input! {
n: usize,
v: [usize; n],
}
assert_eq!(v, vec![1, 2, 3, 4]);
1行目が要素の数で、2行目がその要素の数のデータが並ぶとき。nで要素数を取得し、vに要素n個の配列として取得する。String、isize、charなどparseできるものなら指定可能。
実施例5-2
input! {
n: usize,
v: [usize; all],
}
assert_eq!(v, vec![1, 2, 3, 4]);
横に半角スペース区切りで数字(文字)が並んでいるときには、このマクロではallが使えるので要素数を書かなくても取得できてしまいます。String、isizeなどparseできるものなら指定可能。
実施例5-3
input! {
s: lines,
}
前述の通り、型名をlines指定で1行をStringとして配列で取得できる。String固定。
お題 その6
3
1 2
3 4
5 6
最初に行数の指定があり、その後に2組の数字(文字)が1行に半角スペース区切りで与えられる場合。
実施例6-1
input! {
n: usize,
v: [(usize, usize); n],
}
行の要素数が固定なのでタプルの配列で取得ができる。
実施例6-2
input! {
n: usize,
v: [[usize; 2]; n],
}
行の要素数が固定なので入れ子にした配列で取得ができる。
お題 その7
2 4
1 2 3 4
5 6 7 8
行数と列数が与えられる場合。
実施例7-1
input! {
h: usize, w: usize,
v: [[usize; w]; h],
}
縦と横の要素数がわかっているので、hとwで取得し、その要素数を元に配列で取得する。
お題 その8
実施例8-1
abcd
文字(数字)をvec<char>で取得したい場合。
input! {
c: chars,
}
assert_eq!(c, vec!['a', 'b', 'c', 'd']);
便宜上、文字になっているけど標準入力に文字と数字の区別はないので、数字もcharで取得可能。
お題 その9
2
abcd
efgh
与えられるデータの行数と、その数のデータが与えられ、それをcharで取得したい場合。
実施例9-1
input! {
n: usize,
v: [chars; n],
}
お題 その10
3
1 2
3 4 5
6 7 8 9
1行の要素数がわからないデータが与えられた場合。
実施例10-1
input! {
n: usize,
v: [[usize; all]; n],
}
配列数をallと記述することで、1行の行末までの半角スペース区切りのデータを配列に入れることができる。1行の要素数がわからなくても取得できる。
実施例10-2
input! {
n: usize,
v: [line; n],
}
lineという変数の型名にすると1行をまるごとStringとして配列で取得できる。String固定
実施例10-3
input! {
n: usize,
v: [(usize, [usize; all]); n],
}
各行の先頭データをタプルの1つめとして、残りの半角スペース区切りのデータを配列としてタプルの2つめに格納する。
その他
mutableな変数に格納
input! {
mut n: usize,
}
変数名の前にmutという識別子をつけることでmutableな変数に格納
配列の要素数を取得する変数でusize以外の使用
input! {
n: isize,
v: [usize; n],
}
通常は配列のindexはusizeでないとエラーになる。このマクロではusizeに変換しているので、配列の要素数を取得する変数にusize以外の指定が可能。
配列のindexのゼロベース対応
input! {
n: usize1,
}
usize1という変数名にすることで、-1された数値で取得する。0ベースのindexに対応できる。
staticな変数に取得
static mut N: usize = 0;
fn main() {
unsafe {
input! {
static N: usize1,
}
}
}
変数名の前にstaticという識別子をつけると、static mutで宣言した変数で取得できる。
完成したマクロ
macro_rules! input {
($($r:tt)*) => {
let mut bytes = std::io::Read::bytes(std::io::BufReader::new(std::io::stdin()));
let mut next = move |is_word: bool| -> String{
if is_word {
bytes
.by_ref()
.map(|r|r.unwrap() as char)
.skip_while(|c|c.is_whitespace())
.take_while(|c|!c.is_whitespace())
.collect()
} else {
bytes
.by_ref()
.map(|r|r.unwrap() as char)
.skip_while(|c| c == &'\n')
.take_while(|c| c != &'\n')
.collect()
}
};
input_inner!{next, $($r)*};
};
}
macro_rules! input_inner {
($next:expr) => {};
($next:expr, ) => {};
($next:expr, static $var:ident : $t:tt $($rest:tt)*) => {
$var = read_value!($next, $t);
input_inner!{$next $($rest)*}
};
($next:expr, mut $var:ident : $t:tt $($rest:tt)*) => {
let mut $var = read_value!($next, $t);
input_inner!{$next $($rest)*}
};
($next:expr, $var:ident : $t:tt $($rest:tt)*) => {
let $var = read_value!($next, $t);
input_inner!{$next $($rest)*}
};
}
macro_rules! read_value {
($next:expr, ( $($t:tt),* )) => {
( $(read_value!($next, $t)),* )
};
($next:expr, [ $t:tt ; all ]) => { {
let mut str = $next(false);
str.split_whitespace().map(|it| it.parse::<$t>().unwrap()).collect::<Vec<_>>()
}
};
($next:expr, [ $t:tt ; $len:expr ]) => {
(0..$len as usize).map(|_| read_value!($next, $t)).collect::<Vec<_>>()
};
($next:expr, chars) => {
read_value!($next, String).chars().collect::<Vec<char>>()
};
($next:expr, lines) => {
{
let mut vec = Vec::new();
let mut str = $next(false);
while str != "" {
vec.push(str);
str = $next(false);
}
vec
}
};
($next:expr, line) => {
$next(false)
};
($next:expr, usize1) => {
read_value!($next, usize) - 1
};
($next:expr, $t:ty) => {
$next(true).parse::<$t>().expect("Parse error")
};
}
コメント