diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..b58b603
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..590a59e
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..4e61346
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/praktika.iml b/.idea/praktika.iml
new file mode 100644
index 0000000..c03f621
--- /dev/null
+++ b/.idea/praktika.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/data/comments.txt b/data/comments.txt
new file mode 100644
index 0000000..82cd6f5
--- /dev/null
+++ b/data/comments.txt
@@ -0,0 +1,10 @@
+alice|Это плохое кино, ужас просто! Актеры играют отвратительно
+bob|Отлично! Фильм супер, мне очень понравилось. Напишите мне на test@example.com
+charlie|Нормально, но могло быть и лучше. Спецэффекты слабоваты
+alice|Фигня полная, bad фильм. Зря потратил время и деньги
+bob|Хорошо, спасибо за рекомендацию. Буду ждать продолжение
+david|Коротко и неинформативно
+alice|Отстой! Ужасная игра актеров и сценарий ни о чем
+bob|Прекрасный фильм, спасибо огромное! Обратная связь: feedback@site.ru
+eve|Неплохо, но есть к чему стремиться. Сценарий слабоват
+alice|Это плохое кино, ужас просто!
\ No newline at end of file
diff --git a/data/filtered_comments.txt b/data/filtered_comments.txt
new file mode 100644
index 0000000..278c15a
--- /dev/null
+++ b/data/filtered_comments.txt
@@ -0,0 +1,10 @@
+это *** кино *** просто! актеры играют отвратительно
+отлично! фильм супер мне очень понравилось. напишите мне на test@example.com
+нормально но могло быть и лучше. спецэффекты слабоваты
+*** полная *** фильм. зря потратил время и деньги
+хорошо спасибо за рекомендацию. буду ждать продолжение
+коротко и неинформативно
+отстой! ***ная игра актеров и сценарий ни о чем
+прекрасный фильм спасибо огромное! обратная связь feedback@site.ru
+неплохо но есть к чему стремиться. сценарий слабоват
+это *** кино *** просто!
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/comment_processor.py b/src/comment_processor.py
new file mode 100644
index 0000000..ad13d7c
--- /dev/null
+++ b/src/comment_processor.py
@@ -0,0 +1,169 @@
+import re
+from collections import defaultdict
+
+
+def clean_text(raw: str) -> str:
+ """
+ Удаляет лишние пробелы, приводит к нижнему регистру,
+ удаляет знаки пунктуации (кроме ., !), сохраняет email
+ """
+ if not isinstance(raw, str):
+ raw = str(raw)
+
+ text = raw.lower()
+
+ # Временно заменяем email-адреса
+ email_pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
+ emails = re.findall(email_pattern, text)
+ for i, email in enumerate(emails):
+ text = text.replace(email, f'__EMAIL_{i}__')
+
+ # Удаляем пунктуацию, но сохраняем . и !
+ text = re.sub(r'[^\w\s.!]', '', text)
+
+ # Восстанавливаем email-адреса
+ for i, email in enumerate(emails):
+ text = text.replace(f'__EMAIL_{i}__', email)
+
+ # Нормализуем пробелы (один пробел между словами)
+ text = ' '.join(text.split())
+
+ return text
+
+
+def extract_emails(text: str) -> list:
+ """
+ Возвращает список всех email-адресов в тексте.
+ """
+ if not isinstance(text, str):
+ return []
+ pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
+ return re.findall(pattern, text)
+
+
+# ----- 3. Маскировка нецензурной лексики -----
+def mask_profanity(text: str, bad_words: list) -> str:
+ """
+ Заменяет все вхождения слов из bad_words на ***
+ """
+ if not isinstance(text, str):
+ text = str(text)
+ result = text
+ for word in bad_words:
+ # Регистронезависимая замена
+ pattern = re.compile(re.escape(word), re.IGNORECASE)
+ result = pattern.sub('***', result)
+ return result
+
+
+# ----- 4. Вычисление тональности -----
+def calculate_sentiment_score(text: str, positive_words: set, negative_words: set) -> int:
+ """
+ Возвращает 1, если больше позитивных слов,
+ -1 если больше негативных, 0 если поровну или слов нет
+ """
+ if not isinstance(text, str):
+ text = str(text)
+
+ words = text.lower().split()
+ pos_count = sum(1 for w in words if w in positive_words)
+ neg_count = sum(1 for w in words if w in negative_words)
+
+ if pos_count > neg_count:
+ return 1
+ elif neg_count > pos_count:
+ return -1
+ else:
+ return 0
+
+
+# ----- 5. Фильтрация по длине -----
+def filter_by_length(comments: list, min_len: int, max_len: int) -> list:
+ """
+ Возвращает список комментариев, длина которых входит в диапазон [min_len, max_len]
+ """
+ if not isinstance(comments, list):
+ return []
+ return [c for c in comments if min_len <= len(str(c)) <= max_len]
+
+
+# ----- 6. Тегирование пользователя по активности -----
+def tag_user_by_activity(comments: list, user_name: str) -> str:
+ """
+ Принимает список комментариев-словарей и имя пользователя.
+ Возвращает 'high' (>5), 'medium' (2-5), 'low' (0-1)
+ """
+ if not isinstance(comments, list):
+ return 'low'
+
+ count = sum(1 for c in comments if isinstance(c, dict) and c.get('user') == user_name)
+
+ if count > 5:
+ return 'high'
+ elif count >= 2:
+ return 'medium'
+ else:
+ return 'low'
+
+
+# ----- 7. Агрегация по пользователям -----
+def aggregate_by_user(comments: list) -> dict:
+ """
+ На входе список словарей {user: str, text: str}
+ Возвращает {user: [list_of_comments]}
+ """
+ if not isinstance(comments, list):
+ return {}
+
+ result = defaultdict(list)
+ for c in comments:
+ if isinstance(c, dict) and 'user' in c and 'text' in c:
+ result[c['user']].append(c['text'])
+ return dict(result)
+
+
+# ----- 8. Поиск дубликатов -----
+def find_duplicates(comments: list) -> list:
+ """
+ Возвращает список индексов элементов, встречающихся более одного раза
+ (первое вхождение не считается дубликатом)
+ """
+ if not isinstance(comments, list):
+ return []
+
+ seen = {}
+ duplicate_indices = []
+
+ for idx, comment in enumerate(comments):
+ if comment in seen:
+ duplicate_indices.append(idx)
+ else:
+ seen[comment] = idx
+
+ return duplicate_indices
+
+
+# ----- 9. Генерация отчёта по комментарию -----
+def generate_comment_report(cleaned_text: str, sentiment: int, has_email: bool) -> dict:
+ """
+ Возвращает словарь с метаданными комментария
+ """
+ return {
+ "text": cleaned_text,
+ "sentiment": sentiment,
+ "contains_email": has_email,
+ "length": len(cleaned_text)
+ }
+
+
+# ----- 10. Сохранение отфильтрованных комментариев -----
+def save_filtered_comments(comments: list, file_path: str) -> None:
+ """
+ Сохраняет список очищенных комментариев в файл (каждый с новой строки)
+ """
+ try:
+ with open(file_path, 'w', encoding='utf-8') as f:
+ for comment in comments:
+ f.write(str(comment) + '\n')
+ except Exception as e:
+ print(f"Ошибка при сохранении файла {file_path}: {e}")
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
new file mode 100644
index 0000000..7763e74
--- /dev/null
+++ b/src/main.py
@@ -0,0 +1,152 @@
+import os
+import sys
+
+# Добавляем путь к src для импорта модуля
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+
+from comment_processor import (
+ clean_text,
+ extract_emails,
+ mask_profanity,
+ calculate_sentiment_score,
+ filter_by_length,
+ tag_user_by_activity,
+ aggregate_by_user,
+ find_duplicates,
+ generate_comment_report,
+ save_filtered_comments
+)
+
+# ----- Захардкоженные данные для модерации -----
+BAD_WORDS = ["плохое", "ругательство", "bad", "ужас", "фигня"]
+
+POSITIVE_WORDS = {"хорошо", "отлично", "супер", "класс", "прекрасно", "нравится", "спасибо"}
+NEGATIVE_WORDS = {"плохо", "ужасно", "отстой", "не нравится", "кошмар", "ужас"}
+
+
+def load_raw_comments(file_path: str) -> list:
+ """
+ Загружает сырые данные из файла формата: user|текст комментария
+ Возвращает список словарей [{"user": ..., "text": ...}]
+ """
+ comments = []
+ try:
+ with open(file_path, 'r', encoding='utf-8') as f:
+ for line_num, line in enumerate(f, 1):
+ line = line.strip()
+ if not line:
+ continue
+
+ # Разделяем по первому символу '|'
+ if '|' not in line:
+ print(f"Предупреждение: строка {line_num} не содержит '|', пропускаем: {line}")
+ continue
+
+ user, text = line.split('|', 1)
+ comments.append({"user": user.strip(), "text": text.strip()})
+ except FileNotFoundError:
+ print(f"Файл {file_path} не найден. Работаем с пустым списком комментариев.")
+ except Exception as e:
+ print(f"Ошибка при чтении файла {file_path}: {e}")
+
+ return comments
+
+
+def main():
+ print("=== Система анализа и очистки пользовательских комментариев ===\n")
+
+ # ----- Шаг 1: Загрузка данных -----
+ import os
+ input_file = os.path.join(os.path.dirname(__file__), "..", "data", "comments.txt")
+ raw_comments = load_raw_comments(input_file)
+ print(f"Загружено комментариев: {len(raw_comments)}")
+
+ if not raw_comments:
+ print("Нет данных для обработки. Завершение работы.")
+ return
+
+ # ----- Шаг 2-4: Обработка каждого комментария -----
+ processed_comments = [] # Для хранения очищенных текстов
+ comment_reports = [] # Для итоговых отчётов
+ emails_found_count = 0
+ sentiment_stats = {1: 0, -1: 0, 0: 0} # positive, negative, neutral
+
+ for item in raw_comments:
+ original_text = item['text']
+ user = item['user']
+
+ # Шаг 3: Очистка текста
+ cleaned = clean_text(original_text)
+
+ # Шаг 3 (продолжение): Маскировка плохих слов
+ masked = mask_profanity(cleaned, BAD_WORDS)
+
+ # Шаг 4: Вычисление тональности
+ sentiment = calculate_sentiment_score(masked, POSITIVE_WORDS, NEGATIVE_WORDS)
+ sentiment_stats[sentiment] += 1
+
+ # Дополнительно: проверка наличия email
+ emails = extract_emails(masked)
+ has_email = len(emails) > 0
+ if has_email:
+ emails_found_count += 1
+
+ # Шаг 8: Формирование отчёта
+ report = generate_comment_report(masked, sentiment, has_email)
+ comment_reports.append(report)
+
+ # Сохраняем для дальнейших шагов (сохраняем связь с пользователем)
+ processed_comments.append({"user": user, "text": masked})
+
+ # ----- Шаг 5: Фильтрация по длине -----
+ all_texts = [item['text'] for item in processed_comments]
+ filtered_texts = filter_by_length(all_texts, 10, 200)
+ print(f"\nПосле фильтрации по длине (10-200 символов) осталось: {len(filtered_texts)} комментариев")
+
+ # ----- Шаг 6: Поиск дубликатов среди очищенных комментариев -----
+ duplicate_indices = find_duplicates(all_texts)
+ if duplicate_indices:
+ print(f"Найдены дубликаты на индексах: {duplicate_indices}")
+ else:
+ print("Дубликатов не найдено")
+
+ # ----- Шаг 7: Агрегация по пользователям и тегирование -----
+ user_aggregated = aggregate_by_user(processed_comments)
+ print(f"\nУникальных пользователей: {len(user_aggregated)}")
+
+ user_activity = {}
+ for user, comments_list in user_aggregated.items():
+ tag = tag_user_by_activity(processed_comments, user)
+ user_activity[user] = tag
+ print(f" {user}: {tag} активность ({len(comments_list)} комментариев)")
+
+ # ----- Шаг 9: Сохранение отфильтрованных комментариев -----
+ import os
+ output_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "filtered_comments.txt")
+ save_filtered_comments(filtered_texts, output_file)
+ print(f"\nОтфильтрованные комментарии сохранены в {output_file}")
+
+ # ----- Шаг 10: Вывод итоговой статистики -----
+ print("\n" + "=" * 50)
+ print("ИТОГОВАЯ СТАТИСТИКА:")
+ print("=" * 50)
+ print(f"1. Общее количество уникальных пользователей: {len(user_aggregated)}")
+ print(f"2. Количество комментариев, содержащих email: {emails_found_count}")
+ print(f"3. Распределение тональности:")
+ print(f" - Положительные (sentiment = 1): {sentiment_stats[1]}")
+ print(f" - Отрицательные (sentiment = -1): {sentiment_stats[-1]}")
+ print(f" - Нейтральные (sentiment = 0): {sentiment_stats[0]}")
+ print("=" * 50)
+
+ # Дополнительно: покажем пример первых 3 отчётов
+ print("\nПример первых 3 отчётов по комментариям:")
+ for i, report in enumerate(comment_reports[:3]):
+ print(f"\n Отчёт {i + 1}:")
+ print(f" Текст: {report['text'][:50]}...")
+ print(f" Тональность: {report['sentiment']}")
+ print(f" Содержит email: {report['contains_email']}")
+ print(f" Длина: {report['length']}")
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file