2025年2月12日水曜日

索引たんご(1)

単語あてと推論AIからの続き

一日に一回トライできるパズルゲームの索引たんご。12回のトライで正解を挟み込む必要があるのだけれど,これが中々に難しい。単語辞書中の正解の位置が左にパーセンテージで表示されているので,辞書があればより確実に推定できるはずだけれど,それは公開されていない。

しかたがないので,代替の辞書がないものかと探してみたところ,言語資源開発センター『現代日本語書き言葉均衡コーパス』短単位語彙表(Version 1.0)にたどり着いた。なんとなくそれらしい雰囲気が漂っている。Creative Commonsライセンス(CC BY-NC-ND)なので私達には使いやすい。

8MBのgzipファイルを解凍してできたTSVファイルをmacOSのNumbersに読み込ませて,品詞属性や統計データを取り除いて,正規表現で5文字だけのデータを選び出すと36785語が得られた。さらに,sortしてuniqをかけて同音異義語を排除すると29795語になる。ただし両端に「アアアアア」と「ンンンンン」を加えている(実際は,Excelがユニコードに弱いので慣れないNumbersでの作業を強いられたりして,結構面倒なクレンジングになった)。

これをOpenAI o3-mini-highにプログラムに依頼するのだけれど,そのロジックを考えるのに難儀する。こういうことは丁寧な段取りが重要なのである。一発で動くコードができたので,あとは2-3回微修正を入れるとスイスイとできあがった。ロジックの中身はブラックボックスでも構わないが,各動作の機能仕様やデータ構造は押さえる必要があって,今後のプログラミング教育の方向性は若干かなり変わってくるかもしれない。

Q(koshix):
1から29795まで五十音順に番号のついた5文字のカタカナを格納したファイル5.csvがあります。このプログラムがランダムに発生する1-29795の整数m0に対応するファイル内の 5文字のカタカナc0を当てるゲームです。正解のans=(m0,c0)とします。 最小の推定値min=(m1,c1)の初期値は(1,アアアアア)です。 最大の推定値max=(m2,c2)の初期値は(29795,ンンンンン)です。 ユーザはコマンドラインから5文字のカタカナ5文字cを入力します。 このようなコマンドラインで動作するプログラムを作ってください。

A(OpenAI o3-mini-high):
(28秒の推論の後)

以下は、Python を用いてコマンドライン上で動作するプログラムの例です。

このプログラムでは、まず CSV ファイル("5.csv")から 1~36787 番目の「5文字のカタカナ」を読み込み、

・辞書(word → 番号)およびリスト(番号 → word)の両方で管理します。
・ゲーム開始時に、ランダムに 1~29795 の整数 m₀ を選び、その番号に対応するカタカナ c₀ を正解とします。
・ゲームでは、初期の推定範囲を下限 (1, "アアアアア")、上限 (29795, "ンンンンン") とし、
ユーザが入力する 5文字のカタカナ(必ず CSV 内に存在するものとする)をもとに、
入力された単語の CSV 上の番号と正解の m₀ を比較します。
・もし入力の番号が m₀ より小さい場合は下限を更新し、
大きい場合は上限を更新します。
・ユーザの入力が正解に一致するまで繰り返します。

以下、コード例です。

import csv
import random

def load_katakana(file_path):
    """
    CSVファイル(例: "5.csv")から、番号と5文字カタカナの組を読み込み、
    番号→カタカナのリスト(インデックス番号は番号-1)と、
    カタカナ→番号の辞書を返す。
    CSVは各行が「番号,カタカナ」という形式である前提です。
    """
    total = 29795  # ファイルの行数
    words = [None] * total      # 番号 1~29795 に対応(リストのインデックスは 0~29794)
    katakana_to_index = {}
    
    with open(file_path, 'r', encoding='utf-8') as csvfile:
        reader = csv.reader(csvfile)
        for row in reader:
            if not row or len(row) < 2:
                continue
            try:
                idx = int(row[0])
            except ValueError:
                continue  # 番号が数値でない行はスキップ
            word = row[1].strip()
            if 1 <= idx <= total:
                words[idx - 1] = word
                katakana_to_index[word] = idx

    if any(w is None for w in words):
        raise ValueError("CSVファイルの読み込みに失敗しました。行数やデータ形式を確認してください。")
    
    return words, katakana_to_index

def print_range(lower_index, lower_word, upper_index, upper_word, target_index):
    """
    現在の推定範囲と、正解の位置を表すパーセンテージ (p1, p2) を表示する。
    p1: 下限から正解までの割合, p2: 正解から上限までの割合 (p1+p2=100)
    """
    range_span = upper_index - lower_index
    if range_span > 0:
        p1 = (target_index - lower_index) / range_span * 100
        p2 = 100 - p1
    else:
        p1, p2 = 0, 0
    print(f"現在の推定範囲: {lower_index}:{lower_word}  ~  {upper_index}:{upper_word}  (p1:{p1:.1f}%, p2:{p2:.1f}%)\n")

def main():
    file_path = "5.csv"
    
    try:
        words, katakana_to_index = load_katakana(file_path)
    except Exception as e:
        print(f"エラー: {e}")
        return

    # 初期の推定範囲の設定
    lower_index, lower_word = 1, "アアアアア"    # 最小の推定値
    upper_index, upper_word = 29795, "ンンンンン"  # 最大の推定値

    # 正解は、1~29795 の中からランダムに選ぶ(番号 m0 に対応する c0)
    target_index = random.randint(1, 29795)
    target_word = words[target_index - 1]
    
    attempts = 0
    print("=== カタカナ当てゲーム ===")
    print("CSVファイル内の5文字カタカナのうち、\n  番号 1~29795 に対応する単語を当ててください。")
    print_range(lower_index, lower_word, upper_index, upper_word, target_index)

    while True:
        # 試行回数を表示(例:1回目、2回目…)
        print(f"{attempts + 1}回目")
        guess = input("5文字のカタカナを入力してください: ").strip()
        attempts += 1
        
        if len(guess) != 5:
            print("入力エラー:5文字のカタカナを入力してください。\n")
            continue
        
        if guess not in katakana_to_index:
            print("入力されたカタカナはリストに存在しません。\n")
            continue
        
        guess_index = katakana_to_index[guess]
        
        if not (lower_index < guess_index < upper_index):
            print(f"入力された単語は現在の範囲外です。(現在の範囲:{lower_index}~{upper_index})\n")
            continue
        
        if guess_index == target_index:
            print(f"\n正解です!")
            print(f"{attempts}回目の試行で正解 (番号: {target_index}, カタカナ: {target_word}) に辿り着きました。")
            break
        elif guess_index < target_index:
            if guess_index > lower_index:
                lower_index, lower_word = guess_index, guess
            print("ヒント: 入力は小さすぎます。")
        else:  # guess_index > target_index
            if guess_index < upper_index:
                upper_index, upper_word = guess_index, guess
            print("ヒント: 入力は大きすぎます。")
        
        print_range(lower_index, lower_word, upper_index, upper_word, target_index)

if __name__ == "__main__":
    main()

プログラムのポイントと流れ

1.CSV 読み込み
・load_katakana 関数で CSV ファイルから全 29795 行を読み込み、
番号順にリスト words と、単語から番号を引く辞書 katakana_to_index を作成します。
2.正解の設定
・ランダムに 1~29795 の整数 target_index を選び、対応する単語 target_word を正解とします。
3.推定範囲の初期値
・下限は (1, "アアアアア")、上限は (29795, "ンンンンン") としておき、
ユーザの入力によって更新していきます。
4.ユーザ入力と比較
・ユーザはコマンドラインから 5文字のカタカナを入力します。
・入力された単語が CSV 内に存在し、かつ現在の推定範囲内であるかチェックします。
・単語に対応する番号を取り出し、正解の番号と比較します。
  *一致すればゲーム終了。
  *小さい場合は「ヒント:小さすぎる」と表示し、下限を更新。
  *大きい場合は「ヒント:大きすぎる」と表示し、上限を更新。
・現在の推定範囲を画面に表示し、正解するまで繰り返します。

この例は、インタラクティブな当てゲームとして動作します。実際にご利用になる際は、CSV ファイルの配置場所や文字コード(ここでは UTF-8 を想定)などをご確認ください。




図:本家の索引たんごの成功例(チートを利用した場合)
上記の辞書だけを用い,自力で位置を計算してやるとようやく正解にたどり着けた。

P. S.  上述の「『現代日本語書き言葉均衡コーパス』短単位語彙表」だけど,NumbersからExcelにファイル変換すればよかった。5文字以外も含めてすべてで18万5千語ある。このうち,36%が固有名詞,27%が漢語由来,21%が和語,12%が外来語だ。固有名詞の多くが外来語的なものになる。

0 件のコメント: