拡張FizzBuzz問題をシェルプログラミングで
先日なぜかふと思いつく。FizzBuzz問題を解くのに、シェル芸で制御構造的なもの無しで解いた。
シェル芸でFizzBuzz問題
一見回りくどいように見えるが、この方法には一つ利点?があるように思う。
$ seq 20 |
> awk 'NR%3==0{print $0,"Fizz"}NR%5==0{print $0,"Buzz"}' |
> yarr num=1 |
> loopj num=1 <(seq 20) - |
> sed 's/ 0$//' |
> tr -d ' ' |
> sed '/zz/s/[0-9]*//'
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
このシェル芸の本質は前半まで。まずは倍数と対応する文字列部分を表示
$ seq 20 | awk 'NR%3==0{print $0,"Fizz"}NR%5==0{print $0,"Buzz"}'
3 Fizz
5 Buzz
6 Fizz
9 Fizz
10 Buzz
12 Fizz
15 Fizz
15 Buzz
18 Fizz
20 Buzz
次に同じキー(数字)の出力を、Tukubaiコマンドのyarrを使って横に並べる。
$ seq 20 | awk 'NR%3==0{print $0,"Fizz"}NR%5==0{print $0,"Buzz"}' |
> yarr num=1
3 Fizz
5 Buzz
6 Fizz
9 Fizz
10 Buzz
12 Fizz
15 Fizz Buzz
18 Fizz
20 Buzz
あとは1から20までの数字を使ってloopjで補完。
$ seq 20 |
> awk 'NR%3==0{print $0,"Fizz"}NR%5==0{print $0,"Buzz"}' |
> yarr num=1 | loopj num=1 <(seq 20) -
1 0
2 0
3 Fizz
4 0
5 Buzz
6 Fizz
7 0
8 0
9 Fizz
10 Buzz
11 0
12 Fizz
13 0
14 0
15 Fizz Buzz
16 0
17 0
18 Fizz
19 0
20 Buzz
あとは下記のように文字列処理する。
- 倍数に対応しない数字の行( 0で終わる行)は、
0
を削除 - 倍数に対応する部分は文字だけ表示
このシェル芸の利点?は、倍数と出力する文字列の指定は、最初のawkに指定するだけ。倍数と文字列を増やすのも簡単ということ。
例えば2の倍数の時にJazzと出すのを追加するなら、awk 'NR%2==0{print $0,"Jazz"}NR%3==0{print $0,"Fizz"}NR%5==0{print $0,"Buzz"}'
という具合。
FizzBuzz拡張編を考える
その後、どうせならその利点を生かして、拡張版FizzBuzz問題をシェルプログラミングで解いてみることにした。
環境
下記のUNIX環境。
お題(拡張版FizzBuzz問題)
倍数と表示する文字列を標準入力から指定する。出力する数字の数は第一引数で指定する。
入力
$ cat input
2 Fizz
3 Buzz
出力
$ cat input | ./fizzbuzz.sh 10
1
Fizz
Buzz
Fizz
5
FizzBuzz
7
Fizz
Buzz
Fizz
さて倍数と表示する文字列の処理をするコードをどうするか?下記のようにawkを使ってawkスクリプトを作ってしまおう。
$ cat input
3 Fizz
5 Buzz
$ cat input | awk '{printf "NR%%%s==0{print $0,\"%s\"}\n", $1,$2}'
NR%3==0{print $0,"Fizz"}
NR%5==0{print $0,"Buzz"}
これを一時ファイルに保存して、awkにスクリプトとして渡してやる。
ではシェルスクリプトはこちら。
$ cat efizzbuzz.sh
#!/bin/sh
tmp=/tmp/$$
num=30
[ $# -eq 1 ] && num=$1
seq $num > $tmp-num
sed '/^$/d' |
awk '{printf "NR%%%s==0{print $0,\"%s\"}\n", $1,$2}' > $tmp-awkscript
cat $tmp-num |
awk -f $tmp-awkscript |
yarr num=1 |
loopj num=1 $tmp-num - |
sed 's/ 0$//' |
awk 'NF==1{print}NF!=1{$1="";gsub(/ /,"",$0);print}'
rm -f $tmp*
実行例は下記の通り。
$ cat input
2 Bizz
3 Fizz
5 Buzz
7 Kuzz
$ cat input | ./efizzbuzz.sh 100
1
Bizz
Fizz
Bizz
Buzz
BizzFizz
Kuzz
Bizz
Fizz
BizzBuzz
..........
Kuzz
Bizz
Fizz
Bizz
Buzz
BizzFizz
97
BizzKuzz
Fizz
BizzBuzz
このシェルスクリプトのポイントは下記の通り。
- 条件判定の部分は最小限
- ループ処理は無し
理解出来れば案外スッキリしているのではないか?と思うがどうだろうか?