2013年12月8日日曜日

コンパイル時FizzBuzz

無名再帰の記事を書いたと思ったら書いて無かったし、テンプレート制約なるものを見聞きしたので使ってみたりした。


まずはテンプレート制約を使ったコンパイル時fibonacci
template fibo(int N) if (N < 2 && N >= 0) {
const long fibo = N;
}
template fibo(int N) if (N >= 2) {
const long fibo = fibo!(N - 1) + fibo!(N - 2);
}
template fibo(int N) if (N < 0) {
const long fibo = fibo!(N + 2) - fibo!(N + 1);
}
unittest {
assert(fibo!(0) == 0);
assert(fibo!(1) == 1);
assert(fibo!(2) == 1);
assert(fibo!(4) == 3);
assert(fibo!(7) == 13);
assert(fibo!(-1) == 1);
assert(fibo!(-2) == -1);
assert(fibo!(-3) == 2);
}
void main() {
import std.stdio;
fibo!(30).writeln;
}
view raw template_figo.d hosted with ❤ by GitHub


こんな風に、template temp(T) if(expr)みたいに書くと、if文で判定してテンプレートを適用してくれるみたいです。
これを使うと以前のstatic if文を使ったfizzbuzzも、
template fizzbuzz(int N) if (N % 15 == 0) {
const fizzbuzz = "fizzbuzz";
}
template fizzbuzz(int N) if (N % 3 == 0 && N % 5 != 0) {
const fizzbuzz = "fizz";
}
template fizzbuzz(int N) if (N % 5 == 0 && N % 3 != 0) {
const fizzbuzz = "buzz";
}
template fizzbuzz(int N) if(N % 3 && N % 5) {
import std.conv;
const fizzbuzz = N.to!string;
}
unittest {
assert(fizzbuzz!(1) == "1");
assert(fizzbuzz!(3) == "fizz");
assert(fizzbuzz!(5) == "buzz");
assert(fizzbuzz!(15) == "fizzbuzz");
assert(fizzbuzz!(33) == "fizz");
assert(fizzbuzz!(60) == "fizzbuzz");
}
void main() {
import std.stdio;
fizzbuzz!(15).writeln;
fizzbuzz!(3).writeln;
}


static if文を並べるよりは綺麗に見えるんじゃないでしょうか。適用可能なテンプレートが複数あるとエラーになるので、if文がちょっとアレな感じはありますが。

ここで、以前書いたmixin FizzBuzzを見ると、(このコードだとmixinする意味が謎ですが)
import std.stdio;
import std.conv;
string fizzbuzz(immutable ulong num)
{
return (num % 15 == 0)? "FizzBuzz": (num % 3 == 0)? "Fizz": (num % 5 == 0)? "Buzz": num.to!string();
}
string genfbtable(immutable ulong num)
{
string ret = "[\"0\",";
for(ulong i = 1; i <= num; i++)
{
ret ~= '"' ~ fizzbuzz(i) ~ '"';
ret ~= i == num ? "]" : ",";
}
return ret;
}
void main()
{
immutable ulong num = 100;
string[] table = mixin(genfbtable(num));
for(ulong i = 1; i <= num; i++)
writeln(i, " ", table[i]);
}
view raw fizzbuzz.d hosted with ❤ by GitHub


こんなふうにFizzBuzzの判定をする関数を作って利用してますが、D言語はコンパイル時に関数を実行出来るスゴイ奴があるので、
import std.stdio;
import std.conv;
string genFizzBuzzString(immutable ulong num)
{
return (num % 15 == 0)? "fizzbuzz": (num % 3 == 0)? "fizz": (num % 5 == 0)? "buzz": num.to!string;
}
template fizzbuzz(ulong N) {
const fizzbuzz = genFizzBuzzString(N);
}
unittest {
assert(fizzbuzz!(1) == "1");
assert(fizzbuzz!(3) == "fizz");
assert(fizzbuzz!(5) == "buzz");
assert(fizzbuzz!(15) == "fizzbuzz");
assert(fizzbuzz!(33) == "fizz");
assert(fizzbuzz!(60) == "fizzbuzz");
}
void main() {
import std.stdio;
fizzbuzz!(15).writeln;
fizzbuzz!(3).writeln;
}


このように何も考えなくてもコンパイル時fizzbuzzできますね。

fizzbuzzなんか書いてないで勉強しないといけないんですが。

※追記
早速ご指摘頂きました。



templateとか書かなくてもenum fizzbuzz ~ でいけるというdmd 2.064の新機能を使うようです。
さらにラムダを使うとgenFizzBuzzすら要らなかったという話。
究極的にはこれだけでいい
import std.conv;
enum fizzbuzz(ulong N) = (num => (num % 15 == 0)? "fizzbuzz":
(num % 3 == 0)? "fizz":
(num % 5 == 0)? "buzz":
num.to!string)(N);


D言語マジで頭おかしい

※更に追記
ラムダとか書かなくても3項演算子で代入する式書くだけだった!!!

0 件のコメント:

コメントを投稿