lab3
This commit is contained in:
339
lab3/csv_to_tex.py
Normal file
339
lab3/csv_to_tex.py
Normal file
@@ -0,0 +1,339 @@
|
||||
"""
|
||||
Скрипт для конвертации результатов экспериментов из 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) >= 6: # Pc + 5 значений 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, min(6, 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, min(6, 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"
|
||||
|
||||
latex_code = f""" \\begin{{table}}[h!]
|
||||
\\centering
|
||||
\\small
|
||||
\\caption{{Результаты для $N = {n}$}}
|
||||
\\begin{{tabularx}}{{\\linewidth}}{{l *{{5}}{{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(6, len(row))): # Максимум 5 колонок Pm
|
||||
value = format_value(row[i], best_time, best_fitness)
|
||||
latex_code += f" & {value}"
|
||||
|
||||
# Заполняем недостающие колонки если их меньше 5
|
||||
for i in range(len(row) - 1, 5):
|
||||
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()
|
||||
Reference in New Issue
Block a user