start-01 この学習室の歩き方
コードを直す前に、何を確かめるか
下の説明と処理図を読み、入力した値がどの判断を通って結果になるかを確認します。実行後は「入力」「途中の値」「期待結果と実際の結果」を比較します。最初から正しく動く必要はありません。
演習 start-01 三つの学習記録を混ぜずに集計する
学習支援チームから、読了を押しただけで実習も完了になる進捗表の修正を依頼されました。ローカルの自己記録を、種類別に集計してください。
範囲: JSで架空の学習イベントを集計します。他サービスへの提出・採点とは接続せず、ローカル集計だけで技能や資格の取得を判定しません。入力にない認定の状態は未判定のまま扱います。
処理図と読み方
- 記録一覧
- 独立した区分
- 重複を除く
- ローカルの集計
記録一覧 → 独立した区分 / 独立した区分 → 重複を除く / 重複を除く → ローカルの集計
独自の説明図。JSで架空の学習イベントを集計します。他サービスへの提出・採点とは接続せず、ローカル集計だけで技能や資格の取得を判定しません。入力にない認定の状態は未判定のまま扱います。
記録一覧:読了・クイズ・実習のイベントを、種類とレッスンIDで読みます。
独立した区分:同じレッスンでも記録の種類が違えば別の集合です。
重複を除く:同じ読了ボタンを二度押しても一つとして数えます。
ローカルの集計:種類別の件数を返します。技能や資格の認定状態は未判定です。
input → policy:kindとlessonのイベント列。
policy → process:区分ごとの集合。
process → result:重複を除いた3種類の件数。
観測順序:
input:読了・クイズ・実習のイベントを、種類とレッスンIDで読みます。policy:同じレッスンでも記録の種類が違えば別の集合です。process:同じ読了ボタンを二度押しても一つとして数えます。result:種類別の件数を返します。技能や資格の認定状態は未判定です。
誤解しやすい点: 読了を記録すれば実習までできる、という理解は誤りです。読了・回答・実行は別の証拠で、ローカルの集計だけでは初めての条件に対応する技能を認定できません。
このコードで確かめること
学習記録を数えるプログラムを直します。read は『読んだ』、quiz は『問題に答えた』、lab は『実習した』という記録です。同じ節を2回読んでも読了数は1です。読了しただけで実習済みにならないよう、種類ごとに数えてください。
プログラムが満たす条件
read、quiz、labを別々に数え、重複を除きます。不明な種類は無視し、officialCompletionは未判定にします。
初めてコードを編集する方へ:入力・記号・実行の読み方
- 入力は教材がケースごとに自動で渡します。例えば input.name は入力の name という項目を読む記述です。下の編集欄に実名や本物のパスワードを入力する必要はありません。
- JSONの
{}は項目の組、[]は順番のある一覧です。例えば{"name":"A"}は name が文字列 A の入力です。true は「はい」、false は「いいえ」、null は値がないことを表します。false が必ず失敗という意味ではありません。 - const は値に名前を付ける記述、=== は同じかの比較、&& は両方、|| はどちらか、! は反対です。return が最後の結果を返し、trace は途中の値を記録します。
- まず変更せず「コードを実行し、結果を比較する」を押します。「見直す」のケースを1つ選び、違う項目を確認します。編集欄の「修正対象」の近くを一か所変えて、同じボタンでもう一度実行します。
- ヒントと参考実装は編集欄の下で開けます。答えを写す前に、どの入力で結果が変わるはずか予想してください。一致したら、修正した条件がなぜ必要かを自分の言葉で説明します。
「修正対象」と書かれた部分を探します。まず変更せず実行し、期待結果と違う箇所を確かめてから、条件や計算を変更してください。
このコードは外部のWebサイトやファイルには接続しません。図の各段階で trace に渡された値を表示します。文字列や計算などのコードは実行しますが、実際の攻撃・送信・削除は行いません。
この説明文は振り返り用に保存します。文章の内容そのものを自動で正誤判定することはありません。
条件を1つ変えて試す
下の入力は最初のケースのコピーです。課題で扱う範囲の値を1つだけ変え、結果を予想してから実行してください。いつもの全ケースに、この入力を1件追加して比較します。入力の意味は上の課題と処理図で確認できます。
修正する課題
修正する課題
read、quiz、labを別々に数え、重複を除きます。不明な種類は無視し、officialCompletionは未判定にします。
初期コード(修正対象)
初期コード(修正対象)
async function run(input, trace) {
trace("input", input);
const sets = {read:new Set(), quiz:new Set(), lab:new Set()};
trace("policy", {kinds:Object.keys(sets), official:"未判定"});
for (const event of input.events) {
/* 修正対象:read、quiz、labを別々に数え、重複を除きます。不明な種類は無視し、officialCompletionは未判定にします。 */
const kind = "read";
if (Object.hasOwn(sets, kind)) sets[kind].add(event.lesson);
trace("process", {kind, lesson:event.lesson, counted:Object.hasOwn(sets, kind)});
}
const output = {read:sets.read.size, quiz:sets.quiz.size, lab:sets.lab.size, officialCompletion:"未判定"};
trace("result", output);
return output;
}ケースごとの入力・期待結果・理由
ケースごとの入力・期待結果・理由
ケース1:正常:種類別の三記録
入力:
{"events":[{"kind":"read","lesson":"A"},{"kind":"quiz","lesson":"A"},{"kind":"lab","lesson":"A"}]}期待結果:
{"read":1,"quiz":1,"lab":1,"officialCompletion":"未判定"}理由:読了、回答、実行は別の証拠。
ケース2:異常:未知の種類
入力:
{"events":[{"kind":"certificate","lesson":"A"}]}期待結果:
{"read":0,"quiz":0,"lab":0,"officialCompletion":"未判定"}理由:証明書らしい名称でもローカル集計に混ぜない。
ケース3:境界:読了の重複
入力:
{"events":[{"kind":"read","lesson":"A"},{"kind":"read","lesson":"A"}]}期待結果:
{"read":1,"quiz":0,"lab":0,"officialCompletion":"未判定"}理由:同じ種類とIDは一度だけ数える。
段階的なヒント
段階的なヒント
イベントのkindが捨てられていませんか。
集合を種類ごとに分けると重複を除けます。
技能や資格の認定に必要な証拠は、この入力にはありません。
参考実装
参考実装
async function run(input, trace) {
trace("input", input);
const sets = {read:new Set(), quiz:new Set(), lab:new Set()};
trace("policy", {kinds:Object.keys(sets), official:"未判定"});
for (const event of input.events) {
const kind = event.kind;
if (Object.hasOwn(sets, kind)) sets[kind].add(event.lesson);
trace("process", {kind, lesson:event.lesson, counted:Object.hasOwn(sets, kind)});
}
const output = {read:sets.read.size, quiz:sets.quiz.size, lab:sets.lab.size, officialCompletion:"未判定"};
trace("result", output);
return output;
}別の状況へ適用する
社員研修:出席・テスト・実地確認を別記録にする。
検証:出席の変更が実地確認を勝手に更新しないこと。
リリース判定:実装・テスト・本番反映の証拠を分離する。
検証:ローカル成功だけで公開完了にならないこと。
到達確認
3種類を独立に数える理由を説明できる。
重複と未知種類のケースを通す。
この出力が技能や資格の認定ではないと説明できる。