普段 Anarchy Golf や Code Golf でコードゴルフをやっている際に, 気付いたテクニックやTipsを自分なりにまとめました.
代替案やもっと良い書き方がありましたらお気軽にコメントお願いします.
※コード例にNG
とOK
と付けていますが, NGの例も絶対に使わないというわけではありません.
動作確認環境
$ rustc -V
rustc 1.51.0 (2fd73fabe 2021-03-23)
出力
出力は全てprint
マクロを使う.
改行する際は
print!("{}
",n)
とすることで, println
マクロを使用するより1byte減らせる.
比較
割った余りが0
か?
if n%3==0{} // NG
if n%3<1{} // OK
大きい数と比較
if n<832041{} // NG
if n<1<<20{} // OK
型注釈
let a:u8=0; // NG
let a=0u8; // OK
let s=a.iter().map(|&x|x*2).collect::<Vec<u32>>(); // NG
let s:Vec<u32>=a.iter().map(|&x|x*2).collect(); // OK
collectのような戻り値がジェネリックなものは, Vec<_>
のように省略できる場合もある.
クロージャ
同じ処理をする場合は関数にしてしまった方がbyte数は少なくなる可能性もあるだろう.
その際に再帰しないのであれば, クロージャの方が型注釈が要らないので少なくなる.
fn f(n:i32)->i32{n*2} // NG
let f=|n|n*2; // OK
コマンドライン引数
実行時のパスを含めない場合は以下のように書く.
std::env::args()[1..]
のようには書けない.
for a in std::env::args().skip(1){}
条件に一致するもののみ処理対象にするような問題では.skip(1)
を省略できる.
for a in std::env::args(){}
ループ
C言語だとfor文一択だと思いますが, Rustでは場合によりけりです.
パターン毎に使用例を提示します.
+1
の範囲なら=
を使う
for _ in 0..n+1{} // NG
for _ in 0..=n{} // OK
for_each
は使わない
(0..10).for_each(|i|{}); // NG
for i in 0..10{} // OK
逆順
for i in(0..10).rev(){}
// ^ 空白を詰めれる
n回ループと決まっていない場合
while i<10{
i+=if true{2}else{5}
}
rangeから暗黙的にイテレータにできるので以下も有効的
(0..10).fold(0,|a,x|{print!("{}",a);a+x})
mapはそのままでは標準出力されないことに注意.
collectすると出力される. 変数に束縛する必要はない.
(0..10).map(|n|{print!("{}",n);n}).collect()::<Vec<_>>()
変数
変数を複数使う場合はタプルを分解すると効率良い
let a=0;let b=1;let c=2; // NG
let(a,b,c)=(0,1,2); // OK
mutableな時は変数の使用回数によって使い分け
let(mut a,mut b,mut c)=(0,1,2);
let mut a=(0,1,2);
クロージャも使える
let(a,b)=(0,1);a+b // NG
(|a,b|a+b)(0,1) // OK
式
Rustはほとんどが文ではなく式で構成されている.
つまり, for式やif式から返る値を有効活用できる.
forやwhileは()
を返す必要がある
let m=for n in 0..10{n}; // NG
let m=for n in 0..10{n;}; // OK
()
を返すという特性を活かして, セミコロンを省略
for n in 0..10{print!("{}",n)}
char
'A'
はASCIIコードの10進数で65
である.
文字列を一文字ずつ解析する場合は, chars
よりbytes
の方が良い場合もある.
match c as _{
65=>(),
66=>(),
// ...
89=>(),
_=>()
}
文字列
to_string()
よりもto_owned()
やinto()
が使えないか試す.
文字列リテラルをStringに変換する勇気も時には必要.
print!("{}",
match(i%3,i%5){
(0,0)=>"FizzBuzz".into(),
(0,_)=>"Fizz".into(),
(_,0)=>"Buzz".into(),
_=>i.to_string()
})
空白埋め
図形を描画する系の問題で使用する.
可変幅の場合
let n=42;
print!("{}"," ".repeat(n)); // NG
print!("{:<w$}","",w=n); // NG
print!("{:<1$}","",n); // OK
固定幅の場合
print!("{:^42}",n)
パターンマッチ
波かっこの後ろにカンマは要らない.
match a{
0=>{}
1=>{}
_=>{}
}
一番最後にもカンマは要らない.
match a{
0=>"a",
1=>"b",
_=>"c"
}
rangeを使用する場合は=
が必要.
match c{
'a'..='z'=>(),
'0'..='3'|'6'..='9'=>(),
'🍣'..='🍺'=>(),
_=>()
}
if let
のような論駁可能性はenum以外でも使える.
if matches!(n,2|3|5){} // NG
if n==2||n==3||n==5{} // NG
if let 2|3|5=n{} // OK
キャスト
bool
型は整数型へキャストすることで 0
1
に変換できる
v=if i<1{0}else{n-i}; // NG
v=(i>0)as u8*n-i; // OK
ライフタイム
スコープとライフタイムは別物.
// aとbのライフタイムは同じ
let a;
{
let b=&1;
a=b
}
print!("{}",a) // OK
必要以上のライフタイムを持つ変数へ束縛することでライフタイムを伸ばすことができる.
その際, 初期化されていない変数を参照しない限り, 宣言時の初期化やmut
は必要ない.
let n;
print!("{}",
match(i%3,i%5){
(0,0)=>"FizzBuzz",
(0,_)=>"Fizz",
(_,0)=>"Buzz",
_=>{n=i.to_string();&n}
})
Never type
Rustには発散する !
という型がある.
具体的には break
, return
, panic
そして loop
等がある.
!
は任意の型にキャストすることができるので, こんな活用もできる.
i
が100以上の時にループから抜けたい
if i>99{break} // NG
i>99&&break // OK
unsafe
unsafeなコードで短縮できる場合もある.
コードゴルフで安全性は考慮する必要なし.
unsafe{
static mut A:Vec<Vec<u8>>=vec![];
unsafe fn f(){
// something with using A
}
}
Webサイト
様々な問題の各種言語での解答が載っている.
Rust以外の言語で使用しているアルゴリズムも非常に参考になる.
-
codegolf.stackexchange.com/
ゴルファーのための質問サイト -
TopAnswers
普通に問題と解答が載っている -
Rosetta Code
ショートコーディングではないが, コード例が載っている
その他
- キャスト時は
as _
にできないか試す - 実行時の警告は
RUSTFLAGS=-Awarnings
で無視できる
追記 - rustc 1.58.0 (02072b482 2022-01-11)
Rust 1.58からフォーマット文字列で変数を直接参照できるようになった.
1.58時点では識別子のみ有効であり、式の指定はできない。
print!("{}",i) // NG
print!("{i}") // OK