zaneli-eulerの
この変更をしないと今回のProblem 58, 60 には使い物にならなかった。
従来は、まず調べたい値までの素数の全リストをprimes関数を使用して作っていたが、
それでは速度的に厳しかった。
調べ方がザルのような気もしたが、奇数のリストの中に割り切れる値が無いかを調べるほうが格段に速い。
isPrime
を修正。この変更をしないと今回のProblem 58, 60 には使い物にならなかった。
従来は、まず調べたい値までの素数の全リストをprimes関数を使用して作っていたが、
それでは速度的に厳しかった。
調べ方がザルのような気もしたが、奇数のリストの中に割り切れる値が無いかを調べるほうが格段に速い。
Problem 58. Spiral primes(螺旋素数)
まずspiralDiagonals
で渦巻きの対角線上の数値と、その時点での辺の長さを持つ無限リストを作っている。> take 3 spiralDiagonals [(3,[3,5,7,9]),(5,[13,17,21,25]),(7,[31,37,43,49])]その無限リストに対して
search
で条件に合うものを探す。対角線上の値をdに加算していき、(
length ns
としているが全て4なのは分かっているので固定値でも良かったかも)その中の素数の数をnに加算していき、素数の割合が10%未満になればその時点での辺の長さを返す。
ここで
Zaneli.Euler.isPrime
を何度も呼んでいるが、従来の処理では非常に遅かったので修正。Problem 59. XOR decryption(XOR暗号解読)
おお、Data.Bits
というモジュールを初めて使った。大した事はしていないけど、
よく使う
Data.List
, Data.Maybe
, Data.Char
, Data.Ord
あたりのモジュール以外が登場すると何だか新鮮な感じだ。まずcipher.txtから読み取った文字列をリストに変換する。
今回はスペースではなくカンマで区切りたいのでProblem 54で作った
splitOn
を再度利用した。lines
してconcatMap
はcipher.txtには1行しか内容が無いため不要なように思えるが末尾の改行を考慮してこのようにしている。
次に
keys
で暗号化鍵の候補となる3文字の小文字を生成する。要素数を指定した順列、
Data.List.permutations
のように標準で用意されているかと思ったが多分無い?ので自作してみた。
読み取ったリストと鍵(を
cycle
した無限リスト)をzip
で組にして暗号化された文字は
read
で数値に、鍵となる文字はfromEnum
でASCIIコードに変換して1文字ずつ
xor
で復号していく。「平文はよく用いられる英単語を含んでいる」から出題の意図に沿うかどうか不明だったが、
単語「the」が含まれるかどうかを
contains
で判定して復号したとみなすことにしてみた。いくつか解答している人のコードを参考にしてみたが、同じように単語で判定しているか
英文における頻出文字(こちらによるとe, t, a, o, i, nなどの順?)を利用しているかだった。
15秒程度と、ちょっと時間がかかっている印象があるのでもう少し高速化する方法はあるかな…。
その後、教えていただいたこれとこれを元に書き直してみた。
いくつか初めて使う関数が出てきた。
keys
の作り方を2パターン教えてもらい前者を採用したが、後者のsequenceは何をやっているのか。> :t sequence sequence :: Monad m => [m a] -> m [a] > sequence [[1,2,3], [4,5,6]] [[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]] > sequence [[1,2,3], []] [] > sequence [Just 1, Just 2, Just 3] Just [1,2,3] > sequence [Just 1, Nothing, Just 3] Nothing…ふむぅ、なるほど。この辺も理解を深めるのに参考になりそうだ。
breakはBoolを返す関数がTrueになる最初の要素以降をsndとするペアにしてくれるのか。
isInfixOfはリストを2つ渡して1つめのリストが2つめに含まれていればTrue。
これらは知らずに回りくどい書き方をしてしまっていたな。
break
は全ての要素がFalseならペアのfstが元のリストそのまま、sndが空リストになるが、tail
していたところをdrop 1
にして空リストでも例外が飛ばないようにすることで、splitOn'
のガードが1つ減って見通しが良くなったと思う。
さらにこれとこれとこれを元に高速化バージョン。
isPrintとisSpaceでフィルタすることで、8秒ほどかかっていた処理が0.1秒程度と大幅に向上できた。
> isPrint 'a' True > isPrint ' ' True > isPrint '\t' False > isPrint '\n' False > isSpace 'a' False > isSpace ' ' True > isSpace '\t' True > isSpace '\n' Trueせっかくなので
keys
はsequence
を使ったものに書き換えてみた。Problem 60. Prime pair sets(素数ペア集合)
処理の高速化にだいぶ苦戦した…。primes
で素数を作りながら条件に合うものを探している。Zaneli.Euler.primes
を使えば良かったが、条件に合うかどうかを判定する
search
が時間のかかる処理で、かつ第二引数のリストを元に判定しているのでこれが変更(新しく素数が追加)された場合のみ呼びたいため
ガードの順番を入れ替えて別途定義した。
しかし何とか共通化したいものだ。
「第二引数のリストを元に判定」する場合、今回以外でも効率が悪くなりそう。
search
では、第一引数の素数(primes
のガードの順番により、この値は素数)と任意の順で繋げて素数となるものを第二引数の素数のリストからフィルタし、
そのフィルタした素数のリストの中からお互い同士も同じ条件を満たすものを
concatPrimes
で4つ選び出している。(問は5つの素数の集合の和だが、第一引数との条件を満たす事を判定済みのため)
filterConcatPrimes
を2箇所で行っているのが微妙だが、処理高速化のため試行錯誤の末このようになった。