広島大学ITエンジニアアドベントカレンダー Advent Calendar 2019の1日目の記事です。
広島大学工学部4年生荒木勇登です!
来年から福岡でITエンジニアします!
いみこというハンドルネームでTwitterをやっていますのでそちらもご確認ください。
(https://twitter.com/es__135 )
最近Rustにはまっていて、かつ前から興味のあったコードゴルフをFizzBuzz 問題を題材としてやって見ました。
コードゴルフとは
要件を満たすプログラムを可能な限り短く書く競技です。
(参考:https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%BC%E3%83%89%E3%82%B4%E3%83%AB%E3%83%95)
FizzBuzzとは
15の倍数ならFizzBuzz、3の倍数ならFizz、5の倍数ならBuzzと言うというパーティーゲームが由来のプログラムの練習問題です。
(参考:https://ja.wikipedia.org/wiki/Fizz_Buzz#FizzBuzz%E5%95%8F%E9%A1%8C)
今回は下記のサイトの要件に乗っ取りました
http://golf.shinh.org/p.rb?FizzBuzz
普通に書いてみる
素直に実装するとこんな感じでしょうか。
fn main(){
for i in 1..101{
if i%15 == 0 {
println!("FizzBuzz");
}else if i%3 == 0 {
println!("Fizz");
}else if i%5 == 0 {
println!("Buzz");
}else {
println!("{}",i)
}
}
}
これをそのままコードゴルフっぽくすると下のようになります。
fn main(){for i in 1..101{if i%15==0{println!("FizzBuzz");}else if i%3==0{println!("Fizz");}else if i%5==0{println!("Buzz");}else{println!("{}",i)}}}
149文字でした!!
わお!!!汚い!!!!!!
さて、ここから短くしていきましょう。
matchを使う
Rustにはmatchという機能があります。(Rust以外の言語にもありますが)
(参考:https://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/match.html)
if文だとelse ifを何回も書くのが文字数の無駄かなと思ったのでmatchで書き直してみます。
fn main(){
for i in 1..101{
match i%15 {
0 => println!("FizzBuzz"),
3|6|9|12 => println!("Fizz"), //3の倍数を15で割ったときのあまりは3,6,9,12のいずれか
5|10 => println!("Buzz"), //5の倍数を15で割ったときのあまりは5,10のどちらか
_ => println!("{}",i)
}
}
}
ぱっと見、短そうな気がします!
fn main(){for i in 1..101{match i%15{0=>println!("FizzBuzz"),3|6|9|12=>println!("Fizz"),5|10=>println!("Buzz"),_=>println!("{}",i),}}}
134文字でした!!
やったね!!!!!!
println!()を何回も使わないようにする
何回もprintln!()って書いてるのが無駄なように思いました。
出力の結果をなんらかの文字に入れてその文字を出力するような書き方になおします。
(Rustではletとmatchを下のように組み合わせる書き方がよく使われます。)
fn main(){
for i in 1..101{
let x = match i%15{
0 => "FizzBuzz",
3|6|9|12 => "Fizz",
5|10 => "Buzz",
_ => i,
};
println!("{}",x);
}
}
しかし、このコードは以下のようなエラーが出ます。
error[E0308]: match arms have incompatible types
--> src/main.rs:7:16
|
3 | let x = match i%15{
| _________________-
4 | | 0=>"FizzBuzz",
| | ---------- this is found to be of type `&str`
5 | | 3|6|9|12=>"Fizz",
| | ------ this is found to be of type `&str`
6 | | 5|10=>"Buzz",
| | ------ this is found to be of type `&str`
7 | | _=>i,
| | ^ expected &str, found integer
8 | | };
| |_________- `match` arms have incompatible types
|
= note: expected type `&str`
found type `{integer}`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.
matchで返す型は全部同じじゃないといけないってことですね。
なので、iをintegerから&strに変換する必要があります。
しかし、integerから直接&strにする方法はなく(要出典、あったら教えてください)
Stringを経由する必要があるようです。
(&strとStringの違いはここでは説明しません。他の記事をご覧ください。)
その方法をとると文字数が増えるのでiの型を変換する方針は諦めました。
Twitterでこれ以上短くならんやんけーと言っていたところ夜糸さん(https://twitter.com/yaito3014 )の天才的な解答が引用RTで飛んできました。
(説明のために一部改変)
fn main(){
for i in 0..101{
let s;
println!(
"{}",match i%15{ //println!()の中でmatchをすればprintln!()が1回で済む!!!!!!!
0 => "FizzBuzz",
3|6|9|12 => "Fizz",//3の倍数を15で割ったときのあまりは3,6,9,12のいずれか
5|10 => "Buzz",//5の倍数を15で割ったときのあまりは5,10のどちらか
_ =>{s=i.to_string();&s}
}
);
}
}
そうです!!println!()の()中でmatchを書けば良いんですね!!!!!
天才的な発想力!!!!!やられたーーーーーーーって感じでした(笑)
fn main(){for i in 0..101{let s;println!("{}",match i%15{0=>"fizzbuzz",3|6|9|12=>"fizz",5|10=>"buzz",_=>{s=i.to_string();&s}});}}
130文字
おおおおおおおお短くなったーーーーーーー!!!!!!!!!!!!!!!!!!!!!!!
let sとto_string()を削る
上のコードが天才的でこれで企画おわた(๑╹ω╹๑ )って思いましたが、企画者としてはなんとか自分の自分のアイディアをさらに入れなければなりません。(笑)
let sとto_string()が文字数を使っているのでそこをなんとか削れないかなーと考えました。
そこで考えついたアイディアが「_では空文字列を返して、数字はprint!()で出力する」でした。
すると、let sとto_string()が削れるので短くなるのではないかと考えました。
(コンパイル通るか微妙だと思いましたが)
そのコードが下です。
fn main(){
for i in 1..101{
println!(
"{}",match i%15{ //println!()の中でmatchをすればprintln!()が1回で済む!!!!!!!
0 => "FizzBuzz",
3|6|9|12 => "Fizz", //3の倍数を15で割ったときのあまりは3,6,9,12のいずれか
5|10 => "Buzz", //5の倍数を15で割ったときのあまりは5,10のどちらか
_ => {
print!("{}",i); //iは上のprintln!()とは別のprint!()文で出力する
"" //なにか返さなければならないので空文字列を返す
}
}
);
}
}
なんか短くなってる気がします!!!どうでしょう!!!!
fn main(){for i in 1..101{println!("{}",match i%15{0=>"fizzbuzz",3|6|9|12=>"fizz",5|10=>"buzz",_=>{print!("{}",i);""}});}}
122文字
わーーーーーーーーい、やったねーーーーーーーーーーーーーー、うれしーーーーーーーーーーーーーーーーーー!!!!!!!!!!!!!!!!!!!
コードゴルフのサイトに提出してみる
コードゴルフ用のサイトがありここに投稿してみました。
http://golf.shinh.org/p.rb?FizzBuzz
投稿してみたところ2位でした!!!!!!やったね!!!!!!!!
あと1文字どうやって削ってるんでしょう!!!!!!
まとめ
コードゴルフ初体験でしたが、おもしろくて勉強になりますね!!!
継続的に続けていきたいです!!!
でも、Rustではやらんかな!!!!!!!!
;がいらない!!(12月1日追記)
コメントで指摘をいただきました!ありがとうございます!
fn main(){for i in 1..101{println!("{}",match i%15{0=>"fizzbuzz",3|6|9|12=>"fizz",5|10=>"buzz",_=>{print!("{}",i);""}});}}
fn main(){for i in 1..101{println!("{}",match i%15{0=>"fizzbuzz",3|6|9|12=>"fizz",5|10=>"buzz",_=>{print!("{}",i);""}})}}
)の後ろに;を削除しても実行できます。ということで一文字削減できました!!!やったね!!!
理由についてはRustの言語仕様に関わる話になります。元気があったら追記いたします。
参考文献
wikipedia コードゴルフ
https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%BC%E3%83%89%E3%82%B4%E3%83%AB%E3%83%95
wikipedia FizzBuzz
https://ja.wikipedia.org/wiki/Fizz_Buzz#FizzBuzz%E5%95%8F%E9%A1%8C
プログラミング言語Rust マッチ
https://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/match.html