-
-
Save emanon001/4292285 to your computer and use it in GitHub Desktop.
;;; Project Euler Problem 6 | |
;;; 「二乗の和と和の二乗の差はいくつか?」 | |
;;; http://odz.sakura.ne.jp/projecteuler/index.php?cmd=read&page=Problem%206 | |
(use 'clojure.test) | |
(defn sum-of-squares | |
"与えられた数列についてその二乗の和を返します" | |
[nums] | |
(apply + (map #(* % %) nums))) | |
(defn square-of-sum | |
"与えられた数列についてその和の二乗を返します" | |
[nums] | |
(apply * (repeat 2 (apply + nums)))) | |
(defn solve | |
"最初の n 個の自然数について、二乗の和と和の二乗の差を返します" | |
[n] | |
(->> (range 1 (inc n)) | |
((juxt square-of-sum sum-of-squares)) | |
(apply -))) | |
(is (= (solve 10) 2640)) | |
(time (solve 100)) |
@kohyama
私の解を読み返したところ、(range 1 (inc n))
を見事に2度書いてしまっていました。
ふと気になったのですが、同じ遅延シーケンスを2箇所で順に使おうとすると、片方で使うために計算し終えた要素すべて、もう片方で使うまでメモリに持つことになりますよね。let
でも juxt
でもきっとそうだと思います。
とすると「シーケンスの各要素の計算が高コストなのでメモリを使ってでも二度は計算したくない」のか、 「(もしかしたらものすごく長い)繰り返しの抽象化として遅延シーケンスを使っているのか」で扱いを変えたほうがよい気がします。後者でやりたい場合は「シーケンスを生成する関数」でやればよいでしょうか。
(let [sq #(* % %)
gen-ls #(range 1 (inc n))]
(- (sq (apply + (gen-ls)))
(apply + (map sq (gen-ls)))))
これを @tnoda さんの例にならって関数合成で書いてみたらこんな感じでしょうか。
(let [solve (let [sq #(* % %)
sum (partial apply +)
gen-ls #(range 1 (inc %))]
(comp (partial apply -)
(juxt (comp sq sum gen-ls)
(comp sum (partial map sq) gen-ls))
))]
...
)
もうちょっとシンプルになりそうな気もするのですが……
重要な理由として DRY (Don't Repeat Yourself) 原則があります.
同じことをするコードを複数書いてしまうと, 実装を変更する時にそれら全てを更新しなければならないので良くありません.
#(* % %)
の部分や (range 1 (inc n))
がもっと大きなコードであった場合を想定し, 一度しか書かないためのイディオムを演習しておくと良い. と思います.
その上で, 抽象化しようとしているものの性質
「シーケンスの各要素の計算が高コストなのでメモリを使ってでも二度は計算したくない」のか、 「(もしかしたらものすごく長い)繰り返しの抽象化として遅延シーケンスを使っているのか」
で扱いを変えたほうが良いというのは, なるほど. 確かにおっしゃる通りですね.
今後, 意識してみようと思います.
前者については, Problem 14 の方で話題にあがっている「メモ化」も参考になるかと思います.
juxt
は面白い関数なのですが、使うのにちょうど良い場面になかなか遭遇しない(ぼくが気づいていない)ですね。
うまく使えている例だと思います。
👍 ポイントフリー万歳
ですが念のため,
初学者向け解説
「1から与えられた数
n
までの自然数の, 二乗の和と和の二乗の差はいくらか」ということなのでを計算すれば良いわけですが, 上記のままだと
#(* % %)
と(range 1 (inc n))
をそれぞれ二回直接書いていますので, 宜しくありません.let
を使ってとかけますが, 上記では, 他の書き方について検討しています.
特に
(range 1 (inc n))
部分をlet
を使わないで,#(- (sq (apply + %) (apply + (map sq %))
に相当する関数を合成して, 引数として一度だけ渡すやり方などです.