See the Pen Keyed Collections Workbook by MONO365 -Color your days- (@monoqlo365) on CodePen.
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Keyed Collections ワークブック(Map / Set / WeakMap / WeakSet)</title>
<style>
:root{--bg:#0f1724;--card:#0b1220;--muted:#9aa4b2;--accent:#7dd3fc;--ok:#86efac;--bad:#fca5a5}
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,'Noto Sans JP',Helvetica,Arial; background:linear-gradient(180deg,#071027 0%, #071229 60%); color:#e6eef6; margin:0; padding:28px}
.wrap{max-width:980px;margin:0 auto}
header{display:flex;gap:16px;align-items:center}
h1{margin:0;font-size:20px}
p.lead{color:var(--muted);margin-top:6px}
.card{background:rgba(255,255,255,0.03);border-radius:12px;padding:18px;margin-top:18px;box-shadow:0 6px 20px rgba(2,6,23,0.6)}
.grid{display:grid;grid-template-columns:1fr 360px;gap:18px}
pre{background:#071427;border-radius:8px;padding:12px;overflow:auto}
code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,'Liberation Mono',monospace}
.controls{display:flex;gap:8px;margin-top:8px}
button{background:transparent;border:1px solid rgba(255,255,255,0.08);padding:8px 10px;border-radius:8px;color:var(--accent);cursor:pointer}
button.primary{background:linear-gradient(90deg,#0369a1,#0891b2);border:none;color:#022;}
.exercise{margin-bottom:14px}
.exercise h3{margin:0 0 8px 0;font-size:15px}
label{display:block;margin:8px 0;color:var(--muted)}
input[type=text], textarea{width:100%;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,0.06);background:transparent;color:inherit}
.output{background:#021226;padding:10px;border-radius:8px;margin-top:8px;color:var(--muted);min-height:30px}
.ok{color:var(--ok)} .bad{color:var(--bad)}
.solution{background:linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));padding:12px;border-radius:8px;margin-top:10px;border:1px dashed rgba(255,255,255,0.03)}
details{margin-top:10px}
.console{background:#010613;color:#dff6ff;padding:12px;border-radius:8px;font-family:ui-monospace,monospace}
footer{margin-top:22px;color:var(--muted);font-size:13px}
@media(max-width:980px){.grid{grid-template-columns:1fr}}
</style>
</head>
<body>
<div class="wrap">
<header>
<div>
<h1>Keyed Collections ワークブック(Map / Set / WeakMap / WeakSet)</h1>
<p class="lead">初心者〜上級。演習問題と解説/実行・自動採点機能つき(ブラウザ上で確認できます)</p>
</div>
</header>
<div class="card grid">
<main>
<section class="card">
<h2>使い方ガイド</h2>
<p class="lead">左の練習を解いて、右側で自分のコードを実行・解答確認できます。各問題には「解答表示」ボタンと、自動採点のヒントがあります。</p>
</section>
<section class="card" id="exercises">
<h2>練習問題(初級〜上級)</h2>
<div class="exercise" id="q1">
<h3>Q1. Mapの上書き(初級)</h3>
<p>次のコードの結果をコンソールに出力してください。</p>
<pre><code id="code1">const fruits = new Map();
fruits.set("apple", 3);
fruits.set("banana", 5);
fruits.set("apple", 10);
console.log(fruits.get("apple"));
console.log(fruits.size);
</code></pre>
<div class="controls">
<button onclick="runSnippet('code1')">Run</button>
<button onclick="showSolution('s1')">解答を表示</button>
</div>
<div id="s1" class="solution" style="display:none">
<strong>解答:</strong><br>10<br>2
<details><summary>解説</summary>
Map はキーがユニークなので、同じキーに再代入すると上書きされます。
</details>
</div>
</div>
<div class="exercise" id="q2">
<h3>Q2. Setの重複除去(初級)</h3>
<pre><code id="code2">const numbers = new Set([1, 2, 2, 3]);
numbers.add(3);
numbers.add(4);
console.log(numbers.size);
</code></pre>
<div class="controls">
<button onclick="runSnippet('code2')">Run</button>
<button onclick="showSolution('s2')">解答を表示</button>
</div>
<div id="s2" class="solution" style="display:none">
<strong>解答:</strong>4
<details><summary>解説</summary>
Set は重複を許しません。1,2,3,4 の 4 要素になります。
</details>
</div>
</div>
<div class="exercise" id="q3">
<h3>Q3. Map の has / get(初級)</h3>
<pre><code id="code3">const prices = new Map();
prices.set("pen", 100);
prices.set("book", 300);
console.log(prices.has("book"));
console.log(prices.get("pencil"));
</code></pre>
<div class="controls">
<button onclick="runSnippet('code3')">Run</button>
<button onclick="showSolution('s3')">解答を表示</button>
</div>
<div id="s3" class="solution" style="display:none">
<strong>解答:</strong><br>true<br>undefined
<details><summary>解説</summary>
存在しないキーへの get は undefined を返します。
</details>
</div>
</div>
<hr />
<div class="exercise" id="q4">
<h3>Q4. Object と Map のキーの違い(中級)</h3>
<pre><code id="code4">// A: Object
const obj = {};
obj[5] = "five";
console.log(Object.keys(obj));
// B: Map
const map = new Map();
map.set(5, "five");
console.log([...map.keys()]);
</code></pre>
<div class="controls">
<button onclick="runSnippet('code4')">Run</button>
<button onclick="showSolution('s4')">解答を表示</button>
</div>
<div id="s4" class="solution" style="display:none">
<strong>解答:</strong><br>A: ["5"]<br>B: [5]
<details><summary>解説</summary>
Object のキーは文字列化されます。Map は値の型をそのままキーにできます。
</details>
</div>
</div>
<div class="exercise" id="q5">
<h3>Q5. 配列の重複を Set で削除(中級)</h3>
<pre><code id="code5">const items = ["apple", "banana", "apple", "orange", "banana"];
const unique = [...new Set(items)];
console.log(unique);
</code></pre>
<div class="controls">
<button onclick="runSnippet('code5')">Run</button>
<button onclick="showSolution('s5')">解答を表示</button>
</div>
<div id="s5" class="solution" style="display:none">
<strong>解答:</strong><br>["apple", "banana", "orange"]
<details><summary>解説</summary>
Set を使うだけで簡単に重複を取り除けます。
</details>
</div>
</div>
<div class="exercise" id="q6">
<h3>Q6. Map の順序(中級)</h3>
<pre><code id="code6">const order = new Map();
order.set("first", 1);
order.set("second", 2);
order.set("third", 3);
for (const [k, v] of order) {
console.log(k);
}
</code></pre>
<div class="controls">
<button onclick="runSnippet('code6')">Run</button>
<button onclick="showSolution('s6')">解答を表示</button>
</div>
<div id="s6" class="solution" style="display:none">
<strong>解答:</strong><br>first<br>second<br>third
<details><summary>解説</summary>
Map は挿入順を保持します。
</details>
</div>
</div>
<hr />
<div class="exercise" id="q7">
<h3>Q7. WeakMap の用途(上級)</h3>
<pre><code id="code7">const privateData = new WeakMap();
function createUser(name, secret) {
const obj = { name };
privateData.set(obj, secret);
return obj;
}
const user = createUser("Alice", "🍰");
console.log(privateData.get(user));
</code></pre>
<div class="controls">
<button onclick="runSnippet('code7')">Run</button>
<button onclick="showSolution('s7')">解答を表示</button>
</div>
<div id="s7" class="solution" style="display:none">
<strong>解答:</strong><br>🍰
<details><summary>解説</summary>
WeakMap はオブジェクトをキーにして非公開データを保持できます。キーが参照されなくなれば GC により削除されます。
</details>
</div>
</div>
<div class="exercise" id="q8">
<h3>Q8. WeakSet の特性(上級)</h3>
<pre><code id="code8">const ws = new WeakSet();
let obj = {};
ws.add(obj);
console.log(ws.has(obj));
obj = null;
// 注意:ブラウザ環境で GC のタイミングは不明なので次の行は実際に false になるとは限りません
console.log('あとで参照が消えれば WeakSet からも消える可能性があります');
</code></pre>
<div class="controls">
<button onclick="runSnippet('code8')">Run</button>
<button onclick="showSolution('s8')">解答を表示</button>
</div>
<div id="s8" class="solution" style="display:none">
<strong>解答(概念的):</strong><br>true<br>false(または消える)
<details><summary>解説</summary>
WeakSet は弱参照を保持するため、他で参照されなくなった要素は GC により削除されます。ブラウザでは GC の実行タイミングは制御できません。
</details>
</div>
</div>
<div class="exercise" id="q9">
<h3>Q9. SameValueZero(上級)</h3>
<pre><code id="code9">const s = new Set();
s.add(NaN);
console.log(s.has(NaN));
</code></pre>
<div class="controls">
<button onclick="runSnippet('code9')">Run</button>
<button onclick="showSolution('s9')">解答を表示</button>
</div>
<div id="s9" class="solution" style="display:none">
<strong>解答:</strong><br>true
<details><summary>解説</summary>
Set/Map は SameValueZero を使うため、NaN は自身と等しいとみなされます。
</details>
</div>
</div>
<div class="exercise" id="q10">
<h3>Q10. 複合キーでログイン状態を管理(チャレンジ)</h3>
<pre><code id="code10">const loginStatus = new Map();
const user1 = { id: 1, name: "Alice" };
const user2 = { id: 2, name: "Bob" };
// ここに loginStatus.set(...) を書いて両者の状態を登録し、console.log で確認しよう
</code></pre>
<label for="answer10">あなたのコード(loginStatus を使って状態登録)</label>
<textarea id="answer10" rows="4">loginStatus.set(user1, true);
loginStatus.set(user2, false);
console.log(loginStatus.get(user1));
console.log(loginStatus.get(user2));</textarea>
<div class="controls">
<button onclick="runCustom10()">Run</button>
<button onclick="showSolution('s10')">解答を表示</button>
</div>
<div id="s10" class="solution" style="display:none">
<strong>解答例:</strong>
<pre>loginStatus.set(user1, true);
loginStatus.set(user2, false);
console.log(loginStatus.get(user1)); // true
console.log(loginStatus.get(user2)); // false
</pre>
<details><summary>解説</summary>
オブジェクトそのものをキーにできるため、ID 以外の情報をキーにしても安全に識別できます。
</details>
</div>
</div>
</section>
<section class="card">
<h2>ヒント & ベストプラクティス</h2>
<ul>
<li>Map は動的なキー値ペア管理に向く。Object は構造の定義や JSON 送受信向け。</li>
<li>WeakMap / WeakSet はメモリ解放と連動させたい場合にのみ使用する。デバッグや列挙はできない。</li>
<li>Set は配列のユニーク化や集合演算に便利。Spread と組み合わせるとシンプル。</li>
<li>Map のキーにプリミティブ以外を使うときは、オブジェクトの参照アイデンティティが重要(同一参照でないと別キーになる)。</li>
</ul>
</section>
</main>
<aside class="card">
<h2>実行パネル</h2>
<p class="lead">実行結果はここに表示されます(仮想コンソール)。</p>
<div id="console" class="console">Ready — 実行したいコードの <code>Run</code> をクリックしてください。</div>
<div style="margin-top:12px">
<button onclick="clearConsole()">クリア</button>
<button onclick="showAllSolutions()">全解答を表示</button>
</div>
<details style="margin-top:12px"><summary>補足: ブラウザでの WeakMap/WeakSet の注意</summary>
<p class="lead">ブラウザの GC はタイミングが不明なため、WeakMap/WeakSet の自動削除は即座には観測できないことがあります。実装上の特徴として理解しましょう。</p>
</details>
</aside>
</div>
<footer>
<p>このワークブックは学習用サンプルです。必要なら PDF に変換して配布できます。</p>
</footer>
</div>
<script>
function runSnippet(codeId){
const code = document.getElementById(codeId).textContent;
runCode(code);
}
function runCustom10(){
const base = document.getElementById('code10').textContent;
const user = document.getElementById('answer10').value;
runCode(base + '\n' + user);
}
function runCode(code){
clearConsole();
try {
// セーフに console 出力をキャプチャ
const out = [];
const customConsole = {
log: (...args)=>{ out.push(args.map(a=>inspect(a)).join(' ')); updateConsole(out.join('\n')) },
warn: (...args)=>{ out.push('[WARN] '+ args.join(' ')); updateConsole(out.join('\n')) },
error: (...args)=>{ out.push('[ERROR] '+ args.join(' ')); updateConsole(out.join('\n')) }
};
const fn = new Function('console', code);
fn(customConsole);
} catch (e) {
updateConsole('[ERROR] ' + (e && e.stack ? e.stack : String(e)));
}
}
function inspect(v){
try {
if (v === undefined) return 'undefined';
if (v === null) return 'null';
if (typeof v === 'string') return v;
if (typeof v === 'object') return JSON.stringify(v, getCircularReplacer(), 2);
return String(v);
} catch(e){ return String(v); }
}
function getCircularReplacer(){
const seen = new WeakSet();
return function(key, value){
if (typeof value === 'object' && value !== null){
if (seen.has(value)) return '[Circular]';
seen.add(value);
}
return value;
}
}
function updateConsole(text){
document.getElementById('console').textContent = text;
}
function clearConsole(){ document.getElementById('console').textContent = 'Ready — クリアしました。'; }
function showSolution(id){
const el = document.getElementById(id);
if (!el) return;
el.style.display = el.style.display === 'none' ? 'block' : 'none';
}
function showAllSolutions(){
for (let i=1;i<=10;i++){
const el = document.getElementById('s'+i);
if (el) el.style.display = 'block';
}
}
</script>
</body>
</html>
HTMLファイル名: Keyed_Collections_Workbook.html(ワークスペースに表示されています)
内容:練習問題(初級〜上級)+解答・解説、ブラウザ上でコードを実行して結果を確認できるインタラクティブ実行パネル付きです。
やってみてほしいこと:
- 左欄の各問題で Run を押すと右の「実行パネル」に出力が表示されます。
- 自分のコードを編集して Q10 のように動かせます。
- 「解答を表示」または「全解答を表示」で解説を確認できます。


