Skip to content

Instantly share code, notes, and snippets.

@ponkore
Created December 7, 2012 13:29
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ponkore/4233289 to your computer and use it in GitHub Desktop.
Save ponkore/4233289 to your computer and use it in GitHub Desktop.
Project Euler Problem 1
;;; 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 +))))
@bouzuya
Copy link

bouzuya commented Dec 8, 2012

problem-1 とぼくの解答 との差異とか。

  • 自然数に0を含めたくないのであれば別ですが。(range 1 n) でも (range n) でも大差ないです。2文字短くなる。
  • (reduce +) でなく (apply +) の方が1文字短くなる。

problem-1-2

  • ->> がお好きなんですね。
  • あとから (filter first) のために bool つけるくらいなら最初から filter でよいのでは?
    (filter #(or (zero? (mod % 3)) (zero? (mod % 5))) (iterate inc 1)) ; 動かなかったらごめんなさい。
  • 結局 (take-while #(< % 1000) s) とかするなら (iterate inc 1)(range 1 1000) の方がいい気がしてきます。
  • 引数ありとなしとで動作が違いすぎるので、(reduce + ...) は引数ありの側に置いたほうがよいかと

@ponkore
Copy link
Author

ponkore commented Dec 8, 2012

problem-1-1

  • おそらく bouzuya さんの解答のほうが正攻法だと思います。reduce / apply とか ->> とかは趣味ということで。
  • (range 1 n)(range n) は問題に対する解答の正確性に関わる話なので、結果オーライではだめだ、と自分は考えます(あくまで自分の意見なので参考程度に)。

problem-1-2

  • こちらはあまり突っ込まれたくないような...(笑) メモリも食うし効率も悪い。
  • こんなやり方も出来るよ、ということを書いてみただけだったりします。いろいろ指摘いただいたようで、少し申し訳ないような...。
  • 最後の (reduce + ...) の件はご指摘の通り、私の単純ミスです。直しました。

@tnoda
Copy link

tnoda commented Dec 8, 2012

ヘルパー関数や値に名前をつけるのは良いプログラミング習慣だと思います > fizz-or-buzz? とか fizzbuzz-seq とか.

後でコードを読み直したり,書いた本人以外の人が読むときに大きな助けとなりますね.と偉そうなことを言っている私自身今まであまりそういうことをしてこなかったので, #mitori_clj ではなるべくそうしていこうと思います.


あと, @ponkore の problem-1-2 も #mitori_clj という勉強会の中では ok だと思います.「こんなやり方もできるよ」というのも,関数の使い方や,解法パターンの紹介という意味で,非常に参考になります.私も,problem-1-2 でやっているのと同じように, xcoll[x' flag] のシーケンスに map してから flag をキーに filter するというパターンをよく使っていました.ただし,最近は,xx'nil かに変換する関数 f を用意して (keep f coll) というのをよく使っています.若干コードがコンパクトになるのと,途中でベクタのシーケンスを作らない分,性能面で有利だからです.

@emanon001
Copy link

  • ソースコード内に解説を記述するのも良いかもしれないですね。(Gist さん、コメントの修正ができなくて焦った)
  • 可変長引数を取ることのできる関数に reduceapply のどちらを使うかは悩みますね。僕は、シーケンスを受け取る場合は一貫して reduce を使うようにしています。

@bouzuya
Copy link

bouzuya commented Dec 9, 2012

ponkore へ

結果オーライというわけではありませんし、正確性も欠いていません。

問題では自然数(natural number)と書かれており、正の整数などとは記述されていません。自然数に 0 を含むかどうかは場合によります。今回の場合は、どちらを選択しても解答に影響はありませんし、パフォーマンスにも大差はありません。

ですので、ぼくは記述がより簡潔になる (range n) にしています。

余談ですが、シーケンスに 0 を含むことによるパフォーマンスへの影響がないとは言えませんが、そこを考慮するくらいなら、そもそもシーケンスを生成しない解法を選択すべきでしょうね。きっと。

emanon001 へ

reduce はまとめる意味合いが強く出るので良いですね。

問題とは直接関係はありませんが、個人的には reduce を書くときは (reduce f coll) ではなく (reduce f val coll) を使いたくなります。今回は (reduce + coll) で十分ですが、(reduce + 0 coll) のように書きたいです。理由は最初だけ coll から二要素をとる動作になじめないからです。

@kohyama
Copy link

kohyama commented Dec 9, 2012

解答としては bouzuya さんのコードが文句無し模範解答と思います.

bouzuya さんが突っ込んでいるように, この問題に使うには少々おおげさなところが多いですが,
tnoda さんが言うように, もっと大きな問題で使われるようなイディオムが含まれていて,
参考になります.

コメントや doc-string が, きちんと書いてあるのは良いですね.
私も見習わなくては...

apply +reduce + かは, 宗教戦争になりかねないので,

...敢えて宗教戦争に参加すると :-), この場合は apply + の方が抽象的で良いと思います.

@ponkore
Copy link
Author

ponkore commented Dec 9, 2012

bouzuya さんへ

  • 正確性を欠いていたのはどうやら自分のほうでした。私のようなおっさん世代は、自然数には0は含まないと教えられているのです。bouzuya さんに指摘いただくまで、微塵も疑いませんでした。気分を害されたのならすみません。
  • 自分はこういった場での発言とかでへたを打つことが多いので、気をつけたいと思います。今はメンバーも少ないですが、Clojure コミュニティを広げるため、初心者も入ってきやすくするために、なるべくほんわかした感じで行きましょう。

@plaster
Copy link

plaster commented Dec 9, 2012

初めまして。->> 便利そうですね。私こういうの大好きです。

fizz-or-buz?述語があるとの事前情報があったので、シーケンスをフィルタする解なのだろうと思っていました。
どうやったら「別解」になるかなあなどと思いながら、私も解答つくってみました、、、が、かなりnaiveです。setの練習ですね。
https://gist.github.com/4245866 (ここを見てからだいぶ直したのは秘密です。履歴でばれますが)

あと、bouzuyaさん指摘の 3引数reduce、私もこの挙動が好きです。最初foldという名前で探していたのですが、
reduceのオーバーロード(?)で実現されていたのをみつけてびっくりしました。

@tnoda
Copy link

tnoda commented Dec 11, 2012

@ponkore 少々正確でなくても萎縮せずにどんどんコメントを書ける雰囲気のほうが,初心者には入りやすいと思いますので,お互い,これからどんどん間違えましょう.あとからでもやり直しと修正が効きますから(とフラグを立ててみる).

@plaster 別解ありがとうございます.fold についてはごめんなさい,#mitori_clj の Clojure 1.4.0 なんです.1.5.0 になると Reducers の fold 関数が登場するのですが,それまでは,代わりに reduce を使ってくださいね.

@tnoda
Copy link

tnoda commented Dec 11, 2012

ちなみに私は (apply + coll) 派ですが,この場合は apply で書いても reduce で書いてもどちらでもいいと思います.その理由は,...と書きはじめたら長くなったので別記事にまとめてみました.ご参考まで. > http://tnoda-clojure.tumblr.com/post/37700304493/apply-and-reduce

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment