ClojureでProject Eulerやってます-7
相変わらずProject Eulerやってます。
問題の和訳はここ
Problem 39
辺の長さが{a,b,c}と整数の3つ組である直角三角形を考え, その周囲の長さをpとする. p = 120のときには3つの解が存在する:
{20,48,52}, {24,45,51}, {30,40,50}
p ≦ 1000 で解の数が最大になる p を求めよ.
とりあえずpの条件を満たす数列を作ってみます。
;ピタゴラス数判定
(defn pytha? [a b c]
(let [x (int c)]
(= (* x x) (+ ( * a a) (* b b)))))
(assert (pytha? 3 4 5.0))
;直角三角形の周囲の長さを返す
(defn pytha-length [x y]
(let [z (Math/sqrt (+ (* x x)(* y y)))]
(if (pytha? x y z) (int(+ x y z)) 0 )) )
(assert (= 12 (pytha-length 3 4)))
;周囲がn未満のピタゴラス数の総和の数列
(defn pytha-coll [n]
(take-while #(> n %) (sort (filter #(not (zero? %))(for [x (range 3 n) y (range (inc x) n)] (pytha-length x y ))))))
(assert (= 3 (count (drop-while #(< % 120) (pytha-coll 121)))))p=120の解をちゃんと見つけてくれたようです。
出現したpをkeyに、個数をvalueにとるmapを作ってみます。
;同じ値どおしをmapにまとめる
(defn make-count-map [c]
(loop [map {} coll c]
(cond
(empty? coll) map
:else (recur (assoc map (first coll) (inc (get map (first coll) 0))) (rest coll)))))最大のvalueを探します。
;secondが大きいほうのcollを返す
(defn max-second-col [c1 c2]
(if (> (second c1) (second c2) ) c1 c2))
(assert ( = [0 2] (max-second-col [0 1] [0 2])))
;最大のValueをもつkeyとvalueを返す
(defn find-max-value [m]
(loop [map m coll [-1 -1]]
(cond
(empty? map) coll
:else (recur (dissoc map (key (first map))) (max-second-col coll (first map)) ))))
(println (first (find-max-value (make-count-map (pytha-coll 1001))) ))遅いかなと思ったのですが、1秒もかかりませんでした。使い回ししやすそうな関数も書けたのでいい感じです。次。
Problem 40
正の整数を順に連結して得られる以下の10進の無理数を考える:
0.123456789101112131415161718192021… 小数第12位は1である.
dnで小数第n位の数を表す. d1 × d10 × d100 × d1000 × d10000 × d100000 × d1000000 を求めよ.
とりあえず1000000個の数列を作ります。
(def numbers (take 1000000 (flatten (map #(vec (str %)) (range 1 200000))))) (assert (= 1000000 (count numbers) ))
rangeの終端は適当ですw
12桁目が1かどうか確かめます
;charをintに変換 (defn char-int[c] (- (int c) 48)) (assert (= 1 (char-int(nth numbers 11))))
あってるっぽい。解答を表示します。
(println (* (char-int(nth numbers 0)) (char-int(nth numbers 9)) (char-int(nth numbers 99))
(char-int(nth numbers 999))(char-int(nth numbers 9999))(char-int(nth numbers 99999))(char-int(nth numbers 999999))))てきた。次。
Problem 41
n桁Pandigitalであるとは, 1からnまでの数を各桁に1つずつもつこととする.
下のリンク先にあるような数学的定義とは異なる
例えば2143は4桁Pandigital数であり, かつ素数である. n桁(この問題の定義では9桁以下)Pandigitalな素数の中で最大の数を答えよ.
Problem32で9桁Pandigital数を出力するコードを書いているので、それを利用して9桁の範囲でまず探してみます。
(println (filter #(prime? (Integer/parseInt (apply str %))) (num-list) )) ()
Pandigitalな素数が見つかりませんでした。
8桁でも見つからず、7桁目で見つかりました。
(defn num-list7[]
(for [c (range 7) d (range 6) e (range 5) f (range 4) g (range 3) h (range 2) ]
(result-num [ c d e f g h 0] )))
(println (last (filter #(prime? (Integer/parseInt (apply str %))) (num-list7) )))Problem 42
三角数のn項は tn = ½n(n+1)で与えられる. 最初の10項は
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, … である.
単語中のアルファベットを数値に変換した後に和をとる. この和を「単語の値」と呼ぶことにする. 例えば SKY は 19 + 11 + 25 = 55 = t10である. 単語の値が三角数であるとき, その単語を三角語と呼ぶ.
16Kのテキストファイル words.txt 中に約2000語の英単語が記されている. 三角語はいくつあるか?
words.txtは例によってカンマ区切りでした。 とりあえず読み込んでみます。
(def words-array (sort(re-seq #"\w+" (slurp "words.txt"))))
「単語の値」を取得する関数を書きます。
(defn word-value [s] (reduce + (map #(- (int %) 64) s))) (assert = (55 (word-value "SKY"))) (def words-values (map #(word-value %) words-array )) (println (first (sort > words-values )))
単語の値の最大は192らしいです。 192までの三角数を使って三角数の判定関数を書きます。
(defn tri [n] (quot (* n (inc n)) 2))
(def tri-until-192 (take-while #(< % 192) (map #(tri %) (iterate inc 1))))
(defn tri? [n]
(loop [tri-coll tri-until-192 ]
(cond
(empty? tri-coll) false
(= (first tri-coll) n) true
:else (recur (next tri-coll) ))))単語の値を全部なめてみる。
(println (count(filter #(tri? %) words-values )))
できた。次。
Problem43
数1406357289は0から9のPandigital数である (0から9が1度ずつ現れるので). この数は部分語が面白い性質を持っている.
d1を1桁目, d2を2桁目の数とし, 以下順にdnを定義する. この記法を用いると次のことが分かる.
d2d3d4=406は2で割り切れる
d3d4d5=063は3で割り切れる
d4d5d6=635は5で割り切れる
d5d6d7=357は7で割り切れる
d6d7d8=572は11で割り切れる
d7d8d9=728は13で割り切れる
d8d9d10=289は17で割り切れるこのような性質をもつ0から9のPandigital数の総和を求めよ.
Pandigital数を返す関数は以前書いたものを少し書き換えて使います。
(defn delete [n coll] (flatten (conj (drop (inc n) coll) (take n coll) )))
(defn result-num [coll]
(loop [result-num-coll [] base-coll [0 1 2 3 4 5 6 7 8 9] src-coll coll]
(if (not( empty? src-coll ))
(recur (conj result-num-coll (nth base-coll (first src-coll))) (delete (first src-coll) base-coll) (rest src-coll))
(Long/parseLong (apply str result-num-coll)))))
(assert (= (result-num [1 0 0 0 0 0 0 0 0 0] ) 1023456789))
(defn num-list[]
(for [i (range 1 10) a (range 9) b (range 8) c (range 7) d (range 6) e (range 5) f (range 4) g (range 3) h (range 2) ]
(result-num [i a b c d e f g h 0] )))これを使って結果を表示します。
(defn d234? [i] (zero? (rem ( quot (rem i 1000000000) 1000000) 2))) (defn d345? [i] (zero? (rem ( quot (rem i 100000000) 100000) 3))) (defn d456? [i] (zero? (rem ( quot (rem i 10000000) 10000) 5))) (defn d567? [i] (zero? (rem ( quot (rem i 1000000) 1000) 7))) (defn d678? [i] (zero? (rem ( quot (rem i 100000) 100) 11))) (defn d789? [i] (zero? (rem ( quot (rem i 10000) 10) 13))) (defn d8910? [i] (zero? (rem (rem i 1000) 17))) (assert (d234? 1406357289)) (assert (d345? 1406357289)) (assert (d456? 1406357289)) (assert (d567? 1406357289)) (assert (d678? 1406357289)) (assert (d789? 1406357289)) (assert (d8910? 1406357289 )) (defn d?[n] (and (d234? n) (d345? n) (d456? n) (d567? n) (d678? n) (d789? n )(d8910? n))) (println (reduce + (filter #(d? %) (num-list))))
5分くらいかかりました(汗 1分ルールが気になるところですが、Scalaで同じようなアルゴリズムでやってみたら20秒くらいで終わりましたので、このままで。
せっかく遅いコードがあるのでwプロファイルのやり方とか調べてみようかな。
Problem 44
五角数は Pn = n(3n-1)/2で生成される. 最初の10項は
1, 5, 12, 22, 35, 51, 70, 92, 117, 145, … である.
P4 + P7 = 22 + 70 = 92 = P8である. しかし差 70 – 22 = 48は五角数ではない.
五角数のペア PjとPkについて, 差と和が五角数になるものを考える. 差を D = |Pk – Pj| と書く. 差 D の最小値を求めよ.
五角数って何かしらと思いWikipediaで調べてみました。なるほど。
とりあえず、五角数を求める関数を書きます。
(defn pentagonal [n] (/ (* n (- (* 3 n) 1)) 2)) (assert (= [1, 5, 12, 22, 35, 51, 70, 92, 117, 145] (map #(pentagonal %) (range 1 11))))
和と差が五角数か調べないといけないのですが、Problem 42の時のように調査範囲が決まっているわけではないので同じ手が使えそうにありません。 なので、値から逆に求める関数を書きたいのですが、2次方程式の公式とか忘れてしまいました(汗
”2次方程式 公式”でググってみると、仕事でもよくお世話になっている”物理のかぎしっぽ」さんのページが見つかりました。こちら
五角数Pを求める式を
- P = n(3n-1)/2
とすると
- 3n²-n-2p=0
と変形できます。nが正の整数であればpが五角数といえますので、こういう関数になります。
(defn pentagonal? [n]
(let [x (/ (+ 1 (Math/sqrt (+ 1 (* 24 n)))) 6)]
(== x (int x))))
(assert (pentagonal? 145))2つの五角数のペアを返す関数はこう
(defn add-pentagonals [] (filter #(not (empty? %)) (for [x (iterate inc 1) y (range 1 x)] (if (pentagonal? (+ (pentagonal x) (pentagonal y))) [(pentagonal x) (pentagonal y)] []))))
差が五角数になっているかを返す関数はこう
(defn sub-pentagolan? [c] (pentagonal? (- (first c) (second c))))
結果を表示してみます。
(def result (first (take 1(filter #(sub-pentagolan? %) (add-pentagonals ))))) (println result) (println (- (first result) (second result)))
1秒かからずに結果が表示されました。思ったより大きい数字でしたが。
差と和が五角数になるペアがほかにもあるかと思って試してみたのですが、しばらく走らせてみたものの見つかりませんでした。ほかにもうペアがないのか、それともよほど大きい数字なのか。
Problem 45
三角数, 五角数, 六角数は以下のように生成される.
三角数 Tn=n(n+1)/2 1, 3, 6, 10, 15, … 五角数 Pn=n(3n-1)/2 1, 5, 12, 22, 35, … 六角数 Hn=n(2n-1) 1, 6, 15, 28, 45, … T285 = P165 = H143 = 40755であることが分かる.
次の三角数かつ五角数かつ六角数な数を求めよ.
五角数を作成する関数はProblem 44のを使います。 三角数と六角数の判定関数はこう。
(defn triangle? [n]
(let [x (/ (- (Math/sqrt (+ 1 (* 8 n))) 1) 2)]
(== x (int x))))
(defn hexagonal? [n]
(let [x (/ (+ 1 (Math/sqrt (+ 1 (* 8 n)))) 4)]
(== x (int x))))
(assert (triangle? 40755))
(assert (hexagonal? 40755))表示させてみます。
(println (first (filter #(and (triangle? %) (hexagonal? %)) (map #(pentagonal %) (iterate inc 166)))))
思ったより早く表示されました。