Arantium Maestum

プログラミング、囲碁、読書の話題

Clojure入門 - Project Eulerを解いてみる 問2

第二問

ネタバレ

(comment
    まずは超基本的な関数から。
    このレベルなら本当はfnで匿名関数にしてもいいかも
    少し面白いのはleが無名関数を返す高次関数であるところか)

(defn le [n]
    (fn [x] (<= x n)))

(defn even [x]
    (= 0 (mod x 2)))

(comment
    fibonacciを計算するのに、
    まず2-element vector [v0, v1]を入力として
    [v1, v0+v1]を出力として返すnext-fib-vec関数を用意)
(defn next-fib-vec [v] 
    [(v 1) (+ (v 0) (v 1))])

(comment
    iterateを使ってn, f(n), f(f(n))...
    と無限に続く2-element vectorのlazy seqを作成)
(def fib-vec
    (iterate next-fib-vec [1 1]))

(comment
    2-element vectorの最初のelementのみ摘出して
    無限に続くfibonacciのlazy sequenceを作成)
(def fib
    (map first fib-vec))

(comment
    あとは問題文に従って、400万以下のfibのうち
    偶数のものをすべて足し合わせて結果を表示するのみ)
(println
    (reduce +
        (filter even
            (take-while
                (le 4000000)
                fib))))

第二問にして、Pythonとは相当違う書き方になった。Pythonだったらノータイムでfibを以下のようなステート有りのジェネレータにしている。

def fib():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a+b

Clojureではステートはないのでiterateを使って無限に続く二つの数字のvectorを作成。iterateはStackOverflowでfactorialの問題から発見。

まあよく見ると根本的な考えは同じだが・・・

気になった点としては、匿名関数fnletではなくdefdefnで直接命名してしまっていること。はじめは匿名関数でやってみようとするんだが、なんとなく読みにくく感じて明示的に名前を付けてしまう。これは慣れの問題なのだろうか・・・

あと、改行もどこでやるのがいいのか。改行の位置が決まればインデントは比較的明快に決まるのだが、例えば(reduce +を二行にわけたほうがいいのか・・・ +の部分の関数が多少複雑なら問答無用で改行するが、一語(あるいは一文字!)の場合はむしろバランスが悪いように思えてしまう。reduce +filter evenはほとんど一つの関数のように感じてしまうのも一因か。

このようなもやもやは、模索しながらコードを書いてみてその上でいいコードを読むことで改善していくものだろう。今年は意識的にidiomatic clojureにたくさん触れていこうと思う。