ScalaでProject Euler(62)

Problem 35

この問題はある意味難しいです。
1, 3, 7, 9の4つの数字だけ作って、回して全てが素数かどうか調べればいいので簡単です。ただ、例えば197が「回転素数」だったとして、197・971・719に対して素数判定だったとします。そうすると、719についてもやっぱり3回素数判定をしなければなりません。971も同じです。だから無駄がありますね。これを避けるために回転して最小の数だけ「回転素数」の判定を行います。上の例なら197のときだけ判定します。これが下のコードです。
しかし、一々回転して最小かどうか判定するのは無駄が多い気がします。例えば、頭が1で残りが3、7、9なら明らかに「回転最小」ですね。逆に頭が3でどこかに1があれば「回転最小」ではありません。このように判定せずにいきなり「回転最小」を漏れなく生成できないでしょうか。
これが難しいんです。わかりやすくするために、1が使われているとして、1のみのブロックと1が使われていないブロックに分けます。

11 3 1 7 1 9

最初に1が2つ来て、あと2つ以上続くブロックはないので、これは「回転最小」です。では、これはどうでしょう。

1 33 1 7 1 9

1のブロックはみな同じなので、こういうときはそれ以外のブロックを比較します。最初が1番小さいのでこれは「回転最小」です。ここで33が7と9に比べて小さいと言っているのは、頭から数字を比べてです。この場合頭で大小が決まっています。そこで決まらなければ次の桁を見ます。33と3は3は次の桁がないので3のほうが小さいと解釈します。ではこういうのはどうでしょう。

1 3 1 7 1 3 1 3 1 9

わかりにくいですよね。このように一般に最小かどうかを判定するのは難しいのです。いわんや「回転最小」だけを生成するのは大変そうです。

def is_prime(n :Int) =
    Iterator.from(2).takeWhile(m => m * m <= n).forall(n % _ != 0)

def number(a :List[Int]) = a.foldLeft(0)(_ * 10 + _)
def rotate(a :List[Int]) = a.tail ++ List(a.head)

def repeat(a :List[Int], n :Int) :Iterator[List[Int]] =
    if(n == 0)
        Iterator(Nil)
    else
        repeat(a, n - 1).flatMap(x => a.map(_ :: x))

def is_min_rotate(a :List[Int]) = {
    val front = number(a)
    val it = Iterator.iterate(a)(rotate).slice(1, a.size).map(number)
    it.forall(front <=)
}

def is_circular_prime(a :List[Int]) =
    Iterator.iterate(a)(rotate).take(a.size).map(number).forall(is_prime)

def count_rotate(a :List[Int]) =
    Iterator.iterate(a)(rotate).take(a.size).toSet.size

val N = 6
val it = for(n <- Iterator.range(2, N + 1);
             a <- repeat(List(1, 3, 7, 9), n)
             if is_min_rotate(a))
                yield a
println (it.filter(is_circular_prime).map(count_rotate).sum + 4)