Nemerle で無理数 FizzBuzz


無理数 FizzBuzz を Nemerle で解いてみたという話。無理数 FizzBuzz は以下のような問題です。

無理数 FizzBuzz

自然数を列挙しながら、円周率の整数倍の整数部に等しいときは“Fizz”と言い、自然対数の底の整数倍の整数部に等しいときは“Buzz”と言い、両方であるときは“FizzBuzz”と言う遊び。

Nemerle は遅延評価が使えるので,整数と円周率の整数倍と自然対数の底の整数倍をキープしながら無限リストを作ったらいいんじゃないという方針です。

using Nemerle;
using Nemerle.Utility;

using System.Math;

public class FizzBuzzList
{
   private n : int;
   private pi : double;
   private e : double;
   [Accessor]
   private next : LazyValue[FizzBuzzList];

   public this()
   {
      this.n = 1;
      this.pi = PI;
      this.e = E;
      this.next = lazy(FizzBuzzList(2, PI, E));
   }

   private this(n : int, pi : double, e : double)
   {
      this.n = n;
      this.pi = pi;
      this.e = e;
      def nextN = n + 1;
      def nextPi = if (nextN > pi) pi + PI else pi;
      def nextE = if (nextN > e) e + E else e;
      this.next = lazy(FizzBuzzList(nextN, nextPi, nextE));
   }

   public override ToString() : string
   {
      match (n == Floor(pi), n == Floor(e))
      {
         | (true, true) => "FizzBuzz";
         | (true, false) => "Fizz";
         | (false, true) => "Buzz";
         | (false, false) => n.ToString();
      }
   }
}

mutable fizzbuzz = FizzBuzzList();
repeat (100)
{
   System.Console.WriteLine(fizzbuzz);
   fizzbuzz = fizzbuzz.Next;
}

結果がこちら。

1
Buzz
Fizz
4
Buzz
Fizz
7
Buzz
Fizz
Buzz
11
Fizz
Buzz
14
Fizz
Buzz
17
Fizz
Buzz
20
FizzBuzz
22
23
Buzz
Fizz
26
Buzz
Fizz
Buzz
30
Fizz
Buzz
33
Fizz
Buzz
36
Fizz
Buzz
39
FizzBuzz
41
42
FizzBuzz
44
45
Buzz
Fizz
Buzz
49
Fizz
Buzz
52
Fizz
Buzz
55
Fizz
Buzz
58
FizzBuzz
60
61
FizzBuzz
63
64
FizzBuzz
66
Buzz
68
Fizz
Buzz
71
Fizz
Buzz
74
Fizz
Buzz
77
FizzBuzz
79
80
FizzBuzz
82
83
FizzBuzz
85
Buzz
Fizz
88
Buzz
90
Fizz
Buzz
93
Fizz
Buzz
96
FizzBuzz
98
99
FizzBuzz

おまけ

遅延リストじゃなくて n 番目の数に対して O(1) で出力を求めたいという欲求が。

に相当する無理数を一般的に とします。このとき整数 となるような正の整数 が存在するような に対して Fizz や Buzz というような文字列を出力し,そうでなければ を出力するという処理を考えます。以下ではやや厳密な議論を避けてざっくりとした手法を説明します。 さて,なんとなく周期性を感じる場合は三角関数を使うというのが常套手段です。天下り的ですが, という 2 つの関数を考えます。 xy 平面上の 2 曲線 および のいずれかが x 軸と交差するのは のときです。 のとき のとき のとき ,…というように が交互に x 軸と交差します。

そこで という量を考えてみましょう。区間 において,曲線 は x 軸と高々 1 回しか交差しません。したがって,交差する場合は の正負が逆転するので負に,交差しない場合は の正負が一致するので正になります。 についても同様のことが言えます。

さて,区間 において一方の曲線が x 軸と交差するとき,他方の曲線は x 軸と交差しうるでしょうか。答えはノーです。一方が交差するということは となるような自然数 が存在しているということです。今 を仮定しているので です。一方が で x 軸と交差するので,他方が x 軸と交差するのは です。したがって一方の曲線が x 軸と交わる場合,他方の曲線は x 軸と交わりません。 区間 において一方の曲線が x 軸と交差するならば,他方の曲線は x 軸と交差しないことがわかりました。ということは, (これを とおきます) は負になります。また,区間 において両方の曲線とも x 軸と交差しないなら は正です。逆に が負なら一方の曲線が x 軸と交差し,正ならいずれも交差しません。 以上から, が FizzBuzz の判別に利用できることがわかります。すなわち, が正なら数字をそのまま出力し, が負なら Fizz などの文字列を出力すればよさそうです。 以上を実装したのが次のコードです。 [code language="nemerle"] using Nemerle.Collections; using System.Math; def d(n, s) { def u = PI / s; def x = (2 * n + 1) * u; Cos(x) >= Cos(u); } def isFizz = d(_, PI); def isBuzz = d(_, E); def fizzbuzz(n) { match (isFizz(n), isBuzz(n)) { | (true, true) => "FizzBuzz"; | (true, false) => "Fizz"; | (false, true) => "Buzz"; | (false, false) => n.ToString(); } } $[1..100].Map(fizzbuzz).Iter(System.Console.WriteLine); [/code] 結果は先ほどと同様になります。 コード中では と簡単な形に展開して,括弧の中身の正負を判別に利用しています。判別の不等号に = が付いているのは,上の議論では省略していましたが,区間の端がちょうど 0 になるときのためです (円周率も自然対数の底も無理数なので n が 0 のときのみ起こる)。

ソースコード

ソースコードは GitHub にて公開しています。