著者:井上竜輔
最終回では、筆者が作成したプログラミング学習アプリの概要を紹介します。このアプリは、AIなどの外部APIを活用して、オンラインで動的に問題を生成、実行、評価できるものです。安全性を考慮して、ユーザーが提出したプログラムは「Judge0 CE」というオンラインコード実行システムで、実行、評価します。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。![]()
![]()
図7 「components/CodeEditor.tsx」ファイルのコード(抜粋)
import React from 'react';
import Editor from '@monaco-editor/react';
import { Select, MenuItem, FormControl, InputLabel, Box } from '@mui/material';
(略)
const CodeEditor: React.FC<Props> = ({ language, setLanguage, code, setCode }) => {
return (
<Box sx={{ mt: 2 }}>
<FormControl sx={{ mb: 1, minWidth: 120 }}>
<InputLabel>Language</InputLabel>
<Select
value={language}
onChange={(e) => setLanguage(e.target.value)}
label="Language"
>
<MenuItem value="python">Python</MenuItem>
<MenuItem value="cpp">C++</MenuItem>
<MenuItem value="javascript">JavaScript</MenuItem>
</Select>
</FormControl>
<Editor
height="40vh"
language={language}
value={code}
onChange={(value) => setCode(value || '')}
theme="vs-dark"
/>
</Box>
);
};
図8 handleSubmitCode関数の定義コード
const handleSubmitCode = async () => {
if (!problem) return;
setIsSubmitting(true);
let finalVerdict = "AC (Accepted)";
// サンプルケースごとにコードを実行、検証
for (let i = 0; i < problem.samples.length; i++) {
const sample = problem.samples[i];
try {
// APIを呼び出してコードを実行
const judgeResult = await submitCode(language, code, sample.input);
// 結果を検証
if (judgeResult.status.id === 3) {
const userOutput = (judgeResult.stdout || '').trim();
const expectedOutput = sample.output.trim();
if (userOutput !== expectedOutput) {
finalVerdict = "WA (Wrong Answer)";
}
} else {
finalVerdict = judgeResult.status.description;
}
} catch (error) {
}
// 最初の失敗で処理を中断
if (finalVerdict !== "AC (Accepted)") {
break;
}
// APIのレート制限を避けるための待ち時間
if (i < problem.samples.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
setResult(finalVerdict);
setIsSubmitting(false);
};
図9 フロントエンドからのリクエストを受け取るコード
import type { NextApiRequest, NextApiResponse } from 'next';
import axios from 'axios';
// Judge0 CEの言語IDをマッピング
const LANGUAGE_MAP: { [key: string]: number } = {
python: 71, // Python 3.8.1
cpp: 54, // C++ (GCC 9.2.0)
javascript: 63, // JavaScript (Node.js 12.14.0)
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'メソッドはポストではありません。' });
}
const { language, code, stdin } = req.body;
if (!language || !code || !stdin) {
return res.status(400).json({ error: '要素が足りません。' });
}
const languageId = LANGUAGE_MAP[language];
if (!languageId) {
return res.status(400).json({ error: 対応していない言語です。 ${language} });
}
(略)
}
図10 Judge0 CEのAPIにリクエストを送り、結果をフロントエンドに返すコード
const judge0ApiKey = process.env.NEXT_PUBLIC_JUDGE0_API_KEY;
const options = {
method: 'POST',
url: 'https://judge0-ce.p.rapidapi.com/submissions',
params: {
base64_encoded: 'false',
wait: 'true', // 実行完了まで待機する
},
headers: {
'x-rapidapi-host': 'judge0-ce.p.rapidapi.com',
'x-rapidapi-key': judge0ApiKey, // 環境変数から読み込んだAPIキー
'content-type': 'application/json',
},
data: {
language_id: languageId,
source_code: code,
stdin: stdin,
},
};
try {
const submissionResponse = await axios.request(options);
// Judge0 CEからの結果をフロントエンドに返す
res.status(200).json(submissionResponse.data);
} catch (error) {/* エラーハンドリング*/}
図11 Geminiに送信するプロンプトの例
あなたは優秀な競技プログラミングの問題作成者です。
以下の制約に従って、新しいプログラミング問題を1つ作成してください。
# 制約
- 難易度: AtCoder ProblemsのDifficulty基準で「B」レベル
- トピック: 配列の操作、または文字列の探索
- 入力: 標準入力から受け取ること
- 出力: 標準出力へ書き出すこと
- 形式: 以下のJSONフォーマットに従うこと
# JSONフォーマット
{
"title": "問題タイトル",
"statement": "問題文。背景やストーリーを簡潔に記述。",
"constraints": [
"Nは1以上100以下の整数",
"Sは英小文字からなる長さNの文字列"
],
"input_format": "入力形式の説明",
"output_format": "出力形式の説明",
"samples": [
{ "input": "入力例1", "output": "出力例1" },
{ "input": "入力例2", "output": "出力例2" },
{ "input": "入力例3", "output": "出力例3" }
],
"solution_code": "Pythonによる解答例のコード"
}