Update u8/program.py

This commit is contained in:
Roman Dmytruk 2026-11-03 15:06:00 +00:00
parent c18e89e18c
commit f53efb1050

View File

@ -2,138 +2,213 @@ import sys
from dataclasses import dataclass 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 @dataclass
class Node: class Node:
text: str text: str
left: "Node | None" = None # Vetva 'a' (áno) / Гілка 'a' (так) left: "Node | None" = None
right: "Node | None" = None # Vetva 'n' (nie) / Гілка 'n' (ні) right: "Node | None" = None
# rekurzívne načítanie bázy znalostí (preorder) # Виняток для помилкової бази знань.
# рекурсивне читання бази знаноь (преордер) # Výnimka pre chybnú bázu znalostí.
def read_node(lines_iterator): class DatabaseError(Exception):
# Rekurzívne načíta uzol a jeho podstromy vo formáte preorder. pass
# Рекурсивно зчитує вузол та його піддерева у форматі preorder.
try:
line = next(lines_iterator)
except StopIteration:
raise ValueError("Chýba očakávaný uzol")
# Odstránime iba znaky konca riadka (\n, \r) # Читач рядків зі стандартного входу з підтримкою LF і CRLF.
# Видаляємо лише символи кінця рядка (\n, \r) # Čítač riadkov zo štandardného vstupu s podporou LF aj CRLF.
text = line.rstrip("\r\n")
# Kontrola prázdneho riadka na konci bázy class LineReader:
# Перевірка порожнього рядка в кінці бази def __init__(self, data):
if text == "":
raise ValueError("Chýba očakávaný uzol")
node = Node(text=text) # Ділимо вхід на рядки за символом LF.
# Vstup rozdelíme na riadky podľa znaku LF.
# Ak sa text končí na '?', ide o otázku s dvoma potomkami (ľavý = áno, pravý = nie) 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
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
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("?"): 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 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): 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: if node is None:
return 0 return 0
count = count_questions(node.left) + count_questions(node.right)
is_question = 1 if node.text.endswith("?") else 0 if not is_leaf(node):
return is_question + count_questions(node.left) + count_questions(node.right) count += 1
return count
def count_answers(node): 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: if node is None:
return 0 return 0
count = count_answers(node.left) + count_answers(node.right)
if node.left is None and node.right is None: if is_leaf(node):
return 1 count += 1
return count
return count_answers(node.left) + count_answers(node.right)
# prehľadávanie stromu a dialóg s používateľom def search(node, reader):
# пошук у дереві та діалог з користувачем
def search(node): # Виводимо текст поточного вузла.
# Prechádza stromom podľa volieb používateľa až po konečnú odpoveď. # Vypíšeme text aktuálneho uzla.
# Проходить по дереву відповідно до вибору користувача до кінцевої відповіді.
# Vypíšeme text aktuálneho uzla (otázku alebo zviera)
# Виводимо текст поточного вузла (питання або звіра)
print(node.text) print(node.text)
# Ak sme v liste, našli sme zviera - vypíšeme Koniec a skončíme # Якщо знайшли відповідь, пошук завершено.
# Якщо ми в листі, ми знайшли звіра - виводимо Koniec і завершуємо # Ak sme našli odpoveď, vyhľadávanie končí.
if node.left is None and node.right is None:
print("Koniec")
return
# Načítanie voľby používateľa ('a' alebo 'n') if is_leaf(node):
# Зчитування вибору користувача ('a' або 'n') return True
choice_line = sys.stdin.readline()
if choice_line == "":
return # Neočakávaný koniec vstupu / Несподіваний кінець входу
choice = choice_line.rstrip("\r\n") # Читаємо рівно один вибір користувача.
# Načítame presne jednu voľbu používateľa.
# Spracovanie voľby: 'a' ideme vľavo, 'n' ideme vpravo choice = reader.read_line()
# Обробка вибору: 'a' перехід ліворуч, 'n' перехід праворуч if choice is None:
if choice == 'a':
search(node.left) # Вхід закінчився, коли очікувався вибір.
elif choice == 'n': # Vstup sa skončil, keď sa očakávala voľba.
search(node.right)
else: print("Koniec vstupu")
# Neplatná voľba ukončí program return False
# Некоректний вибір завершує програму if choice == "a":
return
# Так — переходимо в ліве піддерево.
# Á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(): 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.") print("Expert zo zoo to vie.")
# Načítanie celého vstupu do iterátora # Читаємо весь вхід як UTF-8.
# Зчитування всього входу в ітератор # Načítame celý vstup ako UTF-8.
lines_iterator = iter(sys.stdin.read().splitlines(keepends=True))
data = sys.stdin.buffer.read().decode("utf-8")
reader = LineReader(data)
try: try:
root = read_node(lines_iterator) root = read_database(reader)
except (ValueError, StopIteration): except DatabaseError:
print("Chybna databaza")
return return
# Výpočet a výpis celkových štatistík bázy # Кількості рахуємо для всієї бази, а не лише для обраного шляху.
# Підрахунок та вивід загальної статистики бази # Počty rátame pre celú bázu, nielen pre vybranú cestu.
q_count = count_questions(root)
a_count = count_answers(root)
print(f"Pocet otazok: {q_count}") print(f"Pocet otazok: {count_questions(root)}")
print(f"Pocet odpovedi: {a_count}") print(f"Pocet odpovedi: {count_answers(root)}")
print("Odpovedajte 'a' pre prvu moznost alebo 'n' pre druhu moznost.") print("Odpovedajte 'a' pre prvu moznost alebo 'n' pre druhu moznost.")
# Spustenie dialógu od koreňa # «Koniec» виводимо лише після знайденої тварини.
# Запуск діалогу від кореня # «Koniec» vypíšeme iba po nájdení zvieraťa.
search(root)
if search(root, reader):
print("Koniec")
if __name__ == "__main__": if __name__ == "__main__":