準備体操はRuby。無駄に再帰を使う。
自然数nに対して、3つ前がFizzだったらそれ自身もFizz、 5つ前がBuzzだったらそれもBizz。
#!/usr/bin/env ruby
def isfizz(n)
case n
when 3
'Fizz'
when 1,2
''
else
isfizz(n-3)
end
end
def isbuzz(n)
case n
when 5
'Buzz'
when 1,2,3,4
''
else
isbuzz(n-5)
end
end
def fizzbuzz(n)
fb = isfizz(n)+isbuzz(n)
fb>'' ? fb : n.to_s
end
for i in 1..50
printf("%s\n", fizzbuzz(i))
end
まずzshをインストール。
sudo apt install -y zsh
シェルなので手続き実行で戦わせてみる。
1つめのループでは自然数を出力し、出力後行頭にカーソル移動して 待機する。3か5の倍数が来たら出力した自然数を上書きする。
#!/bin/zsh
SECONDS=0.8 # 1秒に1ずつ進むシェルのグローバル変数
goal=30
i=1
next1s() {
n=$SECONDS
while [[ "$n" = "$SECONDS" ]]; do
: # 秒数が変わるまで空ループ
done
}
while [[ $i -le $goal ]]; do
next1s
printf "\n%d\r" $i
i=$((i+1))
sleep 0.9 # 次の秒直前まで休む
done & # 自然数を出し続けるバックグラウンドジョブ
while [[ $i -le $goal ]]; do
next1s
sleep 0.01
[[ $((i%3)) -eq 0 ]] && printf 'Fizz'
sleep 0.9
i=$((i+1))
done & # 3の倍数でバックグラウンド起動
while [[ $i -le $goal ]]; do
next1s
sleep 0.02
[[ $((i%5)) -eq 0 ]] && printf 'Buzz'
sleep 0.9
i=$((i+1))
done # 3と同じく5の倍数でフォアグラウンド起動
1から50までの自然数を以下のようにして作る。
printf "%d\n" {1..50} > 1to50.csv
以下のSQL文をsqlite3に投入する。
sqlite3 fz.sq3 < fizzbuzz.sql
-- For SQLite3
.mode csv
DROP TABLE IF EXISTS num;
CREATE TABLE num(n INTEGER);
.import 1to50.csv num
.separator ''
SELECT x.f, -- 3で割り切れる場合の 'Fizz'
y.b, -- 5で割り切れる場合の 'Buzz'
CASE -- 上記いずれも
WHEN x.f IS NULL AND y.b IS NULL -- NULLなら
THEN x.n -- 自然数自身をSELECT
END
FROM (SELECT a.n, b.f f
FROM num a
LEFT JOIN
(SELECT n, 'Fizz' f
FROM num WHERE n%3 = 0) b
ON a.n=b.n) x
LEFT JOIN
(SELECT n,'Buzz' b
FROM num where n%5=0) y
ON x.n=y.n;
これは、「3の倍数ならFizz」を出す以下のSQL文から考えると分かりやすい。
sqlite3 fz.sq3
としてから実行してみる。
まず、1から50までの自然数で構成される「左」テーブル a を出す。
SELECT n FROM num; 1 2 3 4 5 6 7 8 9 10 : :
同様に、それが3で割り切れたら 'Fizz' を添えて出す「右」テーブル b を出す。
SELECT n, 'Fizz' AS fz FROM NUM WHERE n%3=0; 3|Fizz 6|Fizz 9|Fizz 12|Fizz 15|Fizz 18|Fizz 21|Fizz 24|Fizz 27|Fizz 30|Fizz 33|Fizz 36|Fizz 39|Fizz 42|Fizz 45|Fizz 48|Fizz
条件に当てはまらない(3で割り切れない)nの行は欠損となる。
a と b のテーブルを外部結合する。
SELECT a.n, fz
FROM num a
LEFT JOIN
(SELECT n, 'Fizz' AS fz FROM num WHERE n%3=0)b
ON a.n=b.n;
1|
2|
3|Fizz
4|
5|
6|Fizz
7|
8|
9|Fizz
10|
11|
12|Fizz
:
:
48|Fizz
49|
50|
fzがNull値の場合のみ、nを選択するようにcoalesce関数でつなぐ。
SELECT coalesce(fz, a.n)
FROM num a
LEFT JOIN
(SELECT n, 'Fizz' AS fz FROM num WHERE n%3=0)b
ON a.n=b.n;
1
2
Fizz
4
5
Fizz
7
8
Fizz
10
:
:
47
Fizz
49
50
以上の流れを、WITH句を利用して構造が分かりやすくなるよう 書き直したものを示しておく。
.separator ''
WITH num AS (
SELECT 1 AS n
UNION ALL
SELECT n+1 FROM num WHERE n < 50
), fizz AS (
SELECT n, 'Fizz' AS fz FROM num WHERE n%3=0
), buzz AS (
SELECT n, 'Buzz' AS bz FROM num WHERE n%5=0
)
SELECT fz, bz,
CASE WHEN fz IS NULL AND bz IS NULL THEN a.n END
FROM (num a LEFT JOIN fizz b ON a.n=b.n)
LEFT JOIN buzz c
ON a.n=b.n AND b.n=c.n AND a.n=c.n;
sqlite3 < fz2.sql
とすると実行できる。