そのコードは、(少なくとも私にとっては)読みやすいように、いくつかの変数の名前を示唆的に変更した方が簡単です。
lazy val ps: Stream[Int] = 2 #:: Stream.from(3).filter(i =>
ps.takeWhile{p => p * p <= i}.forall{ p => i % p > 0});
これは非常に自然に左から右に読みます。
素数は2であり、 2乗が を超えないすべての素数が均等に割り切れないi
(つまり、ゼロ以外の剰余がない) 3以上の数です。 p
i
i
真の再帰的なやり方で、この定義が増加し続ける素数の流れを定義するものであると理解するには、そうであると仮定します。その仮定から、矛盾は生じないことがわかります。つまり、定義の真実が成り立ちます。
ps
その後の唯一の潜在的な問題は、ストリームが定義されているときにストリームにアクセスするタイミングです。最初のステップとして、どこかから魔法のように別の素数のストリームが提供されたと想像してください。次に、定義の真偽を確認した後、アクセスのタイミングが適切であることを確認します。つまり、ps
定義される前の領域にアクセスしようとはしません。それは定義を行き詰まらせ、非生産的にします。
どこかで(どこかは覚えていませんが)次のような文章を読んだことを覚えています。生徒と魔法使いの会話です。
- 生徒:素数はどれ?
- 魔法使い: ええと、最初の素数は何ですか?
- s:はい、それは2です。
- w:わかりました (すぐに2を紙に書き留めます)。そして、次はどうですか?
- s:そうですね、次の候補は3です。平方がそれを超えない素数で割られるかどうかを確認する必要がありますが、素数が何であるかはまだわかりません!
- w:心配しないで、あげるよ。それは私が知っている魔法です。やっぱり私は魔法使いです。
- s:では、最初の素数は何ですか?
- w: (一枚の紙を一瞥する) 2 .
- s:よし、その二乗は既に 3 よりも大きい...だまされた! .....
コードの疑似コード1翻訳を次に示します。部分的に右から左に読み、明確にするためにいくつかの変数の名前を再度変更します ( p
「プライム」を使用)。
ps = 2 : filter (\i-> all (\p->rem i p > 0) (takeWhile (\p->p^2 <= i) ps)) [3..]
これも
ps = 2 : [i | i <- [3..], and [rem i p > 0 | p <- takeWhile (\p->p^2 <= i) ps]]
これは、リスト内包表記を使用すると、もう少し視覚的に明らかです。and
ブール値のリスト内のすべてのエントリが( 「for」、「 drawing from」、「that」、「lambda of 」とTrue
読みます) であることを確認します。|
<-
,
(\p-> ...)
p
つまり、は 2 の遅延リストであり、そのようなストリームから引き出されたすべてのそのような から引き出された数については、それは真です。これは実際には最適な試行分割アルゴリズムです。:)ps
i
[3,4,5,...]
p
ps
p^2 <= i
i % p > 0
もちろん、ここには微妙な点があります。リストには制限がありませps
ん。「肉付け」されたまま使用します(もちろん、怠け者だからです)。p
s が から取得されるとps
、その終わりを過ぎて実行される可能性があります。その場合、終了しない計算 (「ブラック ホール」) が発生します。上記の定義では、これは不可能であることが起こります (そして ⁄ は数学的に証明できる必要があります)。2 は無条件に入れps
られるので、そもそも何かが入っています。
しかし、「単純化」しようとすると、
bad = 2 : [i | i <- [3..], and [rem i p > 0 | p <- takeWhile (\p->p < i) bad]]
2: 3 を候補として考えると、2takeWhile (\p->p < 3) bad
の次の数を要求しbad
ますが、そこにはまだ数がありません。それは「自分より先にジャンプ」します。
これは「固定」されています
bad = 2 : [i | i <- [3..], and [rem i p > 0 | p <- [2..(i-1)] ]]
しかし、それははるかに遅い試行分割アルゴリズムであり、最適なものからはほど遠いものです。
--
1 (実際には、 Haskell、その方が簡単です:))