非常に稀に更新中


1 | 2 | 3 | 4 | 5 |oldest Next >>

【D言語】逆FizzBuzz問題を変態的に

2012-05-20 19:05:22 Theme: D言語
逆FizzBuzzというのがTwitterで流れてきたので解いてみました。
原文の日本語訳 http://d.hatena.ne.jp/matarillo/20120515/p1 

FizzBuzzというのは、簡単にいえば世界のナベアツ(現在は桂三度)のネタみたいな感じです。
ちゃんと説明すると、複数人で1から順番に数を言っていきます。
しかし、すこし条件があり、
・3の倍数の場合にはその数の代わりにfizz
・5の倍数の場合にはその数の代わりにbuzz
・3と5の倍数、すなわち15の倍数の時にはfizzbuzz
と言わないといけません。つまり、
1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, buzz, 16, 17....
という感じで続いていきます。
その人がプログラムが本当に書けるかどうかを判定するために使われることが多いようです。

今回はそのFizzBuzzの逆、つまりfizz, buzz...という入力から数列を見つけてきます。
詳しくは原文の日本語訳をお読みください。

さて、FizzBuzzは3の倍数, 5の倍数, 15の倍数に関するものです。
ですから、周期が15の周期関数であると考えることもできます。
つまり、15までについて判定すればよいということになります。
この解き方自体はみなさん簡単に思いつくんではないでしょうか。

他人が簡単に思いつくような解き方を綺麗サッパリ書いても面白く無いので、今回はD言語で";"を関数内にただ一つだけ含むinvFizzBuzz関数を書いてみました。
「";"を関数内にただ一つだけ含む」という意味は、invFizzBuzz関数が次のような形式であるということを意味します。
auto invFizzBuzz(){
    return ....... ;
}
よって、importによる";"や、main関数
void main(){
    invFizzBuzz;
}
の";"は個数には含みません。

「どう解く」か、より、「どう書く」かが難しくなりますが、
ラムダ/UFCS/Range
を大変活用して書くことによりなんとか。

ソースコードは以下になりますが、結果として大変変態なコードになりました。
Ideone : http://ideone.com/AnYfo

import std.algorithm;
import std.functional;
import std.range;
import std.stdio;
import std.string;
import std.typecons;

void main(){
    invFizzBuzz;
}

auto invFizzBuzz(){
    return stdin.byLine.map!(a => a.split)
    .map!(ln =>
        (a => (a > 6) ? [] : ((b, s) =>
                                        (c =>
                                            (c.popFrontN(b), c)
                                        )
                                        (
                                            recurrence!"a[n-1] + 1"(1)
                                            .map!(c => tuple(c, (!(c % 3) ? "fizz" : "") ~ (!(c % 5) ? "buzz" : "")))
                                            .filter!"a[1].length"
                                        )
                                        .take(s)
                                        .map!"a[0]"
                                        .array
                            )(a, ln.length)
        )
        (
            iota(0, 7)
            .map!(a =>
                tuple(
                    iota(1, 16).map!q{(!(a % 3) ? "fizz" : "") ~ (!(a % 5) ? "buzz" : "")}
                              .array
                              .cycle
                              .zip(recurrence!"a[n-1] + 1"(1))
                              .filter!"a[0].length",
                    a
                )
            )
            .map!(a => (a[0].popFrontN(a[1]), a))
            .map!(a =>
                tuple(
                    a[0].zip(ln).map!"a[0][0] == a[1]".reduce!"a && b",
                    a[1],
                    a[0].front[1],
                    a[0].take(ln.length).curry!(reduce!"max(a, b[1])", 0)
                )
            )
            .chain([tuple(true, 7, 0, 0)])
            .filter!"a[0]"
            .reduce!((a, b) => (a[3] - a[2]) <= (b[3] - b[2]) ? a : (b[1] == 7 ? a : b))[1]
        )
    )
    .map!(a => a.empty ? iota(0, 0) : iota(a[0], a[$-1] + 1))
    .map!(a => (a.writeln, 0)).reduce!"a + b";
}

