質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

2回答

4616閲覧

Javascriptでの整数nまでの素数を数えるアルゴリズムについて

phiar_poet

総合スコア230

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

1クリップ

投稿2020/08/04 10:53

編集2020/08/05 02:30

###素数計算をより高速処理をする為に

仕事時間の合間や休みの日に勉強がてら簡単なプログラムを作ったりしているのですが、
最近、素数の数を数えるプログラムをJavacriptで組みました。

様々な方法でアルゴリズムを組み、それぞれの計算速度を比較して楽しんでいますが、
より高速に処理をこなせるアルゴリズムを探しています。

素数判定の様々な計算方法は調べてみたのですが、
数学知識が乏しく理解できるものが少ないというのが現状です。
ここをこうすると速くなる、そもそもこういうアルゴリズムの方がいい!
など、皆さまの知識を吸収させていただきたく、質問させていただいきました。

###処理の内容

整数n以下の全ての素数の数を数える

###現在の実装

javascript

1function PrimeCalculater(op){ //この関数はクリックイベントで呼び出されます 2 let max_value = parseInt($('input.form_value').val()); //Webページ側から整数nとなる値を取得します 3 let primeArr = new Array(); //素数格納用配列 4 let cal_counter = 0; //計算回数カウンタ 5 primeArr.push(2,3,5,7); //Webページ側で最小値10と設定しているので、10以下の素数はあらかじめ用意します 6 7 const startTime = performance.now(); //測定用 8 9 for(i=11; i<max_value; i+=2){ //偶数は省く 10 let status = true; 11 for(j=1; (primeArr[j]<parseInt(Math.sqrt(i))+1) && j<primeArr.length && status; j++){ //primeArr[0] = 2 なので省く 12 cal_counter++; 13 if((i % primeArr[j]) == 0) status = false; //割り切れたら処理を中断し次の検査値へ 14 } 15 if(status) { 16 primeArr.push(i); 17 } 18 } 19 20 //Webページに結果を出力 21 $('td.cal').text(((performance.now() - startTime)/1000).toFixed(12) + ' 秒'); 22 $('td.counter').text(cal_counter.toLocaleString() + ' 回'); 23 $('td.answer').text((primeArr.length).toLocaleString() + ' 個'); 24}

いくつか作成したアルゴリズムの中で最速のものが上記のコードでした。
もちろん、演算回数をカウントする部分を無くしたらもう少し早くなると思いますが…。

Webサイト側では 10~999,999,999 の整数が入力できるフォームがあり、
そこに数値を入力し、実行ボタンを押すことで計算を開始し、結果をサイト上に表示する仕組みです。
(実際はその他の計算方法が選択できる構造になっています)

処理としては、Web側で指定された整数に対して、11~指定値までの奇数を順番に素数判定していきます。
判定方法ですが、判定する素数の平方根(小数点切り捨て)+1 の値までに存在する素数のみで
割り切れるかどうかで素数かどうかを判断し、途中で割り切れれば処理を中断し次の値のチェックに移行します。
素数だった場合は素数リストにその値を追加していきます。
これを指定数まで繰り返していきます。

最終的にその配列の要素数を出力するという仕組みです。

###計算速度
この実装で1億までの素数の数を数えさせた結果は画像の通りです。
イメージ説明

これ以外だと…

1000万の場合は 約3.2秒
100万の場合は 約0.14秒
10万の場合は 約0.01秒

というような計算速度です。
(PC依存の速度なので環境によってだいぶ違うとは思います)

###やりたいこと・知りたいこと
この実装、またはこれ以外のアルゴリズムでより早い計算方法を知りたいです。
自分の解法ではこの速度が限界でした。ご鞭撻のほどお願いいたします。

###追記
具体的には1億以上の数値が入力された場合の演算を高速化したいと考えています。

###2020/08/25 追記(エラトステネスの篩実装)
ご助言頂きましたエラトステネスの篩による実装です。

javascript

1//max_valueにはフォームからの入力値が入ります 2//ここでmax_valueの値が1億などとされるとこの実装ではmemory outし、ページが停止します。 3let prime = new Array(max_value).fill(true); 4 5//primeCounterは本来必要ありませんが、最終的に配列の要素数 = 素数の数としてWebページに出力する都合上、 6//素数の数をカウントしてその大きさの配列を作る為に使用しています 7let primeCounter = 0; 8 9prime[0] = false; 10prime[1] = false; 11 12for(let i = 2; i <= maxSqrt; i++) { //maxSqrt には parseInt(Math.sqrt(max_value))+1 が代入されています 13 if(prime[i]) { 14 for(let j = i * 2; j <= max_value; j += i) { 15 cal_counter++; //この変数は別の場所で宣言しており、演算回数を図る為のものです 16 prime[j] = false; 17 } 18 } 19} 20for(let i = 0; i < prime.length; i++) { 21 cal_counter++; 22 if(prime[i]) primeCounter++; 23} 24 25primeArr = new Array(primeCounter);

エラトステネスの篩に関係のない部分は省略しています。
エラトステネスの篩というものの解釈がこれであっているのか自信はありませんが、
設定された整数と同一の数の配列を宣言し、その中身を一度全て TRUE としておきます。
それから 0 と 1 は素数ではないので手動で FALSE を代入します。
繰り返しを 2 からスタートし、ネストしたループ処理でその倍数となる要素を全て FALSE にしていきます。
ループ処理の際、その要素が既に篩に掛けられ素数ではない判定(FALSE)になっている場合は次へ移行します。
この処理を parseInt(Math.sqrt(max_value))+1 以下まで繰り返します。

最後に配列内で TRUE となっている要素の数を数えればそれが素数の数になっているはず、

という実装です。

この実装によって得られた結果は以下の画像の通りです。
イメージ説明

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

エラトステネスの篩、という古代ギリシャから伝わるアルゴリズムがあります。

詳細は検索すればすぐヒットしますので省略しますが、割り算も不要で、(メモリ容量が十分確保できるなら)高速に処理できます。

投稿2020/08/04 11:48

maisumakun

総合スコア145184

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

phiar_poet

2020/08/04 12:16

ご回答ありがとうございます。 メモリ容量が十分に確保できるなら、がネックなんですよね…。 エラトステネスの篩については調べました。 その実装に伴って最初に整数nまでの配列を作成するという内容だったのですが、 ブラウザの仕様上、例えば要素の数が1億の配列を宣言できるかと言われるとNoで、 ブラウザで動かすJavascriptでの実装が難しかったので度外視していました。 他の言語等では要素数1億の配列の宣言など、一般的なメモリ容量上現実的な数字なのでしょうか…。
maisumakun

2020/08/04 12:32

工夫すれば、要素数1億まで行かなくても処理できます。 ・まず、1億の平方根である1万まででふつうにエラトステネスの篩を行って、1万までの素数リストを出しておく ・1~100万の100万個の配列を用意して、1万までの素数で消して、素数を洗い出す ・次は100万+1~200万で行う(消すのは1万以下の素数だけで問題なし) ・以下、1億まで順次進めていく というように分割して行うことも可能です。
phiar_poet

2020/08/04 22:55

分割して処理を行うというのは盲点でした…。 こういうところの発想の転換が自分には足りないようです。 まず、あれからエラトステネスの篩を素直に実装したところ、 30,000,000程度であれば処理できました。 (40,000,000を宣言した際には急激に遅くなったのでメモリがギリギリなのかもしれません) 余談ですが、入力値が30,000,000の時に既存の計算方法では 計算時間: 7.875954999821 秒 演算回数: 1,636,119,415 回 だったのに対し、エラトステネスの篩による演算結果では 計算時間: 0.653870000038 秒 演算回数: 102,463,862 回 というかなりの高速化が図れました。 ありがとうございます。 なお 30,000,000 までに存在する素数の数は 1,857,859 個 のようでした。 分割して1億以上の処理ができるよう、もう少し考えてみます。
yambejp

2020/08/05 01:14

phiar_poetさん > エラトステネスの篩を素直に実装 ちなみにどういった実装でしょうか? 分割は結局2から順に処理しないといけないので あまり意味がないかもしれません
phiar_poet

2020/08/05 02:33

yambejp様 お返事いただきましてありがとうございます。 実際に実装したコードを質問内容に追記しました。 ご確認いただければと思います。 分割についてはとりあえずコードを書いてみて速度チェックはしてみたいと思っています! 何分、知識が乏しいものなのでとりあえずコードを書いていろいろ試してみる、を実践しているところです…。
guest

0

いろいろやってみましたがエラトステネスの篩を分割で処理させる
というコードはうまく実装できませんでした…。
とりあえず、なんとか計算方法を早くしようとした結果邪道かもしれませんが
次の方法で決着しました(させました…)。

実装

Javascript

1var cal_counter = 0; 2var PRIME_MEM = [...]; //この配列には10,000,000までの素数が入っています 3 4function calculateStart(op){ //サイト側の計算開始ボタンで実行されます 5 6 let max_value = parseInt($('input.form_value').val()); //このDOM要素に計算した数値が入っています 7 let primeArr; 8 let primeCounter; 9 let index = PRIME_MEM.indexOf(max_value); 10 11 const startTime = performance.now(); //計算開始時間を取得 12 13 //10,000,000までの素数表があるので検索値以下の最大の素数を検索します 14 //その最大の素数が登録されているindex番号+1が素数の数となるはず 15 if(max_value <= 10000000){ 16 for(let i = max_value - 1; index == -1; i--){ 17 cal_counter++; 18 index = PRIME_MEM.indexOf(i); 19 } 20 primeCounter = index + 1; 21 } else { //検索値が10,000,000よりも大きければそこからは試し割り算 22 primeCounter = PRIME_MEM.length; 23 for(let i = 10000001; i <= max_value; i++){ 24 if(isPrime(i)) primeCounter++; 25 } 26 } 27 28 //配列を宣言する必要はないがここには記載していない他の計算方法との出力の兼ね合い 29 primeArr = new Array(primeCounter); 30 31 //出力 32 $('td.cal').text(((performance.now() - startTime)/1000).toFixed(12) + ' 秒'); 33 $('td.counter').text(cal_counter.toLocaleString() + ' 回'); 34 $('td.answer').text((primeArr.length).toLocaleString() + ' 個'); 35 36 //cal_counter は var で宣言している為、値を初期化 37 cal_counter = 0; 38} 39 40 //試し割り算を行う関数 41function isPrime(n){ 42 let maxSqrt = Math.sqrt(n); //試し割り算を繰り返す最大値 43 44 for(let i = 0; PRIME_MEM[i] <= maxSqrt; i++){ 45 cal_counter++; 46 if(n % PRIME_MEM[i] == 0){ //素数で割り切れた時点でFALSEを返す 47 return false; 48 } 49 } 50 return true; 51}

###概要

10,000,000までの素数を事前に用意してみる、という実装にしました。
10,000,000以下と10,000,001以上で当然計算方法が異なります。

10,000,000以下の場合はその数値以下の最大の素数を素数表から探し出し、
そのINDEX番号+1が素数の数となる、という計算方法で導き出します。
当たり前ですが、こと10,000,000以下、または10,000,000付近の答えを導き出す場合は
エラトステネスの篩より高速です。

10,000,001以上については試し割り算を行って素数を探していきます。
その際は、素数表の長さプラスそこからの素数の数となるのでそのように実装しています。

###演算結果
イメージ説明

およそ40%ほど計算時間を短縮しました。(元々は77.2秒)

他の数値を入力した場合の計算結果は次の通りです。
大小ランダムな数値を突っ込みます。

入力値最初の試し割り算エラトステネスの篩今回の計算方法素数の数
2,6750.00010秒0.00005秒0.00212秒387種類
68,3920.04201秒0.00096秒0.00164秒6,801種類
284,7680.01984秒0.00360秒0.00487秒24,825種類
1,873,6490.22580秒0.02433秒0.00694秒140,206種類
9,837,5611.93935秒0.20176秒0.00359秒654,606種類
10,574,8233.40969秒0.22342秒0.12017秒700,241種類
29,485,73416.71694秒0.67303秒4.92683秒1,827,874種類
100,000,00076.98432秒メモリ不足44.97676秒5,761,455種類

様々な数値を試してみましたが、素数表を用意した今回の計算方法では、
10,000,000以下の数値に関してはどのような数値が来ても0.001秒~0.007秒処理が行えるようで、
素数表に含まれている数値以下であるならおよそ数値の大小関係なくこの速度が出るということでした。
(検索値以下の最大素数までに何回デクリメントを繰り返すかで処理時間が変化するだけなので検索値の大小で差は生まれにくい)

ただし、入力値が計算数と速度に常に影響する最初の計算方法やエラトステネスの篩については、
入力値が小さければ小さいほど速度が速くなるので今回の計算方法よりも早いという結果です。

10,000,000までの素数表を事前に用意しておく計算方法では、
1,000,000~11,000,000の間で事前に素数表を用意した計算方法が早く、
それ以外ではエラトステネスの篩が最速、という形です。

エラトステネスの篩すごいな……。
メモリさえ足りれば巨大な数値が入ってきてもエラトステネスの篩が最速というのが答えとなりそうです。

お付き合いいただきました方、ありがとうございました!

投稿2020/08/13 02:37

編集2020/08/13 02:42
phiar_poet

総合スコア230

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問