d(n)をn未満の真の約数の和としたとき、d(a)=b かつ d(b)=a であるようなa とbは友愛数と呼ばれます。 10000以下の全ての友愛数の和を求めなさい。
pe_21_1.cljは以前解いたときのものです。pe_21_2.cljは改めて見てみて気に 入らないところを直したものです。
対象となる数それぞれの約数の和を求めるわけですが、約数そのものを求めな くても約数の和を求めることができます。Wikipediaとか こことかに解説があり ますが、その数を素因数分解したものを使います。
素因数分解するために作った関数が、factorsです。 小さい素数から順に割れるだけ割って、次の素数を調べます。
次に、上の方法で必要な数列を作ります。 3^4であれば、(3^0+3^1+3^2+3^3+3^4)=(1+3+9+27+81) が欲しいのですが、こ れを作るのが、multi-plusです。
素因数分解した各成分についてそれぞれmulti-plusを作って掛けると、約数の 和になります。
ここまでできたら回答です。
範囲の数について、約数の和を求め、さらにその数の約数の和を求めて、元の数 と同じになれば友愛数です。
Elapsed time: 923.9876 msecs" 上々ですね。
自分で思いついたのだか、人から聞いたのだかわすれましたが、ある範囲の数 について、その約数の和をすべて求めるには、こんな方法もあります。
以下は、1〜8までの数の約数を並べたものです。
1 : 1 2 : 1 2 3 : 1 3 4 : 1 2 4 5 : 1 5 6 : 1 2 3 6 7 : 1 7 8 : 1 2 4 8
こうやって並べてしまうとそれだけで分ってしまうのですが、縦に見ていくと、 2を約数に持つものは2の倍数で、3を約数に持つものは3の倍数です。
この性質を使えば、機械的に表を埋めていくことができます。この先は、5,10,15...と5の倍数のところに5を入れていく、6の倍数のところに...と進めていけばいいということです。
そして、これは、ほとんどエラトステネスの篩の処理と同じです。
これを使って作ったのがsigma1-list関数です。
中身は普通に二重のループを回しているだけですが、約数の和がわかればいいので、上記の表と違って該当の数を加算しています。
long-array が返ります。clojure的には、シーケンスにして返すのが筋でしょうが、 この後の処理を考えてこうしてあります。
- iでチェックしたjは、iに含めなければ高速化しそうですが、どうしたらいいかわからない
- 友愛数を求めるという問題であれば、ペアにして出力すべきでしょうが、今回は合計してしまうので無視。
約数の和のリストを作るときに、iではなく、1を足すと、約数の個数が求まります。
ということで、パラメータで渡すようししてみました。 約数関数の添字と同じく、σx として、 σ0 は約数の個数、σ1は約数の和がでます。 ただし、一般的な理解と異なり、この処理ではNはNの約数に入っていません。
(defn sigmax-list [x ^long size]
(loop [i 2
arr (long-array size 1)]
(if (>= i (/ size 2))
arr
(recur (inc i)
(loop [a arr
j (* i 2)]
(if (>= j size)
a
(recur (do (aset a j (+ (aget a j) (long (Math/pow i x))))
a)
(+ j i))))))))
副作用しかない関数は、
に必要と判断すれば躊躇せず使います。データパッシングに置き変えることはあまり考慮しません。
PEを解くときには探索空間をatomにして、それを操作するための関数を作ります。
経験は多くありませんが、実務では、セッション管理でいくつか作りました。
命名については、末尾に「!」を付けるのがClojureの作法だと思っていましたので、そうしています。
でも、
aset
など一部の関数や、java由来の関数はこの規則に沿っていないので、あまり統一感は無いですよね。個人的には、
do
が付いているものは逐次処理のための関数で、その対象に副作用のある関数を呼ぶことを想定しているものだと思うので、ちょっと違和感があります。