Skip to content

Instantly share code, notes, and snippets.

@plaster
Created December 15, 2012 07:21
Show Gist options
  • Save plaster/4291907 to your computer and use it in GitHub Desktop.
Save plaster/4291907 to your computer and use it in GitHub Desktop.
Project Euler Problem 9 solution in Clojure (#mitori_clj)
(use 'clojure.test)
;; ピタゴラス数チェック
(defn pythagorean?
[a b c]
(= (* c c) (+ (* a a) (* b b))))
(is (pythagorean? 3 4 5))
;; 合計が a+b+c かつ 0 < a < b < c になるような組の列挙
;; もう少し効率的に枝刈りできるかもしれない
(defn candidates
[a+b+c]
(for [c (range (+ a+b+c 1))
:let [a+b (- a+b+c c)]
b (range (+ a+b 1))
:let [a (- a+b b)]
:when (< 0 a b c)
]
[ a b c ]
))
(is (every? #(= 1000 (apply + %)) (candidates 1000)))
;; "(a,b,c)の組および解答フォーム入力用の積"
(defn solve [n]
(for [abc (candidates n)
:when (apply pythagorean? abc) ]
[ abc (apply * abc) ]))
;; (solve 1000) => ([[200 375 425] 31875000])
;; ピタゴラス数チェック
(defn pythagorean?
[a b c]
(= (* c c) (+ (* a a) (* b b))))
(is (pythagorean? 3 4 5))
;; 合計が a+b+c かつ 0 < a < b < c になるような組の列挙
;; もう少し効率的に枝刈りできるかもしれない
(defn candidates
[a+b+c]
(for [c (range (+ a+b+c 1))
:let [a+b (- a+b+c c)]
b (range (+ a+b 1))
:let [a (- a+b b)]
:when (and (< 0 a b c) (pythagorean? a b c))
]
[ a b c ]
))
(is (every? #(= 1000 (apply + %)) (candidates 1000)))
;; pe-9.core=> (time (apply list (candidates 10000)))
;; "Elapsed time: 6183.66497 msecs"
;; @kohyamaさんアドバイス版
(defn candidates2
[a+b+c]
(for [a (range 1 (quot (inc a+b+c) 3))
:let [b+c (- a+b+c a)]
b (range (inc a) (quot (inc b+c) 2))
:let [c (- b+c b)]
:when (and (< 0 a b c) (pythagorean? a b c))
]
[ a b c ]
))
;; pe-9.core=> (time (apply list (candidates2 10000)))
;; "Elapsed time: 1469.798884 msecs"
;; ([2000 3750 4250])
;; "(a,b,c)の組および解答フォーム入力用の積"
(defn solve [n]
(for [abc (candidates n) ]
[ abc (apply * abc) ]))
;; (solve 1000) => ([[200 375 425] 31875000])
@tnoda
Copy link

tnoda commented Dec 18, 2012

@emanon001

(time (solve 1000)) ; "Elapsed time: 0.85 msecs" とか出るが、どう考えても 5分くらいかかっている。

solve 関数が LazySeq を返しているからだと思います.試しに REPL で

  • (type (first (solve 1000)))
  • (type (solve 1000))

を実行して結果を確認してみたらどうなりますか?

clojure.lang.LazySeq なら,まだ計算は実行されていません.LazySeq の中身には (fn [] (solve 1000)) が入っていて,first されてはじめて,その fn が実行されて (solve 1000) が計算される,というのが私のイメージです.

@emanon001
Copy link

@tnoda
あー、そうか。LazySeq を返していて、それに対して何の操作も行なっていないなら、そりゃ計算は実行されないですね……。
REPL で評価結果を表示するなど、LazySeq から値を取得する必要が生じて初めて計算が実行される、というわけですね。
ああ、この辺り理解したつもりになっていたけど、まだ勉強が足りていない……。

@plaster
Copy link
Author

plaster commented Dec 19, 2012

@tnoda これを質問するなら -main も貼っておくべきでした。失礼しました。

(defn -main
  [& args]
  (println (solve 1000))
  )

これだったのですが、中身を 0 とかにしても同じReflection warning が出たので、確かにleiningenの中っぽいです。
頂いた例 #(Character/digit % 10) をソースにいれてみたところ

Reflection warning, pe_9/core.clj:54 - call to digit can't be resolved.

のように、ちゃんと出てきました。ありがとうございます。

あと、今までどうも勘違いしていたのですが、「reflectionを使うようにコンパイルされる場合に(コンパイル時に)警告」ではなく、「reflectionを使ったら(ランタイムに)警告」なのですね。

@tnoda
Copy link

tnoda commented Dec 20, 2012

@plaster

あと、今までどうも勘違いしていたのですが、「reflectionを使うようにコンパイルされる場合に(コンパイル時に)警告」ではなく、「reflectionを使ったら(ランタイムに)警告」なのですね。

私の書き方がまぎらわしくて誤解させていたのならごめんなさい.警告はコンパイル時に出ていると思います.ここでいうコンパイルとは,

.clj → コンパイル → バイトコード (.class) → 実行

という手順に出てくるコンパイルという意味です.

ためしに,リフレクションを使うような defn を REPL で eval ってみてください.defn で定義される関数を実行しなくても(= リフレクションを使わなくても),警告されると思います.

@plaster
Copy link
Author

plaster commented Dec 20, 2012

確かに、実行時ではなかったです。ありがとうございます。

pe-9.core=> (defn f [x] (Math/abs x))
Reflection warning, NO_SOURCE_PATH:1 - call to abs can't be resolved.
#'pe-9.core/f

では警告が1度だけ出るのに、中身を使う

pe-9.core=> (apply list (map f [1 -2.2 3.33 -4.444]))
(1 2.2 3.33 4.444)

では一度も出ませんでした。

オーバーロードの解決ができないときにreflectionが使われるみたいですね。Mathは特に速いことが望まれる場面多そうですし、一方で long と double のオーバーロードだらけなので、注意が要りそうです。

私が「コンパイル時に警告」でないと思ったのは lein compile で警告が出なかったためだったのですが、
rm -r target; lein compile したところ、期待どおり

Compiling pe-9.core
Reflection warning, pe_9/core.clj:54 - call to abs can't be resolved.

の警告メッセージを得ることができました。
しかも、最初に私が悩んだ

Reflection warning, NO_SOURCE_PATH:1 - call to invokeStaticMethod can't be resolved.

は出ていないので、こちらは私が書いた部分のコードには直接関係ない警告メッセージだったというのも確定ですね。
いろいろすっきりしました、本当にありがとうございます。

@tnoda
Copy link

tnoda commented Dec 21, 2012

@plaster

オーバーロードの解決ができないときにreflectionが使われるみたいですね。Mathは特に速いことが望まれる場面多そうですし、一方で long と double のオーバーロードだらけなので、注意が要りそうです。

もう一つ付け加えると,メソッド呼び出しのレシーバのクラスを型推論できないときも reflection を使うみたいです.たとえば,

user> (defn str* [x] (.toString x))
Reflection warning, NO_SOURCE_FILE:1 - reference to field toString can't be resolved.
#'user/str*

で,これも,

user> (defn date->str [^java.util.Date x] (.toString x))
#'user/date->str

のように型ヒントをつけてあげると怒られなくなりますね.

@plaster
Copy link
Author

plaster commented Dec 22, 2012

@tnoda

もう一つ付け加えると,メソッド呼び出しのレシーバのクラスを型推論できないときも reflection を使うみたいです.

16を解いているときにちょうど疑問に思っていた内容で、大変参考になりました。ありがとうございます。
詳しくはあちらに書きます(が、URL未アナウンスです……)。

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