-
-
Save ponkore/4233289 to your computer and use it in GitHub Desktop.
;;; Project Euler Problem 1 | |
;;; http://odz.sakura.ne.jp/projecteuler/index.php?cmd=read&page=Problem%201 | |
;;; | |
;;; 10未満の自然数のうち、3 もしくは 5 の倍数になっているものは 3, 5, 6, 9 の4つがあり、 これらの合計は 23 になる。 | |
;;; 同じようにして、1,000 未満の 3 か 5 の倍数になっている数字の合計を求めよ。 | |
;;; 3 もしくは 5の倍数の場合 true、そうでない場合 false を返す filter 判定用関数 | |
(defn fizz-or-buzz? | |
"3か5で割り切れるならtrue、そうでないならfalseを返す。" | |
[n] | |
(or (zero? (mod n 3)) (zero? (mod n 5)))) | |
(defn problem-1 | |
"Projec Euler Problem#1" | |
([] (problem-1 1000)) | |
([n] (->> (range 1 n) (filter fizz-or-buzz?) (reduce +)))) |
;;; Project Euler Problem 1 | |
;;; http://odz.sakura.ne.jp/projecteuler/index.php?cmd=read&page=Problem%201 | |
;;; | |
;;; 10未満の自然数のうち、3 もしくは 5 の倍数になっているものは 3, 5, 6, 9 の4つがあり、 これらの合計は 23 になる。 | |
;;; 同じようにして、1,000 未満の 3 か 5 の倍数になっている数字の合計を求めよ。 | |
;;; iterate で繰り返すことができるよう、一つの引数を取り次へとつながるパラメータを戻り値として返す、 | |
;;; シーケンスジェネレータっぽいやつを作る。 | |
(defn fizzbuzz-seq | |
[[result n]] | |
(let [incn (inc n)] | |
[(or (zero? (mod incn 3)) (zero? (mod incn 5))) (inc n)])) | |
;;; テスト | |
;(take 10 (iterate fizzbuzz-seq [false 1])) | |
;=> ([false 1] [false 2] [true 3] [false 4] [true 5] [true 6] [false 7] [false 8] [true 9] [true 10]) | |
;;; それをフィルタする。良い感じ。 | |
;(->> (take 10 (iterate fizzbuzz-seq [false 1])) (filter first)) | |
;=> ([true 3] [true 5] [true 6] [true 9] [true 10]) | |
;;; 答え | |
(defn problem-1-2 | |
([] (problem-1-2 1000)) | |
([n] (->> (iterate fizzbuzz-seq [false 1]) | |
(take n) | |
(filter first) | |
(map second) | |
(reduce +)))) |
ヘルパー関数や値に名前をつけるのは良いプログラミング習慣だと思います > fizz-or-buzz?
とか fizzbuzz-seq
とか.
後でコードを読み直したり,書いた本人以外の人が読むときに大きな助けとなりますね.と偉そうなことを言っている私自身今まであまりそういうことをしてこなかったので, #mitori_clj ではなるべくそうしていこうと思います.
あと, @ponkore の problem-1-2 も #mitori_clj という勉強会の中では ok だと思います.「こんなやり方もできるよ」というのも,関数の使い方や,解法パターンの紹介という意味で,非常に参考になります.私も,problem-1-2 でやっているのと同じように, x
の coll
を [x' flag]
のシーケンスに map
してから flag
をキーに filter
するというパターンをよく使っていました.ただし,最近は,x
を x'
か nil
かに変換する関数 f
を用意して (keep f coll)
というのをよく使っています.若干コードがコンパクトになるのと,途中でベクタのシーケンスを作らない分,性能面で有利だからです.
- ソースコード内に解説を記述するのも良いかもしれないですね。(Gist さん、コメントの修正ができなくて焦った)
- 可変長引数を取ることのできる関数に
reduce
とapply
のどちらを使うかは悩みますね。僕は、シーケンスを受け取る場合は一貫してreduce
を使うようにしています。
ponkore へ
結果オーライというわけではありませんし、正確性も欠いていません。
問題では自然数(natural number)と書かれており、正の整数などとは記述されていません。自然数に 0 を含むかどうかは場合によります。今回の場合は、どちらを選択しても解答に影響はありませんし、パフォーマンスにも大差はありません。
ですので、ぼくは記述がより簡潔になる (range n)
にしています。
余談ですが、シーケンスに 0 を含むことによるパフォーマンスへの影響がないとは言えませんが、そこを考慮するくらいなら、そもそもシーケンスを生成しない解法を選択すべきでしょうね。きっと。
emanon001 へ
reduce はまとめる意味合いが強く出るので良いですね。
問題とは直接関係はありませんが、個人的には reduce を書くときは (reduce f coll)
ではなく (reduce f val coll)
を使いたくなります。今回は (reduce + coll)
で十分ですが、(reduce + 0 coll)
のように書きたいです。理由は最初だけ coll から二要素をとる動作になじめないからです。
解答としては bouzuya さんのコードが文句無し模範解答と思います.
bouzuya さんが突っ込んでいるように, この問題に使うには少々おおげさなところが多いですが,
tnoda さんが言うように, もっと大きな問題で使われるようなイディオムが含まれていて,
参考になります.
コメントや doc-string が, きちんと書いてあるのは良いですね.
私も見習わなくては...
apply +
か reduce +
かは, 宗教戦争になりかねないので,
...敢えて宗教戦争に参加すると :-), この場合は apply +
の方が抽象的で良いと思います.
bouzuya さんへ
- 正確性を欠いていたのはどうやら自分のほうでした。私のようなおっさん世代は、自然数には0は含まないと教えられているのです。bouzuya さんに指摘いただくまで、微塵も疑いませんでした。気分を害されたのならすみません。
- 自分はこういった場での発言とかでへたを打つことが多いので、気をつけたいと思います。今はメンバーも少ないですが、Clojure コミュニティを広げるため、初心者も入ってきやすくするために、なるべくほんわかした感じで行きましょう。
初めまして。->>
便利そうですね。私こういうの大好きです。
fizz-or-buz?
述語があるとの事前情報があったので、シーケンスをフィルタする解なのだろうと思っていました。
どうやったら「別解」になるかなあなどと思いながら、私も解答つくってみました、、、が、かなりnaiveです。setの練習ですね。
https://gist.github.com/4245866 (ここを見てからだいぶ直したのは秘密です。履歴でばれますが)
あと、bouzuyaさん指摘の 3引数reduce、私もこの挙動が好きです。最初foldという名前で探していたのですが、
reduceのオーバーロード(?)で実現されていたのをみつけてびっくりしました。
ちなみに私は (apply + coll)
派ですが,この場合は apply
で書いても reduce
で書いてもどちらでもいいと思います.その理由は,...と書きはじめたら長くなったので別記事にまとめてみました.ご参考まで. > http://tnoda-clojure.tumblr.com/post/37700304493/apply-and-reduce
problem-1-1
(range 1 n)
と(range n)
は問題に対する解答の正確性に関わる話なので、結果オーライではだめだ、と自分は考えます(あくまで自分の意見なので参考程度に)。problem-1-2
(reduce + ...)
の件はご指摘の通り、私の単純ミスです。直しました。