Skip to content

Instantly share code, notes, and snippets.

@emanon001
Created December 15, 2012 09:08
Show Gist options
  • Select an option

  • Save emanon001/4292285 to your computer and use it in GitHub Desktop.

Select an option

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))
@plaster
Copy link
Copy Markdown

plaster commented Dec 17, 2012

juxtおもしろいです。多値がないからこそのコンビネータですね。compと(あと、ある意味でmapとも)対な感じの立ち位置が素敵です。

@emanon001
Copy link
Copy Markdown
Author

これは個人の感想であり感じ方には個人差がありますが, ->> の中で juxt 使うと括弧が二重になるので気持ち悪いです.そして,よく二重にするのを忘れて怒られます.

これは僕も気持ち悪いと感じています。(( ... ) x) だとそれほどですが、(( ... )) だと違和感をかなり感じます。

と、いうことで comp を使って書き直しました。

(defn solve
  "最初の n 個の自然数について、二乗の和と和の二乗の差を返します"
  [n]
  ((comp (partial apply -)
         (juxt square-of-sum sum-of-squares)
         (partial range 1))
     (inc n)))

僕はこちらの方が感覚として読みやすいです。

@tnoda
Copy link
Copy Markdown

tnoda commented Dec 18, 2012

と、いうことで comp を使って書き直しました。

ポイントフリースタイル憧れますよね.

(let [solve (comp (partial apply -)
                  (juxt square-of-sum sum-of-squares)
                  range
                  inc)]
  ... )

という書き方はいかがでしょう.私はよくヘルパー関数をこの形でかきます.

@kohyama
Copy link
Copy Markdown

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
Copy Markdown

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
Copy Markdown

kohyama commented Dec 21, 2012

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

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

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

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

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

@bouzuya
Copy link
Copy Markdown

bouzuya commented Jan 13, 2013

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

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

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