FizzBuzz 問題とは、条件分岐の式をかくための例題としてプログラマーの間では有名な問題です(wiki)。
今回は初心に帰って FizzBuzz を、ワンライナーで解いてみたいと思います!
自分が思いついた解法は次の3つです
前提方針
対象となる数字一覧については seq を用いて連続生成することで、シェルコマンドは文字列操作に集中できるようにしています。
$ seq 5 1 2 3 4 5
以下の例では、100 までの数字について FizzBuzz 処理を実行しています。
awk
やっぱり、シェルで数値の条件分岐などをしようと思ったら AWK が楽かと思います。
まずは、愚直に3つのパターンに条件分岐します。
- 15 で割れるものは "FizzBuzz" と出力する。
- 1 に当てはまらないもののうち、3 で割れるものは "Fizz" と出力する。
- 1 に当てはまらないもののうち、5 で割れるものは "Buzz" と出力する。
$ seq 100 | awk '{if($1 % 15 == 0){print "FizzBuzz"}else if($1 % 3 == 0){print "Fizz"}else if($1 % 5 == 0){print "Buzz"}else{print $1}}' 1 2 Fizz 4 Buzz ...
出力は正しくできてますが、if, else if が続いて読みにくいです。
少し考え方を変えてみます。
15 の倍数の時の処理を 3 の倍数と 5 の倍数の足し算だと考えることで、「"FizzBuzz" = "Fizz" + "Buzz" 」のように出力することを目指します。
以下では awk の連想配列に一時的に値を保存して、最終行が終わったときに実行される END というブロックで連想配列の結果を出力しています。具体的な方針は以下の通りです。
- 数値 i が3 の倍数であれば、連想配列の i のキーに "Fizz"を追加する
- 数値 i が5 の倍数であれば、連想配列の i のキーに "Buzz"を追加する
- 全ての数字のチェックが終わった後、連想配列の数値を出力する。その際、キー i の値が何もなければそのまま i を出力する。
seq 100 | awk '{if($1 % 3 == 0){a[$1] = "Fizz"}}{if($1 % 5 == 0){a[$1] = a[$1]"Buzz"}}END{for(i=1; i<length(a); i++){if(a[i] != ""){print a[i]}else{print i}}}'
sed
次は文字列を置換することで有名な sed を使って処理してみたいと思います。
素直なやり方
sed 余りなどという概念がないため、行数をカウントすることで何の倍数か判断させています。
- 5 の倍数の行数であれば、その数字を Buzz に置き換える
- 3 の倍数の行数の文字に対しては、以下の処理を行う 2.1 その行数の文字が数字であれば、Fizz に置き換える 2.2 その行数の文字が数字以外(="Buzz")であれば、その文字を残しつつ "Fizz" を先頭に追加する。
seq 100 | sed '5~5s@.*@Buzz@' | sed -E '3~3s@[0-9]*([\w]*)@Fizz\1@'
# よく考えないとわからないやり方 seq 100 | sed '5~5s@.*@Buzz@' | sed '3~3s@[0-9]*@Fizz@'
(おまけ)python をワンライナーで使ってみる
python も -c オプションを使うことで、コマンドラインでワンライナーで実行することができます。
Qiita の記事を参考にすると、以下が一番簡潔に書けそうです。
python3 -c 'for i in range(1,101):print("Fizz"*(i%3<1)+"Buzz"*(i%5<1)or str(i))'
おわりに
正規表現について少しずつナレッジが溜まってきたので、そのうちまとめてみたいと思います