入出力はこんな感じになります。
入力 → 出力
fizz → [3]
buzz → [5]
fizz buzz → [9, 10]
buzz fizz → [5, 6]
buzz buzz → []
fizz buzz fizz fizzbuzz fizz buzz fizz → [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
fizzbuzz fizzbuzz → []

このように最短解をちゃんと出力します。

結果的にラムダとUFCS,Rangeを多用した変態コードになりましたが、
別にD言語が変態というわけではなく、
D言語だと綺麗サッパリ書くことも出来ます。
ですから、この記事のソースコードをみて
D言語こわ...ちかよらんとこ...
とかそんなことは思わないでください。
D言語は良心的な紳士言語です。

AtCoder Regular Contest(β) #002(ARC #002)に参加してみた

2012-05-03 12:01:24 Theme: D言語
AtCoderという日本語の競技プログラミングコンテストが昨日の21時から1時間半あったので参加してみました。
リアルタイムでの競技プログラミングへの参加は実は今回が初めてでした。

20時47分 twitterにて

AtCoder待機。今回もD言語で

posted at 20:47:03


ってな感じで、47分から待機してました。
この13分は結構ドキドキしてました(笑)


そして
21時00分
AtCoder Regular Contest(β) #002(ARC #002)が開催されました。


A問題から順番に解いていくのが普通だろうということで、A問題からせっせと解いていきました。
問題文 : http://arc002.contest.atcoder.jp/tasks/arc002_1 
A問題はうるう年の問題なのですが、実はすぐには思いつきませんでした。
1,2分ぐらいどうしようかなーとか考えて、5分くらいかけてコード書くぐらい悩みました(笑)
緊張は怖いですね
以下が私の解答になりますが、とりあえず一発AC。(普通ですね)
http://arc002.contest.atcoder.jp/submissions/7096 


次にB問題へ。
B問題はA問題とすこし関連した問題でした。
問題文 : 
http://arc002.contest.atcoder.jp/tasks/arc002_2
Y ÷ M ÷ Dが割り切れる日付を答えるという問題です。
こっちはA問題ほど悩みませんでした。問題文を読み終えたら自然と手が動いた感じです。 
しかし、WAを一回出してしまいました。
ミスの場所はすぐに見つかりY ÷ M ÷ Dが割り切れるかどうかの条件分岐が少し甘かったようです。
そこを修正しましたらACでました。
以下が私のB問題のACの解答になります。
http://arc002.contest.atcoder.jp/submissions/7804 


次はC問題へ。
B問題を提出し終えた時刻は9時31分でした。
ということはD問題は難しすぎるため捨てるとして、C問題には1時間程度の時間をかけれました。
しかし、結果からいうと最後までC問題は解けませんでした。
C問題の問題文 : 
http://arc002.contest.atcoder.jp/tasks/arc002_3 
C問題の問題文を見た段階では解き方はすぐに思いつきましたが、
実際に25分かけてコードを書いて提出してみると、

ちょっwコンパイルエラーとかないわw

posted at 21:57:37(twitterより)

手元では一切でないのでたぶんDMD2.058と手元のDMD2.059の違いでしょう。
実はどこがエラーなのか詳細がわかることを知らなかった私はここで15分使ってしまいます(恥ずかしい)
そしてそのエラーの箇所を直したソースを提出しましたが、今度は
(この時点で残り16分ぐらい)

なんというランタイムエラー...

posted at 22:13:29

ごめんなさい。ランタイムエラーがいみわからない

posted at 22:18:50

結局ランタイムエラーがわからなかった。゚(゚´Д`゚)゚。

posted at 22:30:16


このランタイムエラーがコンテスト終了までわからず、最終的にC問題は解けませんでした。


結果は、朝確認したところ196位でした。
ちょっと残念ではありますね。
C問題も今朝、一から書きなおしたところ、思った通りにACがでました。
http://arc002.contest.atcoder.jp/submissions/9809 


初めての競技プログラミングということでしたが、凄く楽しめました。
次もAtCoder参加したいですね


コンパイル時式変形

2012-05-01 14:43:56 Theme: D言語
ええ、D言語のお話です。

DAizuというDが付く言語(D言語とかDartとか)の勉強会が会津大学であったようですね。
誰か近畿(大阪ぐらい)で開いてくれる人はいないかなーとか。(自分でひらけよとかナシで)
(学科の実験室借りて開くことは可能だとしてもまず人が集まらない)

さて改めてD言語についてどんな言語か簡潔に説明すると、
D言語とはコンパイル時にほげほげできる言語である。

コンパイル時にほげほげできるとは、DAizuで@youxkeiさんが発表されたスライド
(

https://dl.dropbox.com/s/qfx14qxbsrmcahy/IntroD.pdf )
(

https://dl.dropbox.com/s/91vllglxhb1h6r2/ctpg.pdf )
がすごく参考になるので、そちらを読んでもらうとわかりやすいと思います。

では、コンパイル時にほげほげするのに役に立つD言語の機能を並べてみます。
・template
・型推論
・is式
・static if
・__traits
・テンプレートmixinと文字列mixin
・CTFE(Compile Time Function Evaluation)
・強力な文字列操作(というより、配列(スライス)操作)
・タプル(タプルパラメータ)

ほとんどの説明が@youxkeiさんのスライドで説明されているのでここでは省きます。

Q.そのコンパイル時にほげほげすることを使っていったい何ができるのか。
A.まあなんでも出来ます
コンパイル時に動ける関数はコンパイル時に動いてしまうので結構なんでもできてしまいます。
上に挙げたものを使うとほんとうにコンパイル時になんでもできてしまいます。

例としては@youxkeiさんctpgが一番いいのですが、
私がしていることも今までこちらで紹介してきましたが、また紹介しますと、
コンパイル時に数学的な式の意味を変えずに式変形をする
主に行なっていることは行列,ベクトルの式変形になります。

Q.式変形なんてしてどういったメリットがあるんだよ!書いた通りに動かないとかデバッグしにくいだけじゃんか!
A.たしかにデバッグしにくいかもしれませんが、簡単な書き方ができたり、なおかつ計算量を減少させたりできます

たとえば、BLASを使って3x3行列AとBの積に3x3行列Cを加えるというコードを書いたとします。
普通にBLASを使えば、gemmを使用して
gemm('N', 'N', 3, 3, 3. 1, A.ptr, 3, B.ptr, 3, 1, C.ptr, 3)
writeln(C);
とかいう初心者には超絶難しい関数になります。
簡単にかけたとしても
gemm(1, A, B, 1, C).writeln();
で、gemmという関数がなにをしているか知らなければ、「一体このソースはなにをしているんだ」になります。
しかし、私の書いたライブラリでこれを実現するのは簡単です
(A * B + C).evalExpr.writeln();
はい、そのまま書いてるだけでgemmで動くんです。

仕組みとしましては、
まず演算子の優先順位からA * Bが評価されます。
このとき、A, Bともに一般行列でありますから、A * Bがgemmで計算されることが決まります。
よって、計算式は次のように書き換わります
Matrix!"gemm"(1, A, B) + C
ここで、Matrix!"gemm"(1, A, B)とは、gemmで1 * A * Bするんだけどまだ計算してないよっていうことを意味します。
次にMatrix!"gemm"(1, A, B) + Cが評価されますが、gemmはαAB + βCの形式のルーチンですから、さらに次のように式が変形されます。
Matrix!"gemm"(1, A, B, 1, C)
このMatrix!"gemm"(1, A, B, 1, C)は、「1 * A * B + 1 * Cという演算をした結果の行列」を表しているだけで、実際に計算されているわけではないので、
Matrix!"gemm"(1, A, B, 1, C).evalExpr
でgemmによる演算を実際に行います。

評価の手順の順を追うと、
(A * B + C).evalExpr

(Matrix!"gemm"(1, A, B) + C).evalExpr

Matrix!"gemm"(1, A, B, 1, C).evalExpr

gemm('N', 'N', 3, 3, 3. 1, A.evalExpr.array.ptr, 3, B.evalExpr.array.ptr, 3, 1, C.evalExpr.array.ptr, 3)  

また、(A * B * 3 + C * 5) * 2の場合は
((
A * B * 3 + C * 5 ) * 2).evalExpr

((Matrix!"gemm"(1, A, B) * 3 + Matrix!"scal"(5, C)) * 2).evalExpr

((Matrix!"gemm"(3, A, B) + Matrix!"scal"(5, C)) * 2).evalExpr

(Matrix!"gemm"(3, A, B, 5, C) * 2).evalExpr

Matrix!"gemm"(6, A, B, 10, C).evalExpr

gemm('N', 'N', 3, 3, 3. 6, A.evalExpr.array.ptr, 3, B.evalExpr.array.ptr, 3, 10, C.evalExpr.array.ptr, 3) 

実装としてはもう少し複雑なのですが、だいたいこんな感じです。
最終的にはやたらと変なことになってるように思いますが、すべてライブラリが自動的にするのでユーザーは考える必要はありません
(作る私はシンドイのですが)
重要なのはライブラリのユーザーは

(A * B * 3 + C * 5) * 2 と書くだけということです

こんなのできて何がうれしいの?ってことですが、
・だれでも簡単にBLAS(やLAPACK)の行列演算が使える
・型に守られているためコンパイル時にエラー検出
・実行時にも行列の大きさなどでエラー検出(たとえば3*3行列と2*2行列の足し算)
・それなりに速度を考えた実装
・遅延評価(というより計算を遅延させるだけ)のため、必要な計算だけ必要なときに実行

しかし、計算手順をまったく変えてしまう場合があるので、「精度をかんがえてコーディングしたのにまったく別の式になってしまい、精度がダメダメになった」という事象が大きく響きます。
科学計算で精度は重要です。
しかし、厳密な精度が欲しいひとがBLASのルーチンを知らないわけがなく、そういった人はこのライブラリの目的である「誰でも簡単に行列演算ができる」というものの対象外であると私は考えています。
それにそんな人はD言語なんて使わずにFortran使うでしょうし。

つまりこのライブラリは、「学校の宿題で2次関数を最小二乗法で近似しないといけない」ような人向けであります。(過去の私のことです)

需要があるかどうかは知りません。
そもそもD言語で書いてるあたり、もう需要なんてどうでもいいと思ってるのかもしれません。

いや、違いますね。このようなライブラリはD言語でしか書けませんね。
D言語の強力なコンパイル時の処理があるからこそ、このようなライブラリが書けます。


Amebaおすすめキーワード

    1 | 2 | 3 | 4 | 5 |oldest Next >>
    アメーバに会員登録して、ブログをつくろう! powered by Ameba (アメーバ)|ブログを中心とした登録無料サイト