<!DOCTYPE html>
<html lang="ja-JP">
<head>
<meta charset="UTF-8" />
<title>ライブコードサンプル</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto&family=Noto+Sans+JP&family=Noto+Color+Emoji&display=swap"
/>
<link rel="stylesheet" href="lib/style.css" />
</head>
<body>
<h1>時間のかかる処理</h1>
<p>日本の「新川」という地名のうち、郵便番号が素数のものを抜き出します!</p>
<p>インターバル処理: <span id="random"></span></p>
<button id="heavy">重い処理スタート</button>
<section id="report"></section>
<table id="table">
<thead></thead>
<tbody></tbody>
</table>
<script src="lib/script.js"></script>
</body>
</html>
/** ボックスサイズを `border-box` にそろえる */
html {
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: inherit;
font-style: normal;
}
/** サイトの土台を定義 */
html,
body {
font-family: 'Roboto', 'Noto Sans JP', sans-serif;
font-size: 16px;
font-weight: normal;
margin: 0;
padding: 0;
}
#report {
margin-top: 1rem;
border: 1px solid #000;
}
#table {
border: 1px solid #000;
border-collapse: collapse;
}
#table th, #table td {
border: 1px solid #000;
padding: 0.2rem;
}
/** Worker */
const worker = new Worker('./lib/worker.js');
// サブスレッドからのメッセージを待ち受けるイベントを定義
worker.addEventListener('message', onMessage);
const random = document.querySelector('#random');
const btnBegin = document.querySelector('#heavy');
const report = document.querySelector('#report');
const table = document.querySelector('#table');
// ボタンが押されたときのイベントを定義
btnBegin.addEventListener('click', main);
setInterval(()=>random.textContent = Math.floor(Math.random()*999), 100)
let start = 0;
let head = '';
/**
* メイン処理
*
* @returns {Promise<string>}
*/
async function main() {
btnBegin.removeEventListener('click', main);
clear();
report.append(makeParagraph('CSVを読み込む'));
const csv = await readCSV();
await splitMacroTask();
report.append(makeParagraph('CSVをパースする'));
await splitMacroTask();
const body = csv.split('\n');
head = body.shift();
const locations = parseCSV(body);
report.append(makeParagraph('郵便番号が素数である項目を抜き出す'));
await splitMacroTask();
start = Date.now();
/** 素数を抜き出す処理 */
worker.postMessage(locations);
}
/**
* サブスレッドからのメッセージ受信時の処理
*
* @param {MessageEvent} e メッセージイベント
* @param {string[][]} e.data イベントに添えられたメッセージ
*/
async function onMessage(e) {
const result = e.data;
report.append(makeParagraph(`件数: ${result.length} 件`));
await splitMacroTask();
report.append(
makeParagraph(`重い処理にかかった時間: ${Date.now() - start} ミリ秒`)
);
showResult(head.split(','), result);
btnBegin.addEventListener('click', main);
};
/**
* タスク分割用(描画のヘルパです)
*/
const splitMacroTask = () => {
return new Promise((res) => setTimeout(res, 0));
};
/**
* クリア用
*/
const clear = () => {
report.replaceChildren();
table.querySelector('thead').replaceChildren();
table.querySelector('tbody').replaceChildren();
};
/**
* 文字のある <p> エレメントを得る
*
* @param {string[]} strArr
* @returns {string[][]}
*/
const makeParagraph = (str) => {
const p = document.createElement('p');
p.textContent = str;
return p;
};
/**
* 結果を <table> として表示する
*
* @param {string[]} head
* @param {string[][]} result
*/
const showResult = (head, result) => {
const trHead = getTRNode(head, true);
table.querySelector('thead').append(trHead);
for (const row of result) {
const trBody = getTRNode(row);
table.querySelector('tbody').append(trBody);
}
};
/**
* 結果の <tr> タグを得る
*
* @param {string[]} indexies
* @param {boolean} [isHead=false]
*/
const getTRNode = (indexies, isHead = false) => {
const [, , zip, , area, district] = indexies;
const tr = document.createElement('tr');
const cell = isHead ? 'th' : 'td';
tr.innerHTML = `<${cell}>${zip}</${cell}><${cell}>${area}</${cell}><${cell}>${district}</${cell}>`;
return tr;
};
/**
* CSVを読み込む
*
* @returns {Promise<string>}
*/
const readCSV = async () => {
const response = await fetch(
'https://postcode.teraren.com/postcodes.csv?s=新川'
);
const parsed = await response.arrayBuffer();
const decoder = new TextDecoder('shift-jis');
return decoder.decode(parsed).trim();
};
/**
* CSVをノーマライズして配列化
*
* @param {string[]} strArr
* @returns {string[][]}
*/
const parseCSV = (strArr) =>
strArr.map((str) => str.replace('""', '').split(','));
/**
* メッセージ受信時の処理
*
* @param {MessageEvent} e メッセージイベント
* @param {string[][]} e.data イベントに添えられたメッセージ
*/
self.onmessage = (e) => {
const locations = e.data;
self.postMessage(filterPrime(locations));
};
/**
* 郵便番号の合計が素数である項目を抜き出す。ここでは「試し割り法」を使用。
*
* @param {string[][]} body
* @returns {string[][]}
*/
const filterPrime = (body) => body.filter((row) => trialDivision(+row[2]));
/**
* 「試し割り法」で素数かどうかを検証
*
* @param {number} n 対象の数
* @returns {boolean} 素数かどうか
*/
const trialDivision = (n) => {
if (n < 2) {
return false;
}
// 1 づつ数値を上げて、割り切れるかどうかを検証
for (let i = 2; i < n; i++) {
// 割り切れるか判定
if (n % i == 0) {
// 剰余がなければ割り切れているので、素数ではない
return false;
}
}
// 最後まで割り切れなければ、それは素数である
return true;
};