Skip to content

Instantly share code, notes, and snippets.

@emanon001
Created December 15, 2012 09:08
Show Gist options
  • Save emanon001/4292285 to your computer and use it in GitHub Desktop.
Save emanon001/4292285 to your computer and use it in GitHub Desktop.
Project Euler Problem 6 #mitori_clj
;;; 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
Copy link

kohyama commented Dec 19, 2012

👍 ポイントフリー万歳

ですが念のため,

初学者向け解説

「1から与えられた数 n までの自然数の, 二乗の和と和の二乗の差はいくらか」ということなので

(- (#(* % %) (apply + (range 1 (inc n))))
   (apply + (map #(* % %) (range 1 (inc n)))))

を計算すれば良いわけですが, 上記のままだと #(* % %)(range 1 (inc n)) をそれぞれ二回直接書いていますので, 宜しくありません.
let を使って

(let [sq #(* % %)
      ls (range 1 (inc n))]
  (- (sq (apply + ls))
     (apply + (map sq ls))))

とかけますが, 上記では, 他の書き方について検討しています.

特に (range 1 (inc n)) 部分を let を使わないで,
#(- (sq (apply + %) (apply + (map sq %)) に相当する関数を合成して, 引数として一度だけ渡すやり方などです.

@plaster
Copy link

plaster commented Dec 20, 2012

@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))
                    ))]
  ...
  )

もうちょっとシンプルになりそうな気もするのですが……

@kohyama
Copy link

kohyama commented Dec 21, 2012

重要な理由として DRY (Don't Repeat Yourself) 原則があります.
同じことをするコードを複数書いてしまうと, 実装を変更する時にそれら全てを更新しなければならないので良くありません.
#(* % %) の部分や (range 1 (inc n)) がもっと大きなコードであった場合を想定し, 一度しか書かないためのイディオムを演習しておくと良い. と思います.

その上で, 抽象化しようとしているものの性質

「シーケンスの各要素の計算が高コストなのでメモリを使ってでも二度は計算したくない」のか、 「(もしかしたらものすごく長い)繰り返しの抽象化として遅延シーケンスを使っているのか」

で扱いを変えたほうが良いというのは, なるほど. 確かにおっしゃる通りですね.
今後, 意識してみようと思います.

前者については, Problem 14 の方で話題にあがっている「メモ化」も参考になるかと思います.

@bouzuya
Copy link

bouzuya commented Jan 13, 2013

juxt は面白い関数なのですが、使うのにちょうど良い場面になかなか遭遇しない(ぼくが気づいていない)ですね。

うまく使えている例だと思います。

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