Files
md_to_gost/main12.py
T
2025-09-15 07:48:37 +03:00

915 lines
39 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from abc import ABC, abstractmethod
from typing import Dict, List, Any, Optional, Union, Callable
from docx import Document
from docx.shared import Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
from docx.enum.style import WD_STYLE_TYPE
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
import re
class GostDocumentConfig:
"""Конфигурация параметров документа по ГОСТ"""
def __init__(self, **kwargs):
# Поля документа (в сантиметрах)
self.margins = kwargs.get('margins', {
'top': 2.0,
'bottom': 2.0,
'left': 3.0,
'right': 1.5
})
# Шрифты
self.font = kwargs.get('font', {
'name': 'Times New Roman',
'size': 14,
'main_language': 'ru-RU'
})
# Межстрочный интервал
self.line_spacing = kwargs.get('line_spacing', 1.5)
# Отступы для заголовков
self.heading_spacing = kwargs.get('heading_spacing', {
1: {'before': 24, 'after': 12},
2: {'before': 24, 'after': 6},
3: {'before': 18, 'after': 6}
})
# Стили заголовков
self.heading_styles = kwargs.get('heading_styles', {
1: {'size': 16, 'bold': True, 'all_caps': True, 'align': 'center'},
2: {'size': 14, 'bold': True, 'all_caps': False, 'align': 'left'},
3: {'size': 14, 'bold': True, 'italic': True, 'all_caps': False, 'align': 'left'}
})
# Стиль абзаца
self.paragraph = kwargs.get('paragraph', {
'first_line_indent': 1.25, # в см
'alignment': 'justify'
})
# Стиль таблиц
self.table = kwargs.get('table', {
'font_size': 12,
'header_bold': True
})
# Стиль подписей
self.caption = kwargs.get('caption', {
'font_size': 12,
'italic': True,
'align': 'center'
})
class GostDocumentCore(ABC):
"""Базовый класс для создания документов по ГОСТ с возможностью расширения"""
def __init__(self, config: Optional[GostDocumentConfig] = None,
title: str = "", author: str = ""):
"""
Инициализация документа с настройками ГОСТ
Args:
config: Конфигурация документа (если None - используются стандартные настройки ГОСТ)
title: Заголовок документа
author: Автор документа
"""
self.config = config or GostDocumentConfig()
self.title = title
self.author = author
self.document = Document()
self._element_processors = {}
self._setup_document()
self._create_gost_styles()
# Регистрируем базовые процессоры элементов
self.register_processor('heading', self._process_heading)
self.register_processor('paragraph', self._process_paragraph)
self.register_processor('table', self._process_table)
self.register_processor('list', self._process_list)
self.register_processor('image', self._process_image)
def _setup_document(self):
"""Настройка базовых параметров документа согласно ГОСТ"""
# Настройка полей страницы
sections = self.document.sections
for section in sections:
section.top_margin = Cm(self.config.margins['top'])
section.bottom_margin = Cm(self.config.margins['bottom'])
section.left_margin = Cm(self.config.margins['left'])
section.right_margin = Cm(self.config.margins['right'])
# Установка стандартного стиля для всего документа
style = self.document.styles['Normal']
font = style.font
font.name = self.config.font['name']
font.size = Pt(self.config.font['size'])
# Установка кириллического шрифта
font.element.rPr.rFonts.set(qn('w:eastAsia'), self.config.font['name'])
# Настройка формата абзаца
paragraph_format = style.paragraph_format
paragraph_format.line_spacing_rule = (
WD_LINE_SPACING.ONE_POINT_FIVE if self.config.line_spacing == 1.5
else WD_LINE_SPACING.DOUBLE if self.config.line_spacing == 2.0
else WD_LINE_SPACING.EXACTLY
)
paragraph_format.alignment = (
WD_ALIGN_PARAGRAPH.JUSTIFY if self.config.paragraph['alignment'] == 'justify'
else WD_ALIGN_PARAGRAPH.LEFT if self.config.paragraph['alignment'] == 'left'
else WD_ALIGN_PARAGRAPH.CENTER
)
paragraph_format.first_line_indent = Cm(self.config.paragraph['first_line_indent'])
def _create_gost_styles(self):
"""Создание стилей заголовков и других элементов в соответствии с ГОСТ"""
# Заголовок 1 уровня
self._create_heading_style(1)
# Заголовок 2 уровня
self._create_heading_style(2)
# Заголовок 3 уровня
self._create_heading_style(3)
# Стиль для списка
if 'List GOST' not in self.document.styles:
list_style = self.document.styles.add_style('List GOST', WD_STYLE_TYPE.PARAGRAPH)
list_style.base_style = self.document.styles['List']
list_style.font.name = self.config.font['name']
list_style.font.size = Pt(self.config.font['size'])
list_style.paragraph_format.left_indent = Cm(1.25)
list_style.paragraph_format.space_before = Pt(0)
list_style.paragraph_format.space_after = Pt(6)
list_style.paragraph_format.line_spacing_rule = (
WD_LINE_SPACING.ONE_POINT_FIVE if self.config.line_spacing == 1.5
else WD_LINE_SPACING.DOUBLE
)
# Стиль для подписей таблиц и рисунков
if 'Caption GOST' not in self.document.styles:
caption_style = self.document.styles.add_style('Caption GOST', WD_STYLE_TYPE.PARAGRAPH)
caption_style.base_style = self.document.styles['Caption']
caption_style.font.name = self.config.font['name']
caption_style.font.size = Pt(self.config.caption['font_size'])
caption_style.font.italic = self.config.caption['italic']
caption_style.paragraph_format.alignment = (
WD_ALIGN_PARAGRAPH.CENTER if self.config.caption['align'] == 'center'
else WD_ALIGN_PARAGRAPH.LEFT
)
caption_style.paragraph_format.space_before = Pt(6)
caption_style.paragraph_format.space_after = Pt(12)
def _create_heading_style(self, level: int):
"""Создает стиль заголовка заданного уровня"""
style_name = f'Heading {level} GOST'
if style_name in self.document.styles:
return
style = self.document.styles.add_style(style_name, WD_STYLE_TYPE.PARAGRAPH)
# Настройка шрифта
heading_config = self.config.heading_styles[level]
style.font.name = self.config.font['name']
style.font.size = Pt(heading_config['size'])
style.font.bold = heading_config['bold']
style.font.italic = heading_config.get('italic', False)
style.font.all_caps = heading_config['all_caps']
# Настройка абзаца
spacing = self.config.heading_spacing[level]
style.paragraph_format.space_before = Pt(spacing['before'])
style.paragraph_format.space_after = Pt(spacing['after'])
style.paragraph_format.line_spacing_rule = (
WD_LINE_SPACING.ONE_POINT_FIVE if self.config.line_spacing == 1.5
else WD_LINE_SPACING.DOUBLE
)
# Выравнивание
if heading_config['align'] == 'center':
style.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
elif heading_config['align'] == 'left':
style.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.LEFT
style.paragraph_format.first_line_indent = Cm(0) # Без красной строки для заголовков
def register_processor(self, element_type: str, processor: Callable):
"""
Регистрирует обработчик для определенного типа элемента
Args:
element_type: Тип элемента (например, 'heading', 'table')
processor: Функция-обработчик
"""
self._element_processors[element_type] = processor
def get_processor(self, element_type: str) -> Optional[Callable]:
"""
Возвращает обработчик для указанного типа элемента
Args:
element_type: Тип элемента
Returns:
Обработчик или None, если не найден
"""
return self._element_processors.get(element_type)
def _process_heading(self, element: Dict[str, Any]) -> None:
"""Обрабатывает элемент заголовка"""
level = element.get('level', 1)
text = element.get('text', '')
heading = self.document.add_heading(level=0)
style_name = f'Heading {level} GOST'
if style_name in self.document.styles:
heading.style = style_name
else:
# Базовая настройка, если стиль не создан
heading.alignment = WD_ALIGN_PARAGRAPH.CENTER if level == 1 else WD_ALIGN_PARAGRAPH.LEFT
run = heading.add_run(text)
run.font.size = Pt(16 if level == 1 else 14)
run.font.bold = True
# Добавляем текст
heading.add_run(text)
def _process_paragraph(self, element: Dict[str, Any]) -> None:
"""Обрабатывает элемент абзаца"""
text = element.get('text', '')
style = element.get('style')
p = self.document.add_paragraph()
if style and style in self.document.styles:
p.style = style
# Основной текст по ГОСТ
p.paragraph_format.line_spacing_rule = (
WD_LINE_SPACING.ONE_POINT_FIVE if self.config.line_spacing == 1.5
else WD_LINE_SPACING.DOUBLE
)
p.paragraph_format.alignment = (
WD_ALIGN_PARAGRAPH.JUSTIFY if self.config.paragraph['alignment'] == 'justify'
else WD_ALIGN_PARAGRAPH.LEFT
)
p.paragraph_format.first_line_indent = Cm(self.config.paragraph['first_line_indent'])
print(text)
# Добавляем текст
p.add_run(text)
def _process_table(self, element: Dict[str, Any]) -> None:
"""Обрабатывает элемент таблицы"""
data = element.get('data', [])
headers = element.get('headers')
# Добавляем подпись таблицы
caption = self.document.add_paragraph()
caption.style = 'Caption GOST'
caption.add_run("Таблица ").bold = True
# Создаем таблицу
rows = len(data) + (1 if headers else 0)
cols = len(data[0]) if data else 0
if cols == 0:
return
table = self.document.add_table(rows=rows, cols=cols)
table.style = 'Table Grid'
# Заполняем заголовки, если они есть
if headers:
hdr_cells = table.rows[0].cells
for i, header in enumerate(headers):
hdr_cells[i].text = header
for paragraph in hdr_cells[i].paragraphs:
for run in paragraph.runs:
run.font.bold = self.config.table['header_bold']
# Заполняем данные
start_row = 1 if headers else 0
for i, row_data in enumerate(data):
row_cells = table.rows[i + start_row].cells
for j, cell_data in enumerate(row_data):
row_cells[j].text = str(cell_data)
# Настройка шрифта для всех ячеек
for row in table.rows:
for cell in row.cells:
for paragraph in cell.paragraphs:
for run in paragraph.runs:
run.font.name = self.config.font['name']
run.font.size = Pt(self.config.table['font_size'])
def _process_list(self, element: Dict[str, Any]) -> None:
"""Обрабатывает элемент списка"""
items = element.get('items', [])
list_type = element.get('type', 'unordered') # 'unordered' или 'ordered'
for item in items:
p = self.document.add_paragraph(style='List GOST')
p.paragraph_format.left_indent = Cm(1.25)
p.paragraph_format.first_line_indent = Cm(-0.63) if list_type == 'ordered' else Cm(0)
if list_type == 'ordered':
# Добавляем номер вручную для упорядоченного списка
p.add_run(f"{items.index(item) + 1}. ").bold = True
p.add_run(item)
def _process_image(self, element: Dict[str, Any]) -> None:
"""Обрабатывает элемент изображения (заглушка для будущей реализации)"""
# В реальной реализации здесь будет добавление изображения
path = element.get('path', '')
width = element.get('width', None)
# Добавляем подпись
caption = self.document.add_paragraph()
caption.style = 'Caption GOST'
caption.add_run("Рисунок ").bold = True
# В реальной реализации здесь будет добавление изображения
# if path and os.path.exists(path):
# if width:
# self.document.add_picture(path, width=Cm(width))
# else:
# self.document.add_picture(path)
@abstractmethod
def add_element(self, element_type: str, **kwargs) -> None:
"""
Абстрактный метод для добавления элемента в документ.
Должен быть реализован в дочерних классах.
Args:
element_type: Тип элемента (heading, paragraph, table и т.д.)
**kwargs: Параметры элемента
"""
pass
def save(self, filename: str) -> None:
"""
Сохраняет документ в файл
Args:
filename: Имя файла для сохранения
"""
# Добавляем настройки совместимости
try:
self.document.settings.element.xpath('./w:compat')[0].clear()
except (IndexError, AttributeError):
pass
self.document.save(filename)
def create_title_page(self) -> None:
"""Создает титульную страницу документа"""
# Добавляем отступ сверху
for _ in range(5):
self.document.add_paragraph()
# Заголовок
title_paragraph = self.document.add_heading(level=0)
title_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
title_run = title_paragraph.add_run(self.title)
title_run.font.size = Pt(16)
title_run.font.bold = True
title_run.font.name = self.config.font['name']
# Добавляем отступ
self.document.add_paragraph()
# Автор
author_paragraph = self.document.add_paragraph()
author_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
author_run = author_paragraph.add_run(self.author)
author_run.font.size = Pt(self.config.font['size'])
author_run.font.name = self.config.font['name']
# Добавляем отступ снизу
for _ in range(10):
self.document.add_paragraph()
# Дата
date_paragraph = self.document.add_paragraph()
date_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
date_run = date_paragraph.add_run("2023")
date_run.font.size = Pt(self.config.font['size'])
date_run.font.name = self.config.font['name']
# Устанавливаем титульную страницу как отдельный раздел
self.document.sections[0].start_type = 2 # Новая страница
class MarkdownGostDocument(GostDocumentCore):
"""
Расширенная реализация для обработки Markdown и преобразования в документ по ГОСТ
"""
def __init__(self, config: Optional[GostDocumentConfig] = None,
title: str = "", author: str = ""):
super().__init__(config, title, author)
self._markdown_processors = {}
self._setup_markdown_processors()
def _setup_markdown_processors(self):
"""Настраивает обработчики для различных элементов Markdown"""
# Заголовки
self.register_markdown_processor(r'#{1,6}\s+(.+)', self._process_md_heading)
# Обычный текст
self.register_markdown_processor(r'^(?!#{1,6}\s+)(?!-{3,})(?!#{1,6}$)(?!```)(?!!\[.*\]\(.*\)).+',
self._process_md_paragraph)
# Горизонтальная линия
self.register_markdown_processor(r'^-{3,}$', self._process_md_horizontal_rule)
# Списки
self.register_markdown_processor(r'^(\*|\-|\d+\.)\s+(.+)$', self._process_md_list)
# Таблицы (упрощенная обработка)
self.register_markdown_processor(r'^\|.*\|$', self._process_md_table)
# Изображения
self.register_markdown_processor(r'^!\[(.*)\]\((.*)\)$', self._process_md_image)
# Код
self.register_markdown_processor(r'^```(.*)$', self._process_md_code)
def register_markdown_processor(self, pattern: str, processor: Callable):
"""
Регистрирует обработчик для определенного паттерна Markdown
Args:
pattern: Регулярное выражение для паттерна
processor: Функция-обработчик
"""
self._markdown_processors[pattern] = processor
def _process_md_heading(self, match: re.Match, line: str) -> Dict[str, Any]:
"""Обрабатывает заголовок Markdown"""
heading_text = match.group(1).strip()
level = line.count('#')
# Ограничиваем уровень заголовка от 1 до 3 (согласно ГОСТ)
level = min(max(level, 1), 3)
return {'type': 'heading', 'level': level, 'text': heading_text}
def _process_md_paragraph(self, match: re.Match, line: str) -> Dict[str, Any]:
"""Обрабатывает обычный текст Markdown"""
return {'type': 'paragraph', 'text': line.strip()}
def _process_md_horizontal_rule(self, match: re.Match, line: str) -> Dict[str, Any]:
"""Обрабатывает горизонтальную линию Markdown"""
# В ГОСТ горизонтальные линии не используются, поэтому просто пропускаем
return None
def _process_md_list(self, match: re.Match, line: str) -> Dict[str, Any]:
"""Обрабатывает список Markdown"""
items = [line.strip()]
list_type = 'ordered' if re.match(r'^\d+\.', line) else 'unordered'
return {'type': 'list', 'items': items, 'type': list_type}
def _process_md_table(self, match: re.Match, line: str) -> Dict[str, Any]:
"""Обрабатывает таблицу Markdown (заглушка)"""
# Полная обработка таблиц требует анализа нескольких строк
return None # Будет обработано в полной реализации
def _process_md_image(self, match: re.Match, line: str) -> Dict[str, Any]:
"""Обрабатывает изображение Markdown"""
alt_text = match.group(1)
image_path = match.group(2)
return {'type': 'image', 'path': image_path, 'alt': alt_text}
def _process_md_code(self, match: re.Match, line: str) -> Dict[str, Any]:
"""Обрабатывает блок кода Markdown"""
# Код в ГОСТ обычно оформляется как отдельный раздел или сноска
return {'type': 'paragraph', 'text': f"Код: {line}", 'style': 'Code'}
def add_element(self, element_type: str, **kwargs) -> None:
"""
Добавляет элемент в документ согласно ГОСТ
Args:
element_type: Тип элемента (heading, paragraph, table и т.д.)
**kwargs: Параметры элемента
"""
processor = self.get_processor(element_type)
if processor:
element = {'type': element_type, **kwargs}
processor(element)
def process_markdown(self, markdown_text: str) -> None:
"""
Обрабатывает Markdown текст и добавляет элементы в документ
Args:
markdown_text: Текст в формате Markdown
"""
lines = markdown_text.split('\n')
i = 0
while i < len(lines):
line = lines[i].strip()
if not line:
i += 1
continue
processed = False
for pattern, processor in self._markdown_processors.items():
match = re.match(pattern, line)
if match:
element = processor(match, line)
if element:
self.add_element(element['type'], **{k: v for k, v in element.items() if k != 'type'})
processed = True
break
if not processed:
# Если строка не соответствует ни одному паттерну, обрабатываем как обычный текст
self.add_element('paragraph', text=line)
i += 1
def process_markdown_file(self, file_path: str) -> None:
"""
Обрабатывает файл Markdown и добавляет элементы в документ
Args:
file_path: Путь к файлу Markdown
"""
with open(file_path, 'r', encoding='utf-8') as f:
markdown_text = f.read()
self.process_markdown(markdown_text)
# Пример использования
if __name__ == "__main__":
# Создаем кастомную конфигурацию (можно оставить стандартную)
custom_config = GostDocumentConfig(
margins={
'top': 2.0,
'bottom': 2.0,
'left': 3.0,
'right': 1.5
},
font={
'name': 'Times New Roman',
'size': 14
},
line_spacing=1.5,
heading_spacing={
1: {'before': 24, 'after': 12},
2: {'before': 24, 'after': 6},
3: {'before': 18, 'after': 6}
},
heading_styles={
1: {'size': 16, 'bold': True, 'all_caps': True, 'align': 'center'},
2: {'size': 14, 'bold': True, 'all_caps': False, 'align': 'left'},
3: {'size': 14, 'bold': True, 'italic': True, 'all_caps': False, 'align': 'left'}
}
)
# Создаем документ с поддержкой Markdown
doc = MarkdownGostDocument(
config=custom_config,
title="Отчет о научно-исследовательской работе",
author="Иванов И.И., группа 123"
)
# Добавляем титульную страницу
doc.create_title_page()
# Пример обработки Markdown
markdown_example = """
## Практическая работа 6 (часть 2)
**Тема. Нелинейные структуры данных. Бинарное дерево.**
---
### 1. Условие задания и варианта
**Вариант 27**
**Значение информационной части:** Содержит символьное значение и счетчик (подсчитывает, сколько раз в дерево вставлялся символ).
**Операции варианта:**
1. Модифицировать алгоритм вставки ключа в дерево в соответствии со структурой узла.
2. Определить символ, который встретился большее количество раз.
3. Определить количество цифр в дереве, используя алгоритм обхода в ширину.
---
### 2. Свойства дерева варианта
- **Тип дерева:** Бинарное дерево поиска (БДП) без операции удаления.
- **Структура узла:**
```cpp
struct Node {
char symbol; // Символ
int count; // Счетчик вхождений
Node* left; // Указатель на левое поддерево
Node* right; // Указатель на правое поддерево
};
```
- **Особенности:**
- При вставке символа:
- Если символ уже существует в дереве, увеличивается его `count`.
- Иначе создается новый узел с `count = 1`.
- Упорядочение узлов: По ASCII-коду символов (меньшие символы — слева, большие — справа).
- Операция удаления не реализована (требования варианта 24–30).
---
### 3. Тестовые примеры
#### Пример 1: Вставка символов `a`, `a`, `b`, `3`, `3`, `3`
**Структура дерева:**
```
b(1)
a(2)
3(3)
```
**Пояснение:**
- `a` вставлен 2 раза → `count = 2`.
- `3` вставлен 3 раза → `count = 3` (самый частый символ).
- Количество цифр: 3 (только символ `3`).
#### Пример 2: Вставка символов `5`, `5`, `a`, `5`
**Структура дерева:**
```
a(1)
5(3)
```
**Пояснение:**
- `5` вставлен 3 раза → `count = 3` (самый частый символ).
- Количество цифр: 3.
---
### 4. Описание функций
#### 4.1. Модифицированная вставка символа
**Задача:** Добавить символ в БДП, увеличивая счетчик при совпадении.
**Предусловие:** Дерево инициализировано.
**Постусловие:** Символ добавлен или обновлен в дереве.
**Прототип:**
```cpp
void BinaryTree::insert(char symbol);
```
**Алгоритм:**
1. Начать с корня.
2. Пока текущий узел не `nullptr`:
- Если символ совпадает с текущим узлом → увеличить `count` и завершить.
- Если символ меньше текущего → перейти в левое поддерево.
- Иначе → перейти в правое поддерево.
3. Если узел не найден → создать новый узел с `count = 1`.
---
#### 4.2. Поиск самого частого символа
**Задача:** Найти символ с максимальным значением `count`.
**Предусловие:** Дерево не пустое.
**Постусловие:** Возвращен символ и его счетчик.
**Прототип:**
```cpp
std::pair<char, int> BinaryTree::findMostFrequent();
```
**Алгоритм:**
1. Инициализировать `maxCount = 0`, `maxSymbol = '\\0'`.
2. Обойти все узлы (инордер-обход):
- Для каждого узла сравнить `count` с `maxCount`.
- Если `count > maxCount` → обновить `maxSymbol` и `maxCount`.
3. Вернуть пару `(maxSymbol, maxCount)`.
---
#### 4.3. Подсчет цифр в дереве (обход в ширину)
**Задача:** Посчитать общее количество вхождений цифровых символов (`0`–`9`).
**Предусловие:** Дерево инициализировано.
**Постусловие:** Возвращено число — сумма `count` для всех цифровых символов.
**Прототип:**
```cpp
int BinaryTree::countDigits();
```
**Алгоритм:**
1. Инициализировать очередь и счетчик `total = 0`.
2. Поместить корень в очередь.
3. Пока очередь не пуста:
- Извлечь узел из очереди.
- Если символ узла — цифра (`'0'``'9'`) → добавить `count` к `total`.
- Поместить левый и правый дочерние узлы в очередь.
4. Вернуть `total`.
---
#### 4.4. Отображение дерева
**Задача:** Вывести структуру дерева на экран (повернутое на 90°).
**Предусловие:** Дерево инициализировано.
**Постусловие:** Дерево отображено в консоли.
**Прототип:**
```cpp
void BinaryTree::displayTree();
void BinaryTree::printTree(Node* node, int space);
```
**Алгоритм:**
1. Рекурсивно обойти дерево в порядке: **правое поддерево → корень → левое поддерево**.
2. Для каждого уровня увеличивать отступ (на 10 пробелов).
3. Вывести символ и `count` с текущим отступом.
---
### 5. Код программы
```cpp
#include <iostream>
#include <stack>
#include <queue>
using namespace std;
struct Node {
char symbol;
int count;
Node* left;
Node* right;
Node(char s) : symbol(s), count(1), left(nullptr), right(nullptr) {}
};
class BinaryTree {
private:
Node* root;
void printTree(Node* node, int space) {
if (node == nullptr) return;
space += 10;
printTree(node->right, space);
cout << endl;
for (int i = 10; i < space; i++) cout << " ";
cout << node->symbol << "(" << node->count << ")";
printTree(node->left, space);
}
public:
BinaryTree() : root(nullptr) {}
void insert(char symbol) {
Node** current = &root;
while (*current != nullptr) {
if (symbol == (*current)->symbol) {
(*current)->count++;
return;
} else if (symbol < (*current)->symbol) {
current = &((*current)->left);
} else {
current = &((*current)->right);
}
}
*current = new Node(symbol);
}
pair<char, int> findMostFrequent() {
if (root == nullptr) return make_pair('\\0', 0);
char maxSymbol = '\\0';
int maxCount = 0;
stack<Node*> s;
Node* current = root;
while (current != nullptr || !s.empty()) {
while (current != nullptr) {
s.push(current);
current = current->left;
}
current = s.top();
s.pop();
if (current->count > maxCount) {
maxCount = current->count;
maxSymbol = current->symbol;
}
current = current->right;
}
return make_pair(maxSymbol, maxCount);
}
int countDigits() {
if (root == nullptr) return 0;
int total = 0;
queue<Node*> q;
q.push(root);
while (!q.empty()) {
Node* current = q.front();
q.pop();
if (current->symbol >= '0' && current->symbol <= '9') {
total += current->count;
}
if (current->left) q.push(current->left);
if (current->right) q.push(current->right);
}
return total;
}
void displayTree() {
printTree(root, 0);
cout << "\n\n";
}
};
int main() {
BinaryTree tree;
int choice;
char symbol;
while (true) {
cout << "\n1. Insert symbol\n";
cout << "2. Display tree\n";
cout << "3. Find most frequent symbol\n";
cout << "4. Count digits\n";
cout << "5. Exit\n";
cout << "Enter choice: ";
cin >> choice;
switch (choice) {
case 1:
cout << "Enter symbol: ";
cin >> symbol;
tree.insert(symbol);
break;
case 2:
cout << "Tree structure:\n";
tree.displayTree();
break;
case 3: {
auto result = tree.findMostFrequent();
if (result.second == 0) {
cout << "Tree is empty.\n";
} else {
cout << "Most frequent symbol: " << result.first
<< " (count: " << result.second << ")\n";
}
break;
}
case 4:
cout << "Number of digits: " << tree.countDigits() << endl;
break;
case 5:
return 0;
default:
cout << "Invalid choice.\n";
}
}
}
```
---
### 6. Скриншоты тестирования
#### Тест 1: Вставка `a`, `a`, `b`, `3`, `3`, `3`
![Тест 1](https://i.imgur.com/1aXJz9l.png)
- **Вывод дерева:** Корректно отображена структура с `3(3)` как самым частым символом.
- **Количество цифр:** 3 (символ `3` вставлен 3 раза).
#### Тест 2: Вставка `5`, `5`, `a`, `5`
![Тест 2](https://i.imgur.com/5GkR7yP.png)
- **Самый частый символ:** `5` (count=3).
- **Количество цифр:** 3.
---
### 7. Ответы на контрольные вопросы
1. **Степень дерева** определяется максимальным количеством дочерних узлов у любого узла.
2. **Сильноветвящееся дерево** — дерево, где степень каждого узла равна максимальной степени дерева.
3. **Путь в дереве** — последовательность узлов от корня к целевому узлу.
4. **Длина пути** — количество ребер в пути (на 1 меньше количества узлов).
5. **Степень бинарного дерева** равна 2 (максимум 2 дочерних узла на узел).
6. **Пустое дерево** допустимо (корень = `nullptr`).
7. **Бинарное дерево** — дерево, где каждый узел имеет не более 2 дочерних узлов.
8. **Алгоритм обхода** используется для последовательного доступа ко всем узлам.
9. **Высота дерева:**
```
height(nullptr) = -1
height(node) = 1 + max(height(node->left), height(node->right))
```
10. **Дерево по таблице:**
![Дерево](https://i.imgur.com/mVcRf0D.png)
11. **Порядки обхода:**
- Прямой (КЛП): 18 → 12 → 7 → 4 → 10 → 2 → 5 → 21 → 15 → 14
- Симметричный (ЛКП): 7 → 12 → 2 → 10 → 5 → 18 → 21 → 4 → 14 → 15
- Обратный (ЛПК): 7 → 2 → 5 → 10 → 12 → 21 → 4 → 15 → 14 → 18
12. **Обход в ширину** использует **очередь**.
13. **Пример обхода в ширину:** 18 → 12 → 14 → 7 → 10 → 15 → 4 → 2 → 5 → 21.
14. **Не рекурсивный обход в глубину** использует **стек**.
15. **Обход дерева выражения `(5+3)*((1+2)/3)`:**
- Прямой: `* + 5 3 / + 1 2 3`
- Симметричный: `5 + 3 * 1 + 2 / 3`
- Обратный: `5 3 + 1 2 + 3 / *`
16. **Деревья выражений:**
- `a+b-c*d+e` → `+ - + a b * c d e`
- `/a-b*c d` → Ошибка в выражении (некорректный формат).
- `a b c d / - *` → `* a - b / c d`
- `* - / + a b c d e` → `* - / + a b c d e`
17. **Обход в ширину со стеком** даст порядок, аналогичный **обратному обходу в глубину**.
18. **БДП для симметричного обхода `40 45 46 50 65 70 75`:**
![БДП](https://i.imgur.com/3kLmF9A.png)
19. **Исходное дерево для прямого обхода `50 45 35 15 40 46 65 75 70`:**
![Дерево](https://i.imgur.com/8W0jKQv.png)
20. **БДП после вставки `1, 48, 75, 100`:**
![Дерево](https://i.imgur.com/5GkR7yP.png)
---
**Вывод:** Реализованы все операции для бинарного дерева поиска с модифицированной структурой узла. Программа корректно обрабатывает вставку, поиск самого частого символа и подсчет цифр через обход в ширину. Тесты подтверждают работоспособность алгоритмов..
"""
# Обрабатываем Markdown
doc.process_markdown(markdown_example)
# Сохраняем документ
doc.save("./asdotchet_po_GOST.docx")
print("Документ успешно создан и сохранен как 'otchet_po_GOST.docx'")