FizzBuzzを書く. 但し次の3つの制約を課す.それはつまり、 ・ループを使わない ・剰余を使わない ・条件分岐を使わない. ぜひこの出題者に直接色々問い詰めたいのだが、誰かがこの問題について 考えているのを横から見ていたにすぎないので、私はその出題者を知らない. 当然ながら、「ループを使わない」とは別の何かを使ってループを再現する のだろう.だとすればその再現方法はループとは呼ばないのだろうか. 呼ぶのだとしたら、この問題は手に負えなくなるので、呼ばない、とする のだろう.だとしたらどの程度まで? 一番やっかいなのが3つ目の条件「条件分岐を使わない」だろうけど、 条件分岐を例えばif (cond) { body } else { alt }
なのだとしたらcond ? body : alt
これは条件分岐? 今思いついたがif (cond) body;
はcond && body
などと書けるのだった.取り敢えず気づかなくて以下の様なコードを書いた.#Python j=lambda n,m:n in map(lambda x:x*m, range(1,n)) def l(n): print(["Fizz","Buzz","FizzBuzz",n][{True:2, False:{True:0,False:{True:1,False:3}[j(n,5)]}[j(n,3)]}[j(n,15)]]) map(l,range(1,30))
コレ以上短くできなくて、javascriptにしてみた.// js range=function(i,y){return {true:function(){return [i].concat(range(i+1,y))},false:function(){return [i]}}[i<y]()} j=function(n,m){return!!~range(1,n).map(function(x){return x*m}).indexOf(n)} range(1,30).map(function(n){console.log([a="Fizz",b="Buzz",a+b,n][{true:2,false:{true:0,false:{true:1,false:3}[j(n,5)]}[j(n,3)]}[j(n,15)]])})
ループを使わない以上再帰か何かを書くのだが、その度にfunctionと書くので 結局余計に長くなった.またpythonでは[1..10]をrange(1,10+1)などと書ける が、jsにはそのようなものはなく、自力で書いてしまった.この中でも条件 分岐を使わないことにしたのでさらにさらに長い さらにさらに、pythonでは Arrayがある要素を含めるかどうかを判定する述語 として x in array があるが、 javascriptではそのようなものは聞いたこと がなく、node.jsでダメ元で試したら全く同様に文法でそのようなものがあったが、 これはキーに就いての判定だったので、今は不要.キーというのは ここでは配列なので、[1,2,3]のキーは 0,1,2 である.> 2 in [0,1] false > 2 in [0,1,1] true
結局、何番目に含むかを返すindexOfの返り値を強引にbooleanにした.!!~array.indexOf(x)
8/25 追記 Array#some() というものがあった. 引数はfuctionで、 array.some(function(a){return x==a}) などと 使う.余計長い.それぞれの制約に対する解法を書くと、 ループはもちろん再帰でもいいが、再帰をすると終わらせる為の基底条件が 必須であり、そこで条件分岐を使う.今はできるだけ使いたくないので mapを使用した.つまりfor (i in array) do(i)
はarray.map(function(i){return do(i)})
に等しい 剰余を使ってはいけないというのは剰余を求める演算子、つまり たいていの言語で % か mod なんてので定義されてるものを使っては いけないと解釈.再帰すればfunction mod (n, m) { return n<m ? n : mod(n-m,m); }
これは n、mが生の整数なら正しく動く、がまた条件分岐である. このくらいなら条件分岐でも良かったかもだけど、私はこうした.n `elem` map [1 .. n] (\x → x * m)
つまり、mの倍数のリストを作ってその中にnが含まれるかを判定する. リストは無限列である必要はなく、nもmも1以上ならば、n*mを最大と しておけば十分である. 最後に条件分岐を使ってはいけないという制約. これはオブジェクト指向な部分を利用した. つまり、true, false というbooleanを組み込みに持つjavascriptで、 そのtrue,falseをキーとするオブジェクト(辞書型変数)を作ってしまう.var obj = { true : "#t", false : "#f" };
通常、true,falseの部分は予約語を使うべきでないように思えるが、 実際 obj[true], obj[false]でそれぞれの要素にアクセスできる. そして大事なこととして、キーに述語を入れてしまってalert(obj[1 == 1]); alert(obj[3 < 0]);
これは嬉しい予想どおりに動く. さてショートコーディングにおいて条件分岐は三項演算子 (?:) で書くのが 基本であるが、また次のような文法が利用できることもある、というのを 今思い出した.つまりif (cond) body;
はcond && body
に等しくif (! cond) alt;
はcond || alt
に等しい、 これは論理演算であるが、ifの非常に重要な性質を満たしており、 前に私の書いた true, false をキーとするオブジェクトはその性質 を持っていない(初めjavascriptのコードで私はオブジェクトの要素を 全てラムダ式に包んでいる).if (cond) A else B;
は Aが実行(評価)されるならば、Bは実行(評価)されず、逆もしかりである. これは言語の評価戦略などに関わらず(もしかしたらそういう言語もあるかも だけど)満たされる性質であり、評価戦略という言葉を出したのだから 言ってしまうと遅延評価のように見える. 例えば cond || alt は cond と alt の or を取るのだが、condがtrueならば、altはfalseでも trueでもどちらでも(cond||alt)はtrueを返す、という性質を処理系の 設計者がわかっていれば、altはそもそも評価されない.そして大抵の 処理系ではそうなっているだろう. else を伴うifを書きたい時、次を考える.(cond&&body)||body
condははじめに一度だけ必ず評価される.falseなら(cond .. body)がfalseなので ||の後ろが実行される.trueならbodyを評価し、そしていまここでbodyが常にtrueを返す
とする時、||の後ろは実行されない. これは通常の if .. else .. だ. 逆に(cond || alt) && body
これはaltが常にfalseを返す
時に正しく機能する. またそのbody,altの返り値を考えたくないならばカンマで並べて(cond .. body) , (cond .. alt)
としてもよいが、condは二度評価される. で、これを使えばもっと短く書けるのかな?
コメ(0) | トラ(0)