Claude 3.7 Sonnetのコーディング能力を試す
以前の投稿で様々なAIモデルの使用感をまとめた.今回はClaude 3.7 Sonnetについてまとめたいと思う.Claudeを使うのもかなり久しぶりだ.
まずはWeasel Programを試してみた.すると,Reactを用いて恐ろしいほどリッチなものを作成してくれた.(多少の改善は必要そうであるが)
Weasel Program を可能な限りリッチな表現を用いたコードで書いて.
その過程をアニメーションとして出力できるようにして.
エラーが出ないように自己点検して.
import React, { useState, useEffect, useRef } from 'react';
import { Play, Pause, RotateCcw, Settings } from 'lucide-react';
// Richard Dawkins の有名な "Weasel Program" の実装
export default function WeaselProgram() {
// 目標文字列と設定
const [target, setTarget] = useState("METHINKS IT IS LIKE A WEASEL");
const [currentGeneration, setCurrentGeneration] = useState(0);
const [populationSize, setPopulationSize] = useState(100);
const [mutationRate, setMutationRate] = useState(0.05);
const [delay, setDelay] = useState(50);
const [isRunning, setIsRunning] = useState(false);
const [showSettings, setShowSettings] = useState(false);
const [bestFitness, setBestFitness] = useState(0);
const [history, setHistory] = useState([]);
const [bestString, setBestString] = useState("");
const [initialString, setInitialString] = useState("");
// 使用可能な文字のセット
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ";
const intervalRef = useRef(null);
// 初期ランダム文字列の生成
const generateRandomString = (length) => {
let result = '';
for (let i = 0; i < length; i++) {
result += charset.charAt(Math.floor(Math.random() * charset.length));
}
return result;
};
// 適合度評価(目標文字列との一致度)
const calculateFitness = (str) => {
let score = 0;
for (let i = 0; i < str.length; i++) {
if (str[i] === target[i]) {
score++;
}
}
return score / target.length;
};
// 突然変異を適用
const mutate = (str) => {
let result = '';
for (let i = 0; i < str.length; i++) {
if (Math.random() < mutationRate) {
result += charset.charAt(Math.floor(Math.random() * charset.length));
} else {
result += str[i];
}
}
return result;
};
// 次世代の生成
const evolvePopulation = () => {
// 現在の最良個体を取得
const currentBest = bestString;
// 新しい個体群を生成
let population = [];
for (let i = 0; i < populationSize; i++) {
population.push(mutate(currentBest));
}
// 最良個体を見つける
let bestIndividual = population[0];
let maxFitness = calculateFitness(bestIndividual);
for (let i = 1; i < populationSize; i++) {
const fitness = calculateFitness(population[i]);
if (fitness > maxFitness) {
maxFitness = fitness;
bestIndividual = population[i];
}
}
// 最良個体を更新
setBestString(bestIndividual);
setBestFitness(maxFitness);
setCurrentGeneration(prev => prev + 1);
// 歴史に追加(最大50個まで保存)
setHistory(prev => {
const newHistory = [...prev, { generation: currentGeneration + 1, string: bestIndividual, fitness: maxFitness }];
if (newHistory.length > 50) {
return newHistory.slice(newHistory.length - 50);
}
return newHistory;
});
// 目標達成でストップ
if (maxFitness === 1) {
clearInterval(intervalRef.current);
setIsRunning(false);
}
};
// 進化プロセスの開始/停止
const toggleEvolution = () => {
if (isRunning) {
clearInterval(intervalRef.current);
setIsRunning(false);
} else {
intervalRef.current = setInterval(evolvePopulation, delay);
setIsRunning(true);
}
};
// リセット機能
const resetEvolution = () => {
clearInterval(intervalRef.current);
setIsRunning(false);
setCurrentGeneration(0);
const newInitialString = generateRandomString(target.length);
setInitialString(newInitialString);
setBestString(newInitialString);
setBestFitness(calculateFitness(newInitialString));
setHistory([{ generation: 0, string: newInitialString, fitness: calculateFitness(newInitialString) }]);
};
// 初期化
useEffect(() => {
resetEvolution();
return () => clearInterval(intervalRef.current);
}, [target]);
// 文字表示用のスタイル付けコンポーネント
const CharacterDisplay = ({ char, targetChar }) => {
const isMatch = char === targetChar;
return (
<span className={`inline-block px-1 py-1 font-mono text-xl ${isMatch ? 'bg-green-200 text-green-800' : 'bg-red-100 text-red-800'}`}>
{char}
</span>
);
};
return (
<div className="p-6 max-w-4xl mx-auto bg-white rounded-xl shadow-md">
<h1 className="text-2xl font-bold text-center mb-4">Richard Dawkins のウィーゼルプログラム</h1>
<div className="flex flex-col space-y-6">
{/* 現在の状態表示 */}
<div className="bg-gray-50 p-4 rounded-lg">
<div className="flex justify-between items-center mb-2">
<span className="font-bold">目標文字列:</span>
<div className="flex flex-wrap">
{target.split('').map((char, i) => (
<span key={i} className="inline-block px-1 py-1 font-mono text-xl bg-blue-100 text-blue-800">
{char}
</span>
))}
</div>
</div>
<div className="flex justify-between items-center mb-2">
<span className="font-bold">現在の最良個体:</span>
<div className="flex flex-wrap">
{bestString && bestString.split('').map((char, i) => (
<CharacterDisplay key={i} char={char} targetChar={target[i]} />
))}
</div>
</div>
<div className="grid grid-cols-2 gap-4 mt-4">
<div className="bg-gray-100 p-3 rounded-md">
<span className="font-bold">世代数:</span> {currentGeneration}
</div>
<div className="bg-gray-100 p-3 rounded-md">
<span className="font-bold">適合度:</span> {(bestFitness * 100).toFixed(2)}%
</div>
</div>
</div>
{/* コントロールパネル */}
<div className="flex justify-center space-x-4">
<button
onClick={toggleEvolution}
className={`flex items-center px-4 py-2 rounded-md ${isRunning ? 'bg-amber-500 hover:bg-amber-600' : 'bg-green-500 hover:bg-green-600'} text-white`}
>
{isRunning ? <><Pause size={20} /> 停止</> : <><Play size={20} /> 開始</>}
</button>
<button
onClick={resetEvolution}
className="flex items-center px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md"
>
<RotateCcw size={20} /> リセット
</button>
<button
onClick={() => setShowSettings(!showSettings)}
className="flex items-center px-4 py-2 bg-gray-500 hover:bg-gray-600 text-white rounded-md"
>
<Settings size={20} /> 設定
</button>
</div>
{/* 設定パネル */}
{showSettings && (
<div className="bg-gray-50 p-4 rounded-lg">
<h2 className="text-xl font-bold mb-4">設定</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block mb-2">目標文字列</label>
<input
type="text"
value={target}
onChange={(e) => setTarget(e.target.value.toUpperCase())}
className="w-full p-2 border rounded"
disabled={isRunning}
/>
</div>
<div>
<label className="block mb-2">集団サイズ</label>
<input
type="number"
value={populationSize}
onChange={(e) => setPopulationSize(parseInt(e.target.value))}
min="10"
max="1000"
className="w-full p-2 border rounded"
disabled={isRunning}
/>
</div>
<div>
<label className="block mb-2">突然変異率</label>
<input
type="range"
value={mutationRate}
onChange={(e) => setMutationRate(parseFloat(e.target.value))}
min="0.01"
max="0.5"
step="0.01"
className="w-full"
disabled={isRunning}
/>
<span>{(mutationRate * 100).toFixed(1)}%</span>
</div>
<div>
<label className="block mb-2">更新速度 (ms)</label>
<input
type="range"
value={delay}
onChange={(e) => setDelay(parseInt(e.target.value))}
min="10"
max="500"
step="10"
className="w-full"
disabled={isRunning}
/>
<span>{delay}ms</span>
</div>
</div>
</div>
)}
{/* 進化の履歴グラフ */}
<div className="bg-gray-50 p-4 rounded-lg">
<h2 className="text-xl font-bold mb-4">進化の履歴</h2>
{history.length > 0 && (
<div className="h-64 relative">
{/* グラフ */}
<div className="absolute inset-0">
<svg className="w-full h-full">
<rect x="0" y="0" width="100%" height="100%" fill="#f9fafb" />
{/* グリッド線 */}
{[0.2, 0.4, 0.6, 0.8].map((line) => (
<line
key={line}
x1="0"
y1={`${(1 - line) * 100}%`}
x2="100%"
y2={`${(1 - line) * 100}%`}
stroke="#e5e7eb"
strokeWidth="1"
/>
))}
{/* 適合度ラインチャート */}
{history.length > 1 && (
<polyline
points={history.map((item, i) => `${(i / (history.length - 1)) * 100}%,${(1 - item.fitness) * 100}%`).join(' ')}
fill="none"
stroke="#4f46e5"
strokeWidth="2"
/>
)}
{/* データポイント */}
{history.map((item, i) => (
<circle
key={i}
cx={`${(i / (history.length - 1)) * 100}%`}
cy={`${(1 - item.fitness) * 100}%`}
r="3"
fill="#4f46e5"
/>
))}
</svg>
{/* Y軸ラベル */}
<div className="absolute top-0 left-0 h-full flex flex-col justify-between text-xs text-gray-500 py-2">
<div>100%</div>
<div>80%</div>
<div>60%</div>
<div>40%</div>
<div>20%</div>
<div>0%</div>
</div>
</div>
</div>
)}
</div>
{/* 最近の進化履歴 */}
<div className="bg-gray-50 p-4 rounded-lg">
<h2 className="text-xl font-bold mb-4">進化の記録</h2>
<div className="max-h-60 overflow-auto">
<table className="min-w-full">
<thead className="bg-gray-100">
<tr>
<th className="px-4 py-2 text-left">世代</th>
<th className="px-4 py-2 text-left">文字列</th>
<th className="px-4 py-2 text-left">適合度</th>
</tr>
</thead>
<tbody>
{[...history].reverse().map((item) => (
<tr key={item.generation} className="border-b">
<td className="px-4 py-2">{item.generation}</td>
<td className="px-4 py-2 font-mono">{item.string}</td>
<td className="px-4 py-2">{(item.fitness * 100).toFixed(2)}%</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
}
