色々な言語でのFizzBuzz

Ruby

準備体操はRuby。無駄に再帰を使う。

自然数nに対して、3つ前がFizzだったらそれ自身もFizz、 5つ前がBuzzだったらそれもBizz。

fizzbuzz.rb

#!/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

まずzshをインストール。

sudo apt install -y zsh

シェルなので手続き実行で戦わせてみる。

  1. 1からnまで自然数を出力し続けるループ
  2. 1からnまで3の倍数のときだけ "Fizz" を出力し続けるループ
  3. 1からnまで5の倍数のときだけ "Buzz" を出力し続けるループ

1つめのループでは自然数を出力し、出力後行頭にカーソル移動して 待機する。3か5の倍数が来たら出力した自然数を上書きする。

fizzbuzz.zsh

#!/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の倍数でフォアグラウンド起動

SQL

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句を利用して構造が分かりやすくなるよう 書き直したものを示しておく。

fz2.sql

.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

とすると実行できる。