First Commit

This commit is contained in:
Igor20264
2025-09-15 07:48:37 +03:00
commit d2da20fdb2
12 changed files with 6102 additions and 0 deletions
+915
View File
@@ -0,0 +1,915 @@
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'")