著者:井上竜輔
最終回では、筆者が作成したプログラミング学習アプリの概要を紹介します。このアプリは、AIなどの外部APIを活用して、オンラインで動的に問題を生成、実行、評価できるものです。安全性を考慮して、ユーザーが提出したプログラムは「Judge0 CE」というオンラインコード実行システムで、実行、評価します。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。
図7 「components/CodeEditor.tsx」ファイルのコード(抜粋)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
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 フロントエンドからのリクエストを受け取るコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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にリクエストを送り、結果をフロントエンドに返すコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
あなたは優秀な競技プログラミングの問題作成者です。 以下の制約に従って、新しいプログラミング問題を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による解答例のコード" } |