Claude 3.7 Sonnetのコーディング能力を試す 5


以前の投稿でClaude 3.7 Sonnetのコーディング能力を試した.その際に描画能力が独特なことをまとめた.そこでSyntax Treeを描画するコードが書けないか試してみた.プロンプトと結果は下記.崩れはあるが一発でこれが出るのはすごい.

言語学のSyntax Tree をリッチに描画できるプログラムを書いて.

React上で動くように使い勝手の良いものを
import React, { useState, useEffect } from 'react';

// SVGで統語木を描画するメインコンポーネント
export default function SyntaxTreeBuilder() {
  const [treeInput, setTreeInput] = useState('(S (NP (DET The) (N cat)) (VP (V chased) (NP (DET the) (N dog))))');
  const [fontSize, setFontSize] = useState(14);
  const [nodeDistance, setNodeDistance] = useState(40);
  const [levelHeight, setLevelHeight] = useState(60);
  const [colorScheme, setColorScheme] = useState('default');
  const [showHelp, setShowHelp] = useState(false);
  const [error, setError] = useState(null);
  const [tree, setTree] = useState(null);
  const [svgDimensions, setSvgDimensions] = useState({ width: 800, height: 400 });

  // カラースキーム定義
  const colorSchemes = {
    default: {
      S: '#4299e1', // 青
      NP: '#68d391', // 緑
      VP: '#f6ad55', // オレンジ
      PP: '#b794f4', // 紫
      CP: '#f687b3', // ピンク
      DP: '#9ae6b4', // 淡い緑
      IP: '#90cdf4', // 淡い青
      AP: '#fbd38d', // 淡いオレンジ
      default: '#a0aec0', // グレー
    },
    pastel: {
      S: '#bee3f8', // 淡い青
      NP: '#c6f6d5', // 淡い緑
      VP: '#feebc8', // 淡いオレンジ
      PP: '#e9d8fd', // 淡い紫
      CP: '#fed7e2', // 淡いピンク
      DP: '#c6f6d5', // 淡い緑
      IP: '#bee3f8', // 淡い青
      AP: '#feebc8', // 淡いオレンジ
      default: '#e2e8f0', // 淡いグレー
    },
    monochrome: {
      S: '#2d3748',
      NP: '#4a5568',
      VP: '#718096',
      PP: '#a0aec0',
      CP: '#cbd5e0',
      DP: '#e2e8f0',
      IP: '#edf2f7',
      AP: '#f7fafc',
      default: '#a0aec0',
    }
  };

  // ブラケット表記を解析して木構造に変換
  const parseTree = (input) => {
    try {
      // 入力文字列が空の場合
      if (!input.trim()) {
        return null;
      }

      // カッコの数が一致するか確認
      const openBrackets = (input.match(/\(/g) || []).length;
      const closeBrackets = (input.match(/\)/g) || []).length;
      if (openBrackets !== closeBrackets) {
        throw new Error('括弧の数が一致しません');
      }

      // 正規表現で不正な構文をチェック
      if (!/^\s*\([A-Za-z0-9]+ /.test(input)) {
        throw new Error('構文が正しくありません');
      }

      let index = 0;

      const parseNode = () => {
        // 空白をスキップ
        while (index < input.length && /\s/.test(input[index])) {
          index++;
        }

        if (index >= input.length) {
          return null;
        }

        // 開始括弧をチェック
        if (input[index] === '(') {
          index++; // 開始括弧をスキップ

          // ラベルを取得
          let label = '';
          while (index < input.length && /[A-Za-z0-9]/.test(input[index])) {
            label += input[index++];
          }

          // 少なくとも1文字のスペースが必要
          if (index < input.length && input[index] !== ' ') {
            throw new Error('ラベルの後にスペースが必要です');
          }

          // 空白をスキップ
          while (index < input.length && /\s/.test(input[index])) {
            index++;
          }

          const children = [];

          // 子ノードを取得
          while (index < input.length && input[index] !== ')') {
            if (input[index] === '(') {
              // 子ノードが括弧で始まる場合は再帰的に解析
              const childNode = parseNode();
              if (childNode) {
                children.push(childNode);
              }
            } else {
              // 終端記号(単語)を取得
              let word = '';
              while (index < input.length && !/[\s\(\)]/.test(input[index])) {
                word += input[index++];
              }
              if (word) {
                children.push({ label: word, isLeaf: true });
              }
            }

            // 空白をスキップ
            while (index < input.length && /\s/.test(input[index])) {
              index++;
            }
          }

          if (index >= input.length) {
            throw new Error('閉じ括弧がありません');
          }

          index++; // 閉じ括弧をスキップ

          return { label, children };
        }

        return null;
      };

      const result = parseNode();
      
      // 残りの文字列をチェック(余分な文字がある場合はエラー)
      while (index < input.length) {
        if (!/\s/.test(input[index])) {
          throw new Error('入力の末尾に余分な文字があります');
        }
        index++;
      }

      return result;
    } catch (err) {
      setError(err.message);
      return null;
    }
  };

  // 木構造を計算してSVG描画用のデータを生成
  const calculateTreeLayout = (root) => {
    if (!root) return null;

    // 木の深さと幅を計算
    const getTreeDimensions = (node, depth = 0) => {
      if (!node) return { depth: 0, width: 0 };
      
      if (node.isLeaf) {
        return { depth, width: 1 };
      }
      
      let maxChildDepth = depth;
      let totalWidth = 0;
      
      for (const child of node.children) {
        const childDim = getTreeDimensions(child, depth + 1);
        maxChildDepth = Math.max(maxChildDepth, childDim.depth);
        totalWidth += childDim.width;
      }
      
      return { depth: maxChildDepth, width: Math.max(1, totalWidth) };
    };
    
    const dimensions = getTreeDimensions(root);
    const treeDepth = dimensions.depth;
    const treeWidth = dimensions.width;
    
    // SVGのサイズを調整
    const width = Math.max(800, treeWidth * nodeDistance * 3);
    const height = (treeDepth + 1) * levelHeight + 80; // 余白を追加
    
    setSvgDimensions({ width, height });
    
    // ノードの位置を計算
    const calculateNodePositions = (node, depth = 0, offset = 0) => {
      if (!node) return { node: null, width: 0 };
      
      const nodeWidth = node.isLeaf ? 1 : node.children.reduce((sum, child) => {
        const childCalc = calculateNodePositions(child, depth + 1, offset + sum);
        child.x = childCalc.x;
        child.y = (depth + 1) * levelHeight + 40; // 上部に余白を追加
        return sum + childCalc.width;
      }, 0);
      
      // このノード自体の位置を計算
      node.x = offset + nodeWidth / 2;
      node.y = depth * levelHeight + 40; // 上部に余白を追加
      
      return { node, width: nodeWidth, x: node.x };
    };
    
    calculateNodePositions(root);
    
    return root;
  };

  // 木構造を再計算
  useEffect(() => {
    try {
      setError(null);
      const parsedTree = parseTree(treeInput);
      const calculatedTree = calculateTreeLayout(parsedTree);
      setTree(calculatedTree);
    } catch (err) {
      setError(err.message || '構文解析エラー');
    }
  }, [treeInput, nodeDistance, levelHeight]);

  // SVGで線を描画
  const renderLines = (node) => {
    if (!node || node.isLeaf || !node.children || node.children.length === 0) {
      return null;
    }

    return node.children.map((child, index) => (
      <React.Fragment key={`line-${node.x}-${node.y}-${index}`}>
        <line
          x1={node.x}
          y1={node.y + 10}
          x2={child.x}
          y2={child.y - 10}
          stroke="black"
          strokeWidth="1.5"
        />
        {renderLines(child)}
      </React.Fragment>
    ));
  };

  // SVGでノードを描画
  const renderNodes = (node) => {
    if (!node) return null;

    const getNodeColor = (label) => {
      // ラベルの最初の部分(例: NPからNを抽出)を使用
      const categoryLabel = label.split('-')[0];
      return colorSchemes[colorScheme][categoryLabel] || colorSchemes[colorScheme].default;
    };

    return (
      <React.Fragment>
        <g transform={`translate(${node.x}, ${node.y})`}>
          {!node.isLeaf ? (
            <rect
              x={-(node.label.length * fontSize * 0.4) / 2}
              y={-15}
              width={node.label.length * fontSize * 0.4 + 20}
              height={30}
              rx={5}
              ry={5}
              fill={getNodeColor(node.label)}
              fillOpacity={0.7}
              stroke="black"
              strokeWidth="1"
            />
          ) : null}
          <text
            textAnchor="middle"
            dominantBaseline="middle"
            fontSize={fontSize}
            fontFamily="Arial"
            fontWeight={node.isLeaf ? "normal" : "bold"}
          >
            {node.label}
          </text>
        </g>
        {node.children && node.children.map((child, index) => renderNodes(child))}
      </React.Fragment>
    );
  };

  // サンプル文を設定
  const setSampleTree = (sample) => {
    setTreeInput(sample);
  };

  return (
    <div className="flex flex-col max-w-6xl mx-auto p-4">
      <h1 className="text-2xl font-bold mb-4">リッチな統語木ビルダー</h1>
      
      <div className="mb-4 flex flex-wrap gap-2">
        <button 
          onClick={() => setSampleTree('(S (NP (DET The) (N cat)) (VP (V chased) (NP (DET the) (N dog))))')}
          className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
        >
          基本例
        </button>
        <button 
          onClick={() => setSampleTree('(CP (C that) (IP (NP (DET the) (N student)) (I\' (I will) (VP (V study) (NP (N linguistics))))))')}
          className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
        >
          CP例
        </button>
        <button 
          onClick={() => setSampleTree('(S (NP (DET その) (N 猫)) (VP (PP (NP (N 魚)) (P を)) (V 食べた)))')}
          className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
        >
          日本語例
        </button>
        <button 
          onClick={() => setShowHelp(!showHelp)}
          className="px-3 py-1 bg-gray-500 text-white rounded hover:bg-gray-600"
        >
          {showHelp ? 'ヘルプを隠す' : 'ヘルプを表示'}
        </button>
      </div>

      {showHelp && (
        <div className="mb-4 p-4 bg-gray-100 rounded-lg">
          <h3 className="font-bold mb-2">使い方:</h3>
          <p className="mb-2">括弧表記で統語木を入力してください。括弧表記の例:</p>
          <pre className="bg-gray-200 p-2 rounded">(S (NP (DET The) (N cat)) (VP (V chased) (NP (DET the) (N dog))))</pre>
          <p className="mt-2">各ノードは (ラベル 内容) の形式で表現されます。内容は他のノードまたは単語です。</p>
        </div>
      )}

      <div className="mb-4">
        <label className="block mb-2 font-semibold">統語木の括弧表記:</label>
        <textarea
          value={treeInput}
          onChange={(e) => setTreeInput(e.target.value)}
          className="w-full p-2 border rounded h-24"
          placeholder="(S (NP (DET The) (N cat)) (VP (V chased) (NP (DET the) (N dog))))"
        />
        {error && <p className="text-red-500 mt-1">{error}</p>}
      </div>

      <div className="mb-4 grid grid-cols-1 md:grid-cols-3 gap-4">
        <div>
          <label className="block mb-2 font-semibold">フォントサイズ: {fontSize}</label>
          <input
            type="range"
            min="10"
            max="20"
            value={fontSize}
            onChange={(e) => setFontSize(parseInt(e.target.value))}
            className="w-full"
          />
        </div>
        <div>
          <label className="block mb-2 font-semibold">ノード間距離: {nodeDistance}</label>
          <input
            type="range"
            min="20"
            max="80"
            value={nodeDistance}
            onChange={(e) => setNodeDistance(parseInt(e.target.value))}
            className="w-full"
          />
        </div>
        <div>
          <label className="block mb-2 font-semibold">レベルの高さ: {levelHeight}</label>
          <input
            type="range"
            min="40"
            max="100"
            value={levelHeight}
            onChange={(e) => setLevelHeight(parseInt(e.target.value))}
            className="w-full"
          />
        </div>
      </div>

      <div className="mb-4">
        <label className="block mb-2 font-semibold">カラースキーム:</label>
        <div className="flex gap-3">
          <button
            onClick={() => setColorScheme('default')}
            className={`px-3 py-1 rounded ${colorScheme === 'default' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}
          >
            デフォルト
          </button>
          <button
            onClick={() => setColorScheme('pastel')}
            className={`px-3 py-1 rounded ${colorScheme === 'pastel' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}
          >
            パステル
          </button>
          <button
            onClick={() => setColorScheme('monochrome')}
            className={`px-3 py-1 rounded ${colorScheme === 'monochrome' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}
          >
            モノクローム
          </button>
        </div>
      </div>

      <div className="overflow-auto border rounded p-4">
        <svg width={svgDimensions.width} height={svgDimensions.height}>
          {tree && renderLines(tree)}
          {tree && renderNodes(tree)}
        </svg>
      </div>
    </div>
  );
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です