Web プログラミングの初歩
この章では教材の「初めてのウェブ開発」から「ウェブサイトの見た目を整える」までの内容を扱っています。
1. HTML
HTML を用いて次のよ うなウェブサイトを作ってみましょう。ut.code(); のロゴはここからダウンロードして使用してください。
解答例
解答例
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>ut.code(); のウェブサイト</title>
</head>
<body>
<h1>ut.code();</h1>
<img src="logo.svg" alt="ut.code(); のロゴ" />
<h2>ut.code(); とは</h2>
<p>
ut.code(); は、2019
年設立の東京大学のソフトウェアエンジニアリングコミュニティです。
</p>
<h2>主な活動</h2>
<ul>
<li>
学習
<ul>
<li>学習カリキュラムの作成</li>
<li>自主ゼミの開講</li>
<li>「はじめてのプログラミング教室」の出展</li>
</ul>
</li>
<li>
交流
<ul>
<li>作業会の開催</li>
<li>五月祭・駒場祭への出展</li>
<li>合宿の開催</li>
</ul>
</li>
<li>
開発
<ul>
<li>プロジェクトの開発</li>
<li>ブレインストーミングの実施</li>
<li>定例ミーティングの開催</li>
</ul>
</li>
</ul>
</body>
</html>
2. 名前の長さ
太郎くんは、名前を 4 文字以上 10 文字以下で入力して登録できるウェブサイトを作ろうとしています。そこで、名前の文字数を変数に入れておき、
- 文字数が 4 文字以上 10 文字以下なら
登録できます
- 文字数が 0 文字なら
名前を入力してください
- それ以外の文字数(1 文字〜3 文字または 11 文字以上)なら
名前は4文字以上10文字以下で入力してください
と表示されるプログラムを以下のように作成しました。
const nameLength = 7;
if (4 <= nameLength <= 10) {
document.write("登録できます");
}
if (nameLength === 0) {
document.write("名前を入力してください");
} else {
document.write("名前は4文字以上10文字以下で入力してください");
}
しかし、変数の値を色々変えて試してみたところ、思った通りに表示されないことがわかりました。このプログラムを、正しく動作するように修正してください。修正すべき箇所は 2 つあります。
解答例
解答例
const nameLength = 7;
if (4 <= nameLength && nameLength <= 10) {
// 比較演算子を2つ並べるのではなく、論理演算子&&を用います。
document.write("登録できます");
} else if (nameLength === 0) {
// else if の else が抜けていました。
document.write("名前を入力してください");
} else {
document.write("名前は4文字以上10文字以下で入力してください");
}
3. ブラックジャック
田中さんと佐藤さんの 2 人が、トランプゲームのブラックジャックで遊んでいます。ブラックジャックのルールは次の通りとします。
- 2 人の手札の数字の合計を比べ、より大きい方が勝ちとなる。
- ただし、どちらか 1 人の手札の数字の合計が 21 を超えていた場合、その人の負けとなる。
- 2 人の手札の数字の合計が同じだった場合や、2 人とも 21 を超えていた場合は引き分けとなる。
田中さんと佐藤さんの手札の数字の合計をそれぞれ変数に入れておき、田中さんと佐藤さんのどちらが勝つか、あるいは引き分けかを表示するプログラムを作成してください。
解答例
解答例
let tanakaHandTotal = 19;
let satoHandTotal = 22;
// 21を超えていた場合は0(最弱)として扱う
if (tanakaHandTotal > 21) tanakaHandTotal = 0;
if (satoHandTotal > 21) satoHandTotal = 0;
if (tanakaHandTotal > satoHandTotal) {
document.write("田中さんの勝ち");
} else if (tanakaHandTotal < satoHandTotal) {
document.write("佐藤さんの勝ち");
} else {
document.write("引き分け");
}
4. トラック
引っ越しトラックを考えます。
- 段ボール箱の数が 30 個以内
- 合計の重量が 2000 kg 以内
の両方の条件を満たすときに、トラックは出発できます。
「箱の数」「合計の重量」の 2 つの変数の値によって、
- 出発できる場合には「出発できます」
- 箱が多すぎる場合には「箱の数を減らしてください」
- 重量が大きすぎる場合には「重量を減らしてください」
- 箱が多すぎかつ重量も重すぎる場合には「箱の数と重量を減らしてください」
と表示されるプログラムを作ってみましょう。
また、出発できる場合には文字を緑色で、出発できない場合には文字を赤色で表示するようにしましょう。
document.write()
は文字列だけでなく、HTML 要素を出力することができます。
document.write('<p style="color: blue">Hello World!</p>');
解答例
解答例
let box = 25;
let weight = 1000;
if (box <= 30 && weight <= 2000) {
document.write('<p style="color: green">出発できます</p>');
} else if (box > 30 && weight <= 2000) {
document.write('<p style="color: red">箱の数を減らしてください</p>');
} else if (box <= 30 && weight > 2000) {
document.write('<p style="color: red">重量を減らしてください</p>');
} else {
document.write('<p style="color: red">箱の数と重量を減らしてください</p>');
}
5. 九九
HTML の table
, tr
, td
タグ を用いて、九九の表を画面に表示させてみましょう。
開きタグと閉じタグをどのタイミングで出力すべきかに注意しましょう。
解答例
解答例
document.write("<table>");
for (let i = 1; i <= 9; i++) {
document.write("<tr>");
for (let j = 1; j <= 9; j++) {
document.write(`<td> ${i * j} </td>`);
}
document.write("</tr>");
}
document.write("</table>");
6. フィボナッチ数列
1,1,2,3,5...というように、前 2 つの数を足すと次の数になるような数の並びをフィボナッチ数列と言います。引数 n
に対してフィボナッチ数列の n 番目の数を返す関数を定義してください。ただし 1 番目と 2 番目の数は 1 とします。
解答例
解答例
function fibonacci(n) {
if (n <= 2) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
// このように、関数が自分自身を呼び出すときその関数を再帰関数と呼びます。
別解
もし「配列」「繰り返し」という概念をすでに学習している場合、次のような書き方もできます。
function fibonacci(n) {
let sequence = [1, 1];
for (let i = 2; i < n; i += 1) {
sequence.push(sequence[i - 1] + sequence[i - 2]);
}
return sequence[n - 1];
}
7. バブルソート
引数に対して「バブルソート」という整列アルゴリズムを行い、整列済み配列を返す関数bubbleSort()
を作成しましょう。
そのアルゴリズムは次の通りです。
- ソート前配列のある要素を取り、その一つ後ろの要素と比較する
- もし一つ後ろの要素の方が小さければ、二項を入れ替える
- そうでなければ、何もしない
- その操作を一番前の要素から一番後ろにたどり着くまで繰り返す
- これで一番後ろの要素が一番大きいものであると確定する
- 上の操作を、全ての要素が後ろから大きい順に並ぶまで繰り返す
テスト用に、以下のランダムに生成された配列を自由に使ってよいものとします。
const array1 = [7, 1, 10, 4, 3, 5, 9, 2, 8, 6];
const array2 = [8, 2, 9, 14, 12, 1, 5, 13, 16, 3];
const array3 = [73, 39, 94, 57, 42, 78, 20, 55, 56, 77];
const array4 = [247, 785, 73, 879, 515, 545, 423, 617, 19, 600];
要素を入れ替えるとき、単に
array[0] = array[1];
array[1] = array[0];
とするだけでは両方がarray[1]
になってしまい、うまくいきません。どうすればよいでしょうか。
解答例
解答例
function swapIndex(array, indexA, indexB) {
const temp = array[indexA];
array[indexA] = array[indexB];
array[indexB] = temp;
}
function bubbleSort(array) {
for (let i = array.length - 1; i > 0; i--) {
for (let j = 0; j < i; j++) {
if (array[j] > array[j + 1]) swapIndex(array, j, j + 1);
}
}
return array;
}
副作用と 純粋関数 の話
本解答例では返り値をarrayに代入していないにも関わらず、arrayの中身が変わってしまいます。なぜでしょうか?
参照の節で説明したように、これは配列が評価されたときにそれ自身ではなく、配列の参照が得られるからです。関数を実行したときに返り値以外に関数外部に影響を与えることを副作用と呼び、副作用を持たない関数を純粋関数と呼びます。思わぬ副作用を防ぐために有効範囲の広い関数は純粋関数であることが望ましいとされています。また関数のマクロ的役割である「操作のまとまり」として、副作用である外部へのアクセス(画面への表示、インターネットのアクセス、ファイルの入出力など)をひとまとめにする場合は、専用の関数を作成し、それを明示した方がよいとされています。
また関数の引数以外の可変変数を参照することは参照透過性の妨げになり、デバッグが難しくなるといわれています。
上の関数を純粋関数に書き直すと例えば、
解答例
+ /* swapIndexは純粋関数ではないが、あくまでモジュール化のプロセスであり、
+ 外部では使わないので、予期せぬ副作用は発生しにくいためこのままでもよ い */
function swapIndex(array, indexA, indexB) {
const temp = array[indexA];
array[indexA] = array[indexB];
array[indexB] = temp;
}
- function bubbleSort(array) {
+ function bubbleSort(inputArray) {
+ let array = inputArray.slice(); // 配列の値をコピーする
for (let i = array.length - 1; i > 0; i--) {
for (let j = 0; j < i; j++) {
if (array[j] > array[j + 1]) swapIndex(array, j, j+1);
}
}
return array;
}
となります。