Gemini Advancedでコーディングを試す.
最近,Gemini Advancedをよく取り上げているが本当に使い勝手が良い.特にプロンプトに忠実に従うことが求められる作業に適していると思う.
他方,創造性が求められる作業はどうなのかと思い,Weasel Programを試してみた.
出力は下記.(最後の保存のコードがコメントアウトされていたので,その部分のみ手動で修正した)
Weasel Program を可能な限りリッチな表現を用いたコードで書いて.
その過程をアニメーションとして出力できるようにして.
import random
import string
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from typing import List, Tuple, Iterator, Final # 型ヒントのために typing モジュールをインポート
# --- 定数定義 (Constants) ---
TARGET_PHRASE: Final[str] = "METHINKS IT IS LIKE A WEASEL" # 目標フレーズ
CHARACTERS: Final[str] = string.ascii_uppercase + " " # 使用する文字セット (大文字アルファベット + スペース)
POPULATION_SIZE: Final[int] = 100 # 各世代で生成する子孫の数
MUTATION_RATE: Final[float] = 0.05 # 各文字が突然変異を起こす確率 (5%)
TARGET_LENGTH: Final[int] = len(TARGET_PHRASE) # 目標フレーズの長さ
# --- ヘルパー関数 (Helper Functions) ---
def calculate_fitness(individual: str, target: str) -> int:
"""
個体 (文字列) の適応度を計算する。
ターゲットフレーズと一致する文字数を適応度とする。
Args:
individual (str): 評価する個体の文字列。
target (str): 目標の文字列。
Returns:
int: 適応度 (一致した文字数)。
"""
return sum(1 for i, char in enumerate(individual) if char == target[i])
def mutate(parent: str, mutation_rate: float, allowed_chars: str) -> str:
"""
親個体から突然変異を加えた子孫を生成する。
Args:
parent (str): 親となる文字列。
mutation_rate (float): 各文字が変異する確率。
allowed_chars (str): 使用可能な文字セット。
Returns:
str: 突然変異が加えられた新しい文字列。
"""
mutated_chars: List[str] = []
for char in parent:
if random.random() < mutation_rate:
# 変異させる場合は、許可された文字セットからランダムに選ぶ
mutated_chars.append(random.choice(allowed_chars))
else:
# 変異させない場合は、元の文字をそのまま使う
mutated_chars.append(char)
return "".join(mutated_chars)
def generate_random_string(length: int, allowed_chars: str) -> str:
"""
指定された長さのランダムな文字列を生成する。
Args:
length (int): 生成する文字列の長さ。
allowed_chars (str): 使用可能な文字セット。
Returns:
str: ランダムに生成された文字列。
"""
return "".join(random.choice(allowed_chars) for _ in range(length))
# --- Weasel Program シミュレーション ---
def run_weasel_simulation(
target: str,
population_size: int,
mutation_rate: float,
allowed_chars: str
) -> Iterator[Tuple[int, str, int]]:
"""
Weasel Program のシミュレーションを実行し、各世代の最良個体を返すジェネレータ。
Args:
target (str): 目標フレーズ。
population_size (int): 各世代の子孫の数。
mutation_rate (float): 突然変異率。
allowed_chars (str): 使用可能な文字セット。
Yields:
Iterator[Tuple[int, str, int]]: (世代番号, 最良個体の文字列, 適応度) のタプル。
"""
target_len: int = len(target)
current_best_string: str = generate_random_string(target_len, allowed_chars)
current_best_fitness: int = calculate_fitness(current_best_string, target)
generation: int = 0
# 最初の状態をyield
yield generation, current_best_string, current_best_fitness
while current_best_string != target:
generation += 1
# 親から突然変異を加えた子孫集団を生成
offspring_population: List[str] = [
mutate(current_best_string, mutation_rate, allowed_chars)
for _ in range(population_size)
]
# 各子孫の適応度を計算
offspring_fitness: List[Tuple[str, int]] = [
(offspring, calculate_fitness(offspring, target))
for offspring in offspring_population
]
# 適応度が最も高い子孫を選択 (複数いる場合は最初の一つ)
# 親自身の適応度も考慮に入れる(退化を防ぐため)
offspring_fitness.append((current_best_string, current_best_fitness))
best_offspring: Tuple[str, int] = max(offspring_fitness, key=lambda item: item[1])
current_best_string, current_best_fitness = best_offspring
# 現在の世代の最良の結果をyield
yield generation, current_best_string, current_best_fitness
# 進捗表示 (コンソール) - オプション
if generation % 10 == 0 or current_best_string == target:
print(f"Generation {generation:>4}: Fitness={current_best_fitness}/{TARGET_LENGTH} | {current_best_string}")
# --- アニメーション設定 ---
print("Starting Weasel simulation...")
# シミュレーションを実行し、全世代の履歴を取得
simulation_history: List[Tuple[int, str, int]] = list(run_weasel_simulation(
TARGET_PHRASE,
POPULATION_SIZE,
MUTATION_RATE,
CHARACTERS
))
print(f"Simulation finished after {simulation_history[-1][0]} generations.")
# Matplotlibでアニメーションを作成
fig, ax = plt.subplots(figsize=(12, 3)) # 横長のウィンドウ
plt.rcParams['font.family'] = 'monospace' # 等幅フォントを指定
# アニメーションの初期化関数
def init():
ax.clear()
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis('off') # 軸を非表示に
title = ax.set_title("Initializing...")
text_display = ax.text(0.5, 0.5, "", ha='center', va='center', fontsize=18, wrap=True)
return text_display, title
# アニメーションの各フレームを更新する関数
def update(frame_index: int):
generation, current_string, fitness = simulation_history[frame_index]
ax.clear() # 前のフレームの内容をクリア
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis('off')
title = ax.set_title(f"Generation: {generation} | Fitness: {fitness}/{TARGET_LENGTH}")
text_display = ax.text(0.5, 0.5, current_string, ha='center', va='center', fontsize=18, wrap=True)
return text_display, title
print("Creating animation...")
# アニメーションオブジェクトの作成
# interval: フレーム間の待機時間 (ミリ秒)
# repeat: アニメーションを繰り返すか (Falseで1回のみ)
# blit: Trueにすると再描画が高速化される場合があるが、環境によっては問題が起きることも
ani = animation.FuncAnimation(
fig,
update,
frames=len(simulation_history), # フレーム数 = 世代数
init_func=init,
interval=50, # アニメーション速度 (ms)
blit=True,
repeat=False
)
# アニメーションの表示
plt.tight_layout() # レイアウト調整
plt.show()
# --- アニメーションの保存 (オプション) ---
# アニメーションをGIFファイルとして保存します。
# pillow ライブラリが必要です (pip install Pillow)。
print("Saving animation as GIF... (This may take a while)")
try:
save_path = 'weasel_animation.gif'
# Pillow ライブラリを使ってGIFを保存します。fpsは1秒あたりのフレーム数です。
ani.save(save_path, writer='pillow', fps=10)
print(f"Animation saved to {save_path}")
except Exception as e:
print(f"Error saving animation: {e}")
print("Ensure you have the necessary writer library (e.g., pillow) installed.")
print("You can install it using: pip install Pillow")
