1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScriptで素数 2

Last updated at Posted at 2018-05-26

参照記事:
99 Haskell Problems より、[31] でもその前に
Haskellで素数 2 自分でもやってみた。
Pythonで素数 2 Pythonでやってみた。
JavaScriptで素数 あんまり芳しくなかった前回。

前記事のまとめとこの記事の目的

Haskell界隈の方法をまねしてやってみたら、効率はよさげだったが、2207までしか求められなかった。
この記事では、もっと大きい素数まで効率よく求められるようにします。

JavaScriptでまねしたHaskell界隈のやりかた

をざっくり説明すると、

  • ある素数pとその次の素数qと
  • p以下の2、3以外の素数をすべて掛けた数mを使って
  • pp から qq-2 までの 2、3の倍数を除いた素数の候補から
  • mとの最大公約数が1になる数を素数とする

という処理を繰り返し行なっています。

この m が意外とはやくMAX_SAFE_INTEGER に達してしまって、以降、正確に計算できなくなってしまいました。

ちょっと工夫してみた。

変更点 1

m を 単一の整数値でなく、MAX_SAFE_INTEGER 以下の数で因数分解した配列にする。
そうすればMAX_SAFE_INTEGERを超える数も正しく計算できるし、素数の判定も m の全ての要素について最大公約数が1になるか、で簡単に判定することができます。

変更点 2

一回目のzonedPrimesで 7 から 23 までの素数がわかりますが、次の回を 55 = 25 から 77ー2 = 47 までではなく、最後に求められた 23 を使って一気に
23*23ー2 = 527までいってしまおう、という作戦。
引数の _m を使うとかなりオーバーヘッドがあるものの、変更なしの簡単なアルゴリズムでいける。
求められた素数を使って zonedPrimes 内で次回分の m を更新するようにします。

変更点 3

arrayToG,zonedPrimes終了時に次回の引数を返すようにしました。

//最大公約数。
const gcd = x => y => y === 0 ? x : gcd( y )( x % y )
// xsのすべての要素とyがたがいに素
const isPrimeToEveryElement = xs => y => xs.every(e=>gcd(e)(y)===1)

// xsを破壊的に変更する。
//  xsの最後の要素と x をかけて MAX_SAFE_INTEGER以下ならそれをxsの最後の要素にする
//  でなければ、x を xs に追加
const destructivelyAddFactor = xs => x => {
  const y = xs[ xs.length - 1 ] * x;
  if( y <= Number.MAX_SAFE_INTEGER ) 
          xs[ xs.length - 1 ] = y;
        else 
          xs.push( x );
}
  

// 配列をジェネレーターにする。終了時にzonedPrimesの引数を返す
const arrayToG = xs => function*(){
    yield* xs;
    return {m: [5], s: 7, p: 5};
}();

// sに 6*n+1 型の自然数、pに素数をとって、
// s から p*p-2 までの2と3の倍数を除いた素数の候補を返すジェネレーター。
const croppedPrimables = s => p => function*(){
  let x = s;
  while(x <= p * p - 2 ){
    yield x;
    yield x = x + 4;
    x = x + 2;
  }
}();

// _m : 2,3 以外の p 以下の素数を全てかけた数を
//    MAX_SAFE_INTEGER以下の因数に分解して配列にしたもの
// sに 6*n+1 型の自然数、pに素数をとって、
// s から p*p-2 までの素数を返すジェネレーターを返すクロージャ。
//ジェネレーターは終了時に次回の引数 を返す
const zonedPrimes = _m =>  s => p => {
  let m =[ ..._m ];
  return function*(){
    const nG = croppedPrimables( s )( p );
    let nextNominee;
    let lastYielded;
    while( !( nextNominee = nG.next() ).done ){
      if(isPrimeToEveryElement( _m )( nextNominee.value ) ){
        yield ( lastYielded = nextNominee.value );
        destructivelyAddFactor( m )( lastYielded );
      }
    }
    return { m : m, s : p * p, p : lastYielded };
  }();
}

//素数を返すジェネレーター。返り値が MAX_SAFE_INTEGER に逹したら終了する。
const primes = () => function*(){
  let x;
  let primeG = arrayToG([2, 3, 5]);

  while(true){
    while( !( x = primeG.next() ).done ) {
      if(x.value > Number.MAX_SAFE_INTEGER) return;
      yield   x.value;
    }
    primeG = zonedPrimes( x.value.m )( x.value.s )( x.value.p );
  }
}();

// 2207の次の素数。前回NGだったやつ。
{ 
	const primesGen = primes();  
	let x;  
	while( ( x = primesGen.next().value ) <= 2207 );  
	console.log(x);  
}

//=>
2213	// 正しい。

できたー。
もっと大きい数もいけます。
環境のtimeOutにもよりますが、数万~十数万までは求められました。

まとめ

何か前記事のよりもさらに速い様子。
単純な試し割りの10倍くらいは速い雰囲気。正確にはどうなんでしょう?

ジェネレータの終了時に return で値を返すというのをやってみたら、便利だったけど謎度は深まったかも。
yield時とreturn時と全然違うものを返すのでちょっと変な感じ。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?