サンプル・プログラム
平均を求める
例として、テストの平均点を求めるプログラム "average1.html" を作ってみよう。
左図がプログラムの実行例である。
average1.html
115: //点数表
116: let scores = [75, 68, 81, 90, 57, 52, 96, 72, 68, 72];
この配列をユーザー定義関数 dispScoreTable に渡し、平均点を求めるとともに、点数一覧表を作成する。
average1.html
36: /**
37: * 点数表を作成し,画面に表示する.
38: * @param Array scores 点数表
39: * @return なし
40: */
41: function dispScoreTable(scores) {
42: //エラーメッセージをクリア
43: document.getElementById('error').innerHTML = '';
44:
45: //配列のバリデーション
46: if (! Array.isArray(scores)) {
47: //エラーを表示
48: document.getElementById('error').innerHTML = 'エラー:正しい点数表を用意してください.';
49:
50: //点数表を作成
51: } else {
52: //点数合計と要素数をカウント
53: let sum = 0; //合計点数
54: for (let i = 0; i < scores.length; i++) {
55: let a = parseFloat(scores[i]);
56: //数字以外が含まれていたらNaNを代入して脱出
57: if (Number.isNaN(a)) {
58: n = sum = NaN;
59: return;
60: }
61: sum += a;
62: }
63:
64: //点数表の1行目
65: html =`
66: <tr>
67: <th>出席番号</th>
68: <th>点数</th>
69: </tr>
70: `;
71: //計算結果のバリデーション
72: if (Number.isNaN(sum)) {
73: //エラーを表示
74: document.getElementById('error').innerHTML = 'エラー:正しい点数表を用意してください.';
75:
76: //点数表を作成
77: } else {
78: for (let i = 0; i < scores.length; i++) {
79: html +=`
80: <tr>
81: <td>${i + 1}</td>
82: <td>${scores[i]}</td>
83: </tr>
84: `;
85: }
86: html +=`
87: <tr>
88: <td>合計点</td>
89: <td>${sum}</td>
90: </tr>
91: <tr>
92: <td>平均点</td>
93: <td>${sum / scores.length}</td>
94: </tr>
95: `;
96: //結果を表示
97: document.getElementById('scoreTable').innerHTML = html;
98: }
99: }
100: }
配列に格納されている要素の数は、length プロパティを使って取得できる。
要素の数だけ forループを回し、合計点と平均点を計算する。
あとは、点数表をTABLEタグとして組み立てる。
引数の参照渡し
parameter1.html
19: <script>
20: // ユーザー定義関数 ========================================================
21: /**
22: * 引数に+1する(その1)
23: * @param Number a
24: * @return Number a + 1
25: */
26: function plus1(a) {
27: let b = a + 1;
28: return b;
29: }
30:
31: /**
32: * 引数に+1する(その2)
33: * @param Number a
34: * @return Number a + 1
35: */
36: function plus2(a) {
37: a = a + 1;
38: document.getElementById('var3').innerHTML = 'a = ' + a.toString();
39: return a;
40: }
41:
42: // メイン・プログラム ======================================================
43: window.onload = function() {
44: let a = 1;
45: document.getElementById('var1').innerHTML = 'A = ' + a.toString();
46: plus1(a);
47: document.getElementById('var2').innerHTML = 'A = ' + a.toString();
48: plus2(a);
49: document.getElementById('var4').innerHTML = 'A = ' + a.toString();
50: }
51: </script>
引数の値渡し
A = 1,2,3,4,5
A = 1,2,3,4,5
a = 1,2,3,5,5
A = 1,2,3,5,5
parameter2.html
19: <script>
20: // ユーザー定義関数 ========================================================
21: /**
22: * 配列引数に+1する(その1)
23: * @param Array a 配列
24: * @param Number n 要素番号
25: * @return Number a + 1
26: */
27: function plus3(a, n) {
28: let b = a[n] + 1;
29: return b;
30: }
31:
32: /**
33: * 配列引数に+1する(その2)
34: * @param Array a 配列
35: * @param Number n 要素番号
36: * @return Number a + 1
37: */
38: function plus4(a, n) {
39: a[n] = a[n] + 1;
40: document.getElementById('var4').innerHTML = 'a = ' + a.toString();
41: return a[n];
42: }
43:
44: // メイン・プログラム ======================================================
45: window.onload = function() {
46: let a = [1, 2, 3, 4, 5];
47: let n = 3;
48: document.getElementById('var1').innerHTML = 'A = ' + a.toString();
49: plus3(a, n);
50: document.getElementById('var3').innerHTML = 'A = ' + a.toString();
51: plus4(a, n);
52: document.getElementById('var5').innerHTML = 'A = ' + a.toString();
53: }
54: </script>
配列をシャフルする
shuffle.html
36: /**
37: * 指定した配列をシャフルする.
38: * @param Array arr シャフルしたい配列
39: * @return なし
40: */
41: function shuffle(arr) {
42: for (let i = arr.length; i > 1; i--) {
43: let k = Math.floor(Math.random() * i);
44: [arr[k], arr[i - 1]] = [arr[i - 1], arr[k]];
45: }
46: }
要素の数は lengthプロパティによって取得できる。
ランダムに要素を取り出すには、Math.random 関数を使う。戻り値が浮動小数なので、Math.floor 関数を使い、小数点以下を切り捨てる。
コラム:完全数を求める
たとえば 6 の約数は 1, 2, 3, 6 だが、自分自身を除いた和 1 + 2 + 3 は 6 に等しくなるので、6 は完全数である。
古代から 6, 28, 496, 8128 の4つが完全数であることが知られているが、ピタゴラスが名付けたとか、世界を創造した6日間と月の公転周期の28日が含まれているからキリスト教の神を完全性を表しているなどと言われてきた。
perfectNumbers1.html
23: const RANGE_MIN = 2; //探索範囲の最小値
24: const RANGE_MAX = 100000; //探索範囲の最大値
perfectNumbers1.html
38: /**
39: * 完全数かどうかを判定する.
40: * @param Number num 判定する値
41: * @return Boolean true:完全数である / false:完全数ではない
42: */
43: function isPerfectNumber(num) {
44: let sum = 0;
45:
46: // 自分自身以外の約数の和を求める
47: for (let i = 1; i < num; i++) {
48: if (num % i === 0) {
49: sum += i;
50: }
51: }
52:
53: // 和が元の数と等しければ完全数
54: return sum === num;
55: }
perfectNumbers1.html
57: /**
58: * 完全数を求める.
59: * @param Number min 探索範囲の最小値
60: * @param Number max 探索範囲の最大値
61: * @return Array: 完全数の配列
62: */
63: function findPerfectNumbers(min, max) {
64: let perfectNumbers = []; // 完全数を格納する配列
65:
66: for (let i = min; i <= max; i++) {
67: if (isPerfectNumber(i)) {
68: perfectNumbers.push(i);
69: }
70: }
71:
72: return perfectNumbers;
73: }
perfectNumbers1.html
88: // 完全数を求める
89: console.time('計算時間'); // 計算時間の計測開始
90: perfectNumbers = findPerfectNumbers(RANGE_MIN, RANGE_MAX);
91: ss = '計算範囲:' + RANGE_MIN.toLocaleString() + '~' + RANGE_MAX.toLocaleString() + '<br><br>';
92: console.timeEnd('計算時間'); // 計算時間をconsoleに表示する
93:
94: // 完全数を表示する
95: for (let i = 0; i < perfectNumbers.length; i++) {
96: ss += perfectNumbers[i].toLocaleString() + '<br>';
97: }
98: document.getElementById('results').innerHTML = ss;
10万までの探索をやらせると、けっこう時間がかかる。しかも悲しいことに、冒頭の4つの完全数しか見つからない。5番目の完全数は 33550336 であり、これに到達するまでには相当な時間がかかるだろう。2021年8月現在発見されている完全数は51個で、51個目は2486万2048桁もある。
ユークリッド・オイラーの定理によると、
\[ P = 2^{p - 1} \times (2^p - 1) \]
\( p \)が素数であり、なおかつ \( 2^p - 1 \) も素数である場合(これをメルセンヌ素数と呼ぶ)、\( P \) は偶数の完全数になる。
perfectNumbers2.html
24: const RANGE_MIN = 1; //探索範囲の最小値
25: const RANGE_MAX = 100000; //探索範囲の最大値
perfectNumbers2.html
39: /**
40: * 素数かどうかを判定する.
41: * @param Number num 判定する値
42: * @return Boolean True:素数である / False:素数ではない
43: */
44: function isPrime(num) {
45: if (num < 2) {
46: return false;
47: }
48: for (let i = 2; i <= Math.sqrt(num); i++) {
49: if (num % i === 0) {
50: return false;
51: }
52: }
53: return true;
54: }
perfectNumbers2.html
56: /**
57: * メルセンヌ素数かどうかを判定する.
58: * @param Number p 2のべき乗数
59: * @return Boolean True:メルセンヌ素数である / False:メルセンヌ素数ではない
60: */
61: function isMersennePrime(p) {
62: let mersenneNumber = Math.pow(2, p) - 1;
63:
64: return isPrime(mersenneNumber);
65: }
perfectNumbers2.html
67: /**
68: * 完全数を求める.
69: * @param Number min 探索範囲の最小値
70: * @param Number max 探索範囲の最大値
71: * @return Array 完全数の配列
72: */
73: function findPerfectNumbers(min, max) {
74: let perfectNumbers = []; // 完全数を格納する配列
75:
76: if (min < 2) {
77: min = 2;
78: }
79: p = 2;
80: while (true) {
81: if (isMersennePrime(p)) {
82: let num = Math.pow(2, p - 1) * (Math.pow(2, p) - 1);
83: if (num > max) break
84: perfectNumbers.push(num);
85: }
86: p++;
87: }
88: return perfectNumbers;
89: }
perfectNumbers2.html
104: // 完全数を求める
105: console.time('計算時間'); // 計算時間の計測開始
106: perfectNumbers = findPerfectNumbers(RANGE_MIN, RANGE_MAX);
107: ss = '計算範囲:' + RANGE_MIN.toLocaleString() + '~' + RANGE_MAX.toLocaleString() + '<br><br>';
108: console.timeEnd('計算時間'); // 計算時間をconsoleに表示する
109:
110: // 完全数を表示する
111: for (let i = 0; i < perfectNumbers.length; i++) {
112: ss += perfectNumbers[i].toLocaleString() + '<br>';
113: }
114: document.getElementById('results').innerHTML = ss;
ただし、偶数にしても奇数にしても、完全数が無限に存在するのかどうかは未解決の問題である。
このような計算量の多い問題では、前提条件を置くことで計算量を劇的に減らせることができる場合がある。結果を早く求めることができればユーザー喜ぶから、システムの非機能要件として計算量を減らすことを検討するといい。
読みやすいプログラム:コメント
かつて、アセンブリやC言語のようなプログラミング言語では、関数や変数名が省略形であったり、独特なプログラミング記述法があったために、コメントは不可欠であった。
プログラムを読めば分かることをコメントに書くのは二度手間であるし、バージョンアップ時にいずれか一方の更新を忘れるとバグの温床になることから、コメントは必要最小限にとどめるべきというのが、今風の読みやすいプログラムとなる。
●コメントに記載する内容
- プログラム使用上の注意、制約条件など。
- プログラム使用にあたって準備すること、実施することなど。
- 参照しているライブラリ、フレームワーク、APIなどの情報。
- プログラムの著作権情報。
- コメントで補足しないと分からないような複雑な箇所。
- 変数やプロパティの1つ1つに対する解説。
- プログラムを読めば分かる内容。ただし、読み手のスキルに応じてコメントを付加すること。
- プログラムの更新履歴。ただし、バージョン管理システムを利用していない場合はコメントとして記述すること。
/**たとえば、次のようなコメントである。
* (1行目)機能概要【必須】
* (2行目)機能詳細,制約事項など【省略可能】
* @param{空白}変数の型{空白}変数名{空白}内容‥‥引数1の説明【省略可能】
* @param{空白}変数の型{空白}変数名{空白}内容‥‥引数2の説明【省略可能】
* (1行に1引数を記述する.)
* @return{空白}変数の型‥‥戻り値の説明【省略可能】
*/
/**
* 点数表を作成し,画面に表示する.
* @param Array scores 点数表
* @return なし
*/
関数/メソッド | 機能 | 詳細 |
---|---|---|
getLastModified | ファイル更新日を返す | |
dispScoreTable | 点数表を作成し,画面に表示する. |
読みやすいプログラム:引数の変更・上書きはしない
let a = 1;上のようなプログラムの場合、最初の領域に1が代入され、2行目には別領域が用意され、そこへ2が代入される。そして、変数 a の参照先を2番目の領域に更新する。
a = 2;
これが引数にしたときにも起きるので、じつは参照渡しに近いことをやっているのだが、結果的に値渡ししているように見える。
配列の場合は、配列が格納されている領域のポインタ(のようなもの)を渡す。引数で渡すときには、あらたな領域に代入されるのだが、そこには同じ値のポインタ(のようなもの)を代入するので、値渡しのように要素の値を変えることができる。
つまり、JavaScriptの場合、プリミティブ型でもオブジェクト型でも、仕様上は同じ方法で引数を渡している。これを共有渡しと呼ぶ方もいるが、正式な呼称はないようだ。
また、内部的にどのような渡し方をしているかはブラウザの実装によるので、実際に同じ方法で渡しているかどうかは分からない。
このあたりの事情は、@yuta0801氏の記事「JavaScriptに参照渡し/値渡しなど存在しない」に詳しい。
このようにJavaScriptの引数には癖があるため、引数の変更・上書きはしないことが読みやすいプログラムとなる。
引数は、見た目の上では、Number型やString型のようなプリミティブ型の場合は参照渡しで、配列などオブジェクト型は値渡しで渡される。(JavaScriptの引数渡しは、他の言語の参照渡しや値渡しとは異なる。)