328 lines
12 KiB
Python
328 lines
12 KiB
Python
"""
|
||
Скрипт для конвертации результатов экспериментов из CSV в LaTeX таблицы для lab5.
|
||
|
||
Адаптирован из lab2/csv_to_tex.py для работы с форматом эволюционных стратегий.
|
||
Формат входных данных: "время±стд (поколения±стд) фитнес"
|
||
"""
|
||
|
||
import re
|
||
from pathlib import Path
|
||
|
||
# Настройка цвета для выделения лучших результатов
|
||
# None - только жирным, строка (например "magenta") - жирным и цветом
|
||
HIGHLIGHT_COLOR = "magenta"
|
||
|
||
|
||
def parse_csv_file(csv_path: str) -> tuple[str, list[list[str]]]:
|
||
"""
|
||
Парсит CSV файл с результатами эксперимента.
|
||
|
||
Args:
|
||
csv_path: Путь к CSV файлу
|
||
|
||
Returns:
|
||
Tuple с заголовком и данными таблицы
|
||
"""
|
||
with open(csv_path, "r", encoding="utf-8") as file:
|
||
lines = file.readlines()
|
||
|
||
# Удаляем пустые строки и берём только строки с данными
|
||
clean_lines = [line.strip() for line in lines if line.strip()]
|
||
|
||
# Первая строка - заголовки
|
||
header = clean_lines[0]
|
||
|
||
# Остальные строки - данные
|
||
data_lines = clean_lines[1:]
|
||
|
||
# Парсим данные
|
||
data_rows = []
|
||
for line in data_lines:
|
||
parts = line.split(",")
|
||
if len(parts) >= 2: # mu + хотя бы одно значение
|
||
data_rows.append(parts)
|
||
|
||
return header, data_rows
|
||
|
||
|
||
def extract_time_value(value: str) -> float | None:
|
||
"""
|
||
Извлекает значение времени из строки формата "X.Y±Z.W (...)".
|
||
|
||
Args:
|
||
value: Строка с результатом
|
||
|
||
Returns:
|
||
Время выполнения как float или None если значение пустое
|
||
"""
|
||
value = value.strip()
|
||
if value == "—" or value == "" or value == "–":
|
||
return None
|
||
|
||
# Ищем паттерн "число.число±число"
|
||
match = re.match(r"(\d+\.?\d*)±", value)
|
||
if match:
|
||
return float(match.group(1))
|
||
|
||
# Если нет ±, пробуем просто число перед скобкой
|
||
match = re.match(r"(\d+\.?\d*)\s*\(", value)
|
||
if match:
|
||
return float(match.group(1))
|
||
|
||
return None
|
||
|
||
|
||
def extract_generations_value(value: str) -> float | None:
|
||
"""
|
||
Извлекает среднее число поколений из строки формата "... (X±Y) ...".
|
||
|
||
Args:
|
||
value: Строка с результатом
|
||
|
||
Returns:
|
||
Среднее число поколений как float или None если значение пустое
|
||
"""
|
||
value = value.strip()
|
||
if value == "—" or value == "" or value == "–":
|
||
return None
|
||
|
||
# Ищем паттерн "(число±число)" и берём первое число
|
||
match = re.search(r"\((\d+\.?\d*)±", value)
|
||
if match:
|
||
return float(match.group(1))
|
||
|
||
# Если нет ±, пробуем просто число в скобках
|
||
match = re.search(r"\((\d+\.?\d*)\)", value)
|
||
if match:
|
||
return float(match.group(1))
|
||
|
||
return None
|
||
|
||
|
||
def find_best_time(data_rows: list[list[str]]) -> float | None:
|
||
"""
|
||
Находит минимальное время выполнения среди всех значений в таблице.
|
||
|
||
Args:
|
||
data_rows: Строки данных таблицы
|
||
|
||
Returns:
|
||
Минимальное время или None если нет валидных значений
|
||
"""
|
||
min_time = None
|
||
|
||
for row in data_rows:
|
||
for i in range(1, len(row)): # Пропускаем первую колонку (mu)
|
||
time_value = extract_time_value(row[i])
|
||
if time_value is not None:
|
||
if min_time is None or time_value < min_time:
|
||
min_time = time_value
|
||
|
||
return min_time
|
||
|
||
|
||
def find_best_generations(data_rows: list[list[str]]) -> float | None:
|
||
"""
|
||
Находит минимальное число поколений среди всех значений в таблице.
|
||
|
||
Args:
|
||
data_rows: Строки данных таблицы
|
||
|
||
Returns:
|
||
Минимальное число поколений или None если нет валидных значений
|
||
"""
|
||
min_gens = None
|
||
|
||
for row in data_rows:
|
||
for i in range(1, len(row)): # Пропускаем первую колонку (mu)
|
||
gens_value = extract_generations_value(row[i])
|
||
if gens_value is not None:
|
||
if min_gens is None or gens_value < min_gens:
|
||
min_gens = gens_value
|
||
|
||
return min_gens
|
||
|
||
|
||
def format_value(
|
||
value: str, best_time: float | None = None, best_gens: float | None = None
|
||
) -> str:
|
||
"""
|
||
Форматирует значение для LaTeX таблицы, выделяя лучшие результаты жирным.
|
||
|
||
Args:
|
||
value: Строковое значение из CSV
|
||
best_time: Лучшее время в таблице для сравнения
|
||
best_gens: Лучшее число поколений для сравнения
|
||
|
||
Returns:
|
||
Отформатированное значение для LaTeX
|
||
"""
|
||
value = value.strip()
|
||
if value == "—" or value == "" or value == "–":
|
||
return "—"
|
||
|
||
# Парсим значение: "время±стд (поколения±стд) фитнес"
|
||
# Пример: "60.6±47.9 (37±29) 0.0000"
|
||
pattern = r"(\d+\.?\d*)±(\d+\.?\d*)\s*\((\d+\.?\d*)±(\d+\.?\d*)\)\s+(\d+\.?\d+)"
|
||
match = re.match(pattern, value)
|
||
|
||
if not match:
|
||
# Если не удалось распарсить, возвращаем как есть
|
||
return value
|
||
|
||
time_avg = float(match.group(1))
|
||
time_std = float(match.group(2))
|
||
gens_avg = float(match.group(3))
|
||
gens_std = float(match.group(4))
|
||
fitness = match.group(5)
|
||
|
||
# Формируем части БЕЗ стандартных отклонений
|
||
time_part = f"{time_avg:.1f}"
|
||
gens_part = f"{gens_avg:.0f}"
|
||
|
||
# Проверяем, является ли время лучшим
|
||
is_best_time = best_time is not None and abs(time_avg - best_time) < 0.1
|
||
is_best_gens = best_gens is not None and abs(gens_avg - best_gens) < 0.1
|
||
|
||
# Выделяем лучшее время
|
||
if is_best_time:
|
||
if HIGHLIGHT_COLOR is not None:
|
||
time_part = f"\\textcolor{{{HIGHLIGHT_COLOR}}}{{\\textbf{{{time_part}}}}}"
|
||
else:
|
||
time_part = f"\\textbf{{{time_part}}}"
|
||
|
||
# Выделяем лучшее число поколений
|
||
if is_best_gens:
|
||
if HIGHLIGHT_COLOR is not None:
|
||
gens_part = f"\\textcolor{{{HIGHLIGHT_COLOR}}}{{\\textbf{{{gens_part}}}}}"
|
||
else:
|
||
gens_part = f"\\textbf{{{gens_part}}}"
|
||
|
||
# Не показываем фитнес в таблице, т.к. он всегда близок к нулю
|
||
return f"{time_part} ({gens_part})"
|
||
|
||
|
||
def generate_latex_table(dimension: str, header: str, data_rows: list[list[str]]) -> str:
|
||
"""
|
||
Генерирует LaTeX код таблицы.
|
||
|
||
Args:
|
||
dimension: Размерность задачи (2 или 3)
|
||
header: Заголовок таблицы
|
||
data_rows: Строки данных
|
||
|
||
Returns:
|
||
LaTeX код таблицы
|
||
"""
|
||
# Находим лучшее время и лучшее число поколений в таблице
|
||
best_time = find_best_time(data_rows)
|
||
best_gens = find_best_generations(data_rows)
|
||
|
||
# Извлекаем заголовки колонок из header
|
||
header_parts = header.split(",")
|
||
p_mut_values = header_parts[1:] # Пропускаем "mu \ p_mut"
|
||
|
||
num_cols = len(p_mut_values)
|
||
|
||
latex_code = f""" \\begin{{table}}[h!]
|
||
\\centering
|
||
\\small
|
||
\\caption{{Результаты для $n = {dimension}$. Формат: время в мс (число поколений)}}
|
||
\\begin{{tabularx}}{{{0.95 if num_cols <= 5 else 1.0}\\linewidth}}{{l *{{{num_cols}}}{{Y}}}}
|
||
\\toprule
|
||
$\\mathbf{{\\mu \\;\\backslash\\; p_{{mut}}}}$"""
|
||
|
||
# Добавляем заголовки p_mut
|
||
for p_mut in p_mut_values:
|
||
latex_code += f" & \\textbf{{{p_mut.strip()}}}"
|
||
|
||
latex_code += " \\\\\n \\midrule\n"
|
||
|
||
# Добавляем строки данных
|
||
for row in data_rows:
|
||
mu_value = row[0].strip()
|
||
latex_code += f" \\textbf{{{mu_value}}}"
|
||
|
||
# Добавляем значения для каждого p_mut
|
||
for i in range(1, len(row)):
|
||
value = format_value(row[i], best_time, best_gens)
|
||
latex_code += f" & {value}"
|
||
|
||
# Заполняем недостающие колонки если их меньше чем в заголовке
|
||
for i in range(len(row) - 1, num_cols):
|
||
latex_code += " & —"
|
||
|
||
latex_code += " \\\\\n"
|
||
|
||
latex_code += f""" \\bottomrule
|
||
\\end{{tabularx}}
|
||
\\label{{tab:es_results_{dimension}}}
|
||
\\end{{table}}"""
|
||
|
||
return latex_code
|
||
|
||
|
||
def main():
|
||
"""Основная функция скрипта."""
|
||
experiments_path = Path("lab5_experiments")
|
||
|
||
if not experiments_path.exists():
|
||
print("Папка lab5_experiments не найдена!")
|
||
return
|
||
|
||
tables = []
|
||
|
||
# Обрабатываем файлы dimension_2.csv и dimension_3.csv
|
||
for dimension in [2, 3]:
|
||
csv_file = experiments_path / f"dimension_{dimension}.csv"
|
||
|
||
if csv_file.exists():
|
||
print(f"Обрабатываем {csv_file}...")
|
||
|
||
try:
|
||
header, data_rows = parse_csv_file(str(csv_file))
|
||
best_time = find_best_time(data_rows)
|
||
best_gens = find_best_generations(data_rows)
|
||
latex_table = generate_latex_table(str(dimension), header, data_rows)
|
||
tables.append(latex_table)
|
||
print(
|
||
f"[OK] Таблица для n={dimension} готова (лучшее время: {best_time:.1f} мс, лучшее число поколений: {best_gens:.0f})"
|
||
)
|
||
|
||
except Exception as e:
|
||
print(f"[ERROR] Ошибка при обработке {csv_file}: {e}")
|
||
else:
|
||
print(f"[ERROR] Файл {csv_file} не найден")
|
||
|
||
# Сохраняем все таблицы в файл
|
||
if tables:
|
||
output_file = experiments_path / "tables.tex"
|
||
with open(output_file, "w", encoding="utf-8") as f:
|
||
f.write("% Автоматически сгенерированные LaTeX таблицы\n")
|
||
f.write(
|
||
"% Лучший результат по времени и по числу поколений выделены жирным отдельно\n"
|
||
)
|
||
f.write("% Убедитесь, что подключен \\usepackage{tabularx}\n")
|
||
if HIGHLIGHT_COLOR is not None:
|
||
f.write(
|
||
"% ВНИМАНИЕ: Убедитесь, что подключен \\usepackage{xcolor} для цветового выделения\n"
|
||
)
|
||
f.write(
|
||
"% Используйте \\newcolumntype{Y}{>{\\centering\\arraybackslash}X} перед таблицами\n\n"
|
||
)
|
||
|
||
for i, table in enumerate(tables):
|
||
if i > 0:
|
||
f.write("\n \n")
|
||
f.write(table + "\n")
|
||
|
||
print(f"\n[OK] Все таблицы сохранены в файл '{output_file}'")
|
||
print(f"Сгенерировано таблиц: {len(tables)}")
|
||
else:
|
||
print("Не найдено данных для генерации таблиц!")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
|