341 lines
13 KiB
Python
341 lines
13 KiB
Python
"""
|
||
Скрипт для конвертации результатов экспериментов из CSV в LaTeX таблицы.
|
||
|
||
Этот скрипт автоматически сканирует папку experiments/, находит все подпапки
|
||
с файлами results.csv, парсит данные экспериментов и генерирует LaTeX код
|
||
таблиц в формате, готовом для вставки в отчёт.
|
||
|
||
Структура входных данных:
|
||
- experiments/N/results.csv, где N - размер популяции
|
||
- CSV содержит результаты экспериментов с различными параметрами Pc и Pm
|
||
- Значения в формате "X.Y (Z)" где X.Y - время выполнения, Z - количество итераций
|
||
- "—" для отсутствующих данных
|
||
|
||
Выходной файл: tables.tex с готовым LaTeX кодом всех таблиц.
|
||
Лучшие результаты по времени и фитнесу выделяются жирным (и цветом, если задан HIGHLIGHT_COLOR).
|
||
"""
|
||
|
||
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: # Pc + как минимум одно значение Pm
|
||
data_rows.append(parts)
|
||
|
||
return header, data_rows
|
||
|
||
|
||
def extract_time_value(value: str) -> float | None:
|
||
"""
|
||
Извлекает значение времени из строки формата "X.Y (Z)" или "X.Y (Z) W.V".
|
||
|
||
Args:
|
||
value: Строка с результатом
|
||
|
||
Returns:
|
||
Время выполнения как float или None если значение пустое
|
||
"""
|
||
value = value.strip()
|
||
if value == "—" or value == "" or value == "–":
|
||
return None
|
||
|
||
# Ищем паттерн "число.число (число)"
|
||
match = re.match(r"(\d+\.?\d*)\s*\(", value)
|
||
if match:
|
||
return float(match.group(1))
|
||
|
||
return None
|
||
|
||
|
||
def extract_fitness_value(value: str) -> float | None:
|
||
"""
|
||
Извлекает значение фитнеса из строки формата "X.Y (Z) W.V".
|
||
|
||
Args:
|
||
value: Строка с результатом
|
||
|
||
Returns:
|
||
Значение фитнеса как float или None если значение пустое
|
||
"""
|
||
value = value.strip()
|
||
if value == "—" or value == "" or value == "–":
|
||
return None
|
||
|
||
# Ищем паттерн "число.число (число) число.число"
|
||
# Фитнес - это последнее число в строке
|
||
match = re.search(r"\)\s+(\d+\.?\d*)\s*$", 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)): # Пропускаем первую колонку (Pc)
|
||
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_fitness(data_rows: list[list[str]]) -> float | None:
|
||
"""
|
||
Находит минимальное значение фитнеса среди всех значений в таблице.
|
||
|
||
Args:
|
||
data_rows: Строки данных таблицы
|
||
|
||
Returns:
|
||
Минимальное значение фитнеса или None если нет валидных значений
|
||
"""
|
||
min_fitness = None
|
||
|
||
for row in data_rows:
|
||
for i in range(1, len(row)): # Пропускаем первую колонку (Pc)
|
||
fitness_value = extract_fitness_value(row[i])
|
||
if fitness_value is not None:
|
||
if min_fitness is None or fitness_value < min_fitness:
|
||
min_fitness = fitness_value
|
||
|
||
return min_fitness
|
||
|
||
|
||
def format_value(
|
||
value: str, best_time: float | None = None, best_fitness: float | None = None
|
||
) -> str:
|
||
"""
|
||
Форматирует значение для LaTeX таблицы, выделяя лучшие результаты жирным.
|
||
|
||
Args:
|
||
value: Строковое значение из CSV
|
||
best_time: Лучшее время в таблице для сравнения
|
||
best_fitness: Лучший фитнес в таблице для сравнения
|
||
|
||
Returns:
|
||
Отформатированное значение для LaTeX
|
||
"""
|
||
value = value.strip()
|
||
if value == "—" or value == "" or value == "–":
|
||
return "—"
|
||
|
||
# Проверяем есть ли фитнес в строке
|
||
fitness_match = re.search(r"(\d+\.?\d*)\s*\((\d+)\)\s+(\d+\.?\d*)\s*$", value)
|
||
|
||
if fitness_match:
|
||
# Есть фитнес: "время (поколения) фитнес"
|
||
time_str = fitness_match.group(1)
|
||
generations_str = fitness_match.group(2)
|
||
fitness_str = fitness_match.group(3)
|
||
|
||
current_time = float(time_str)
|
||
current_fitness = float(fitness_str)
|
||
|
||
# Проверяем, является ли время лучшим
|
||
time_part = f"{time_str} ({generations_str})"
|
||
if best_time is not None and abs(current_time - best_time) < 0.001:
|
||
if HIGHLIGHT_COLOR is not None:
|
||
time_part = (
|
||
f"\\textcolor{{{HIGHLIGHT_COLOR}}}{{\\textbf{{{time_part}}}}}"
|
||
)
|
||
else:
|
||
time_part = f"\\textbf{{{time_part}}}"
|
||
|
||
# Проверяем, является ли фитнес лучшим
|
||
fitness_part = fitness_str
|
||
if best_fitness is not None and abs(current_fitness - best_fitness) < 0.00001:
|
||
if HIGHLIGHT_COLOR is not None:
|
||
fitness_part = (
|
||
f"\\textcolor{{{HIGHLIGHT_COLOR}}}{{\\textbf{{{fitness_part}}}}}"
|
||
)
|
||
else:
|
||
fitness_part = f"\\textbf{{{fitness_part}}}"
|
||
|
||
return f"{time_part} {fitness_part}"
|
||
|
||
else:
|
||
# Нет фитнеса: только "время (поколения)"
|
||
time_match = re.match(r"(\d+\.?\d*)\s*\((\d+)\)", value)
|
||
if time_match:
|
||
current_time = float(time_match.group(1))
|
||
if best_time is not None and abs(current_time - best_time) < 0.001:
|
||
if HIGHLIGHT_COLOR is not None:
|
||
return f"\\textcolor{{{HIGHLIGHT_COLOR}}}{{\\textbf{{{value}}}}}"
|
||
else:
|
||
return f"\\textbf{{{value}}}"
|
||
|
||
return value
|
||
|
||
|
||
def generate_latex_table(n: str, header: str, data_rows: list[list[str]]) -> str:
|
||
"""
|
||
Генерирует LaTeX код таблицы.
|
||
|
||
Args:
|
||
n: Размер популяции
|
||
header: Заголовок таблицы
|
||
data_rows: Строки данных
|
||
|
||
Returns:
|
||
LaTeX код таблицы
|
||
"""
|
||
# Находим лучшее время и лучший фитнес в таблице
|
||
best_time = find_best_time(data_rows)
|
||
best_fitness = find_best_fitness(data_rows)
|
||
|
||
# Извлекаем заголовки колонок из header
|
||
header_parts = header.split(",")
|
||
pm_values = header_parts[1:] # Пропускаем "Pc \ Pm"
|
||
num_pm_columns = len(pm_values) # Динамически определяем количество колонок
|
||
|
||
latex_code = f""" \\begin{{table}}[h!]
|
||
\\centering
|
||
\\small
|
||
\\caption{{Результаты для $N = {n}$}}
|
||
\\begin{{tabularx}}{{\\linewidth}}{{l *{{{num_pm_columns}}}{{Y}}}}
|
||
\\toprule
|
||
$\\mathbf{{P_c \\;\\backslash\\; P_m}}$"""
|
||
|
||
# Добавляем заголовки Pm
|
||
for pm in pm_values:
|
||
latex_code += f" & \\textbf{{{pm.strip()}}}"
|
||
|
||
latex_code += " \\\\\n \\midrule\n"
|
||
|
||
# Добавляем строки данных
|
||
for row in data_rows:
|
||
pc_value = row[0].strip()
|
||
latex_code += f" \\textbf{{{pc_value}}}"
|
||
|
||
# Добавляем значения для каждого Pm
|
||
for i in range(1, min(num_pm_columns + 1, len(row))):
|
||
value = format_value(row[i], best_time, best_fitness)
|
||
latex_code += f" & {value}"
|
||
|
||
# Заполняем недостающие колонки если их меньше чем num_pm_columns
|
||
for i in range(len(row) - 1, num_pm_columns):
|
||
latex_code += " & —"
|
||
|
||
latex_code += " \\\\\n"
|
||
|
||
latex_code += f""" \\bottomrule
|
||
\\end{{tabularx}}
|
||
\\label{{tab:pc_pm_results_{n}}}
|
||
\\end{{table}}"""
|
||
|
||
return latex_code
|
||
|
||
|
||
def main():
|
||
"""Основная функция скрипта."""
|
||
experiments_path = Path("experiments")
|
||
|
||
if not experiments_path.exists():
|
||
print("Папка experiments не найдена!")
|
||
return
|
||
|
||
tables = []
|
||
|
||
# Сканируем все подпапки в experiments, сортируем по числовому значению N
|
||
subdirs = [
|
||
subdir
|
||
for subdir in experiments_path.iterdir()
|
||
if subdir.is_dir() and subdir.name.isdigit()
|
||
]
|
||
subdirs.sort(key=lambda x: int(x.name))
|
||
|
||
for subdir in subdirs:
|
||
n = subdir.name
|
||
csv_file = subdir / "results.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_fitness = find_best_fitness(data_rows)
|
||
latex_table = generate_latex_table(n, header, data_rows)
|
||
tables.append(latex_table)
|
||
print(
|
||
f"✓ Таблица для N={n} готова (лучшее время: {best_time}, лучший фитнес: {best_fitness})"
|
||
)
|
||
|
||
except Exception as e:
|
||
print(f"✗ Ошибка при обработке {csv_file}: {e}")
|
||
else:
|
||
print(f"✗ Файл {csv_file} не найден")
|
||
|
||
# Сохраняем все таблицы в файл
|
||
if tables:
|
||
with open("tables.tex", "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✓ Все таблицы сохранены в файл 'tables.tex'")
|
||
print(f"Сгенерировано таблиц: {len(tables)}")
|
||
else:
|
||
print("Не найдено данных для генерации таблиц!")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|