diff --git a/u8/program.py b/u8/program.py index 72ddb32..39a54db 100644 --- a/u8/program.py +++ b/u8/program.py @@ -2,138 +2,213 @@ import sys from dataclasses import dataclass -# dátová struktúra pre uzol stromu / датова структура для вузла дерева +# Вузол дерева: текст питання або назва тварини, лівий (так) і правий (ні) нащадки. +# Uzol stromu: text otázky alebo názov zvieraťa, ľavý (áno) a pravý (nie) potomok. @dataclass class Node: text: str - left: "Node | None" = None # Vetva 'a' (áno) / Гілка 'a' (так) - right: "Node | None" = None # Vetva 'n' (nie) / Гілка 'n' (ні) + left: "Node | None" = None + right: "Node | None" = None -# rekurzívne načítanie bázy znalostí (preorder) -# рекурсивне читання бази знаноь (преордер) +# Виняток для помилкової бази знань. +# Výnimka pre chybnú bázu znalostí. -def read_node(lines_iterator): - # Rekurzívne načíta uzol a jeho podstromy vo formáte preorder. - # Рекурсивно зчитує вузол та його піддерева у форматі preorder. +class DatabaseError(Exception): + pass - try: - line = next(lines_iterator) - except StopIteration: - raise ValueError("Chýba očakávaný uzol") - # Odstránime iba znaky konca riadka (\n, \r) - # Видаляємо лише символи кінця рядка (\n, \r) - text = line.rstrip("\r\n") +# Читач рядків зі стандартного входу з підтримкою LF і CRLF. +# Čítač riadkov zo štandardného vstupu s podporou LF aj CRLF. - # Kontrola prázdneho riadka na konci bázy - # Перевірка порожнього рядка в кінці бази - if text == "": - raise ValueError("Chýba očakávaný uzol") +class LineReader: + def __init__(self, data): + + # Ділимо вхід на рядки за символом LF. + # Vstup rozdelíme na riadky podľa znaku LF. + + lines = data.split("\n") + + # Якщо вхід закінчується LF, останній порожній елемент не є рядком. + # Ak vstup končí znakom LF, posledný prázdny prvok nie je riadok. + + if lines and lines[-1] == "": + lines.pop() + + # Прибираємо лише CR з кінця (для CRLF), пробіли залишаємо. + # Odstránime iba CR na konci (pre CRLF), medzery ponecháme. + + self.lines = [line[:-1] if line.endswith("\r") else line for line in lines] + self.pos = 0 - node = Node(text=text) + def read_line(self): + + # Повертає наступний рядок або None, якщо вхід закінчився. + # Vráti ďalší riadok alebo None, ak sa vstup skončil. + + if self.pos >= len(self.lines): + return None + line = self.lines[self.pos] + self.pos += 1 + return line - # Ak sa text končí na '?', ide o otázku s dvoma potomkami (ľavý = áno, pravý = nie) - # Якщо текст закінчується на '?', це питання з двома потомками (лівий = так, правий = ні) + +def is_leaf(node): + + # Лист — вузол без нащадків (кінцева відповідь). + # List je uzol bez potomkov (konečná odpoveď). + + return node.left is None and node.right is None + + +def read_node(reader): + + # Рекурсивно читаємо вузол і його піддерева у форматі preorder. + # Rekurzívne načítame uzol a jeho podstromy vo formáte preorder. + + text = reader.read_line() + if text is None or text == "": + + # Очікуваний вузол відсутній. + # Chýba očakávaný uzol. + + raise DatabaseError() + node = Node(text) if node.text.endswith("?"): - node.left = read_node(lines_iterator) - node.right = read_node(lines_iterator) - + + # Питання має рівно двох нащадків: спершу ліве піддерево, потім праве. + # Otázka má presne dvoch potomkov: najprv ľavý podstrom, potom pravý. + + node.left = read_node(reader) + node.right = read_node(reader) return node -# rekurzívne počítanie prvkov bázy -# рекурсивний підрахунок елементів бази +def read_database(reader): + + # Читаємо все дерево і перевіряємо обов'язковий порожній рядок-роздільник. + # Načítame celý strom a overíme povinný prázdny oddeľovací riadok + + root = read_node(reader) + separator = reader.read_line() + if separator is None or separator != "": + + # Відсутній роздільник або зайві вузли після завершення дерева. + # Chýba oddeľovač alebo sú ďalšie uzly po dokončení stromu. + + raise DatabaseError() + return root + def count_questions(node): - # Spočíta všetky vnútorné uzly (otázky končiace znakom '?'). - # Підраховує всі внутрішні вузли (питання, що закінчуються на '?'). - + + # Рекурсивно рахуємо внутрішні вузли (питання). + # Rekurzívne počítame vnútorné uzly (otázky). + if node is None: return 0 - - is_question = 1 if node.text.endswith("?") else 0 - return is_question + count_questions(node.left) + count_questions(node.right) + count = count_questions(node.left) + count_questions(node.right) + if not is_leaf(node): + count += 1 + return count def count_answers(node): - # Spočíta všetky listové uzly (konečné odpovede / zvieratá). - # Підраховує всі листові вузли (кінцеві відповіді / звірів). - + + # Рекурсивно рахуємо листи (кінцеві відповіді). + # Rekurzívne počítame listy (konečné odpovede). + if node is None: return 0 - - if node.left is None and node.right is None: - return 1 - - return count_answers(node.left) + count_answers(node.right) + count = count_answers(node.left) + count_answers(node.right) + if is_leaf(node): + count += 1 + return count -# prehľadávanie stromu a dialóg s používateľom -# пошук у дереві та діалог з користувачем - -def search(node): - # Prechádza stromom podľa volieb používateľa až po konečnú odpoveď. - # Проходить по дереву відповідно до вибору користувача до кінцевої відповіді. - - # Vypíšeme text aktuálneho uzla (otázku alebo zviera) - # Виводимо текст поточного вузла (питання або звіра) - +def search(node, reader): + + # Виводимо текст поточного вузла. + # Vypíšeme text aktuálneho uzla. + print(node.text) - - # Ak sme v liste, našli sme zviera - vypíšeme Koniec a skončíme - # Якщо ми в листі, ми знайшли звіра - виводимо Koniec і завершуємо - if node.left is None and node.right is None: - print("Koniec") - return - - # Načítanie voľby používateľa ('a' alebo 'n') - # Зчитування вибору користувача ('a' або 'n') - choice_line = sys.stdin.readline() - if choice_line == "": - return # Neočakávaný koniec vstupu / Несподіваний кінець входу - - choice = choice_line.rstrip("\r\n") - - # Spracovanie voľby: 'a' ideme vľavo, 'n' ideme vpravo - # Обробка вибору: 'a' перехід ліворуч, 'n' перехід праворуч - if choice == 'a': - search(node.left) - elif choice == 'n': - search(node.right) - else: - # Neplatná voľba ukončí program - # Некоректний вибір завершує програму - return + + # Якщо знайшли відповідь, пошук завершено. + # Ak sme našli odpoveď, vyhľadávanie končí. + + if is_leaf(node): + return True + + # Читаємо рівно один вибір користувача. + # Načítame presne jednu voľbu používateľa. + + choice = reader.read_line() + if choice is None: + + # Вхід закінчився, коли очікувався вибір. + # Vstup sa skončil, keď sa očakávala voľba. + + print("Koniec vstupu") + return False + if choice == "a": + + # Так — переходимо в ліве піддерево. + # Áno — pokračujeme do ľavého podstromu. + + return search(node.left, reader) + if choice == "n": + + # Ні — переходимо в праве піддерево. + # Nie — pokračujeme do pravého podstromu. + + return search(node.right, reader) + + # Недійсний вибір (зокрема порожній рядок). + # Neplatná voľba (vrátane prázdneho riadka). + + print("Nerozumiem") + return False def main(): - # Požadovaný úvodný výpis - # Обов'язковий стартовий вивід + + # Збільшуємо ліміт рекурсії для глибоких дерев. + # Zvýšime limit rekurzie pre hlboké stromy. + + sys.setrecursionlimit(1000000) + + # Вивід у UTF-8 з LF наприкінці рядків. + # Výstup v UTF-8 s LF na konci riadkov. + + sys.stdout.reconfigure(encoding="utf-8", newline="\n") + print("Expert zo zoo to vie.") - # Načítanie celého vstupu do iterátora - # Зчитування всього входу в ітератор - lines_iterator = iter(sys.stdin.read().splitlines(keepends=True)) + # Читаємо весь вхід як UTF-8. + # Načítame celý vstup ako UTF-8. + + data = sys.stdin.buffer.read().decode("utf-8") + reader = LineReader(data) try: - root = read_node(lines_iterator) - except (ValueError, StopIteration): + root = read_database(reader) + except DatabaseError: + print("Chybna databaza") return - # Výpočet a výpis celkových štatistík bázy - # Підрахунок та вивід загальної статистики бази - q_count = count_questions(root) - a_count = count_answers(root) - - print(f"Pocet otazok: {q_count}") - print(f"Pocet odpovedi: {a_count}") + # Кількості рахуємо для всієї бази, а не лише для обраного шляху. + # Počty rátame pre celú bázu, nielen pre vybranú cestu. + + print(f"Pocet otazok: {count_questions(root)}") + print(f"Pocet odpovedi: {count_answers(root)}") print("Odpovedajte 'a' pre prvu moznost alebo 'n' pre druhu moznost.") - # Spustenie dialógu od koreňa - # Запуск діалогу від кореня - search(root) + # «Koniec» виводимо лише після знайденої тварини. + # «Koniec» vypíšeme iba po nájdení zvieraťa. + + if search(root, reader): + print("Koniec") if __name__ == "__main__":