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
# 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):
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("?"):
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, reader):
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)
# Виводимо текст поточного вузла (питання або звіра)
# Виводимо текст поточного вузла.
# 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
# Якщо знайшли відповідь, пошук завершено.
# Ak sme našli odpoveď, vyhľadávanie končí.
# 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 / Несподіваний кінець входу
if is_leaf(node):
return True
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
# Обробка вибору: 'a' перехід ліворуч, 'n' перехід праворуч
if choice == 'a':
search(node.left)
elif choice == 'n':
search(node.right)
else:
# Neplatná voľba ukončí program
# Некоректний вибір завершує програму
return
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)
# Кількості рахуємо для всієї бази, а не лише для обраного шляху.
# Počty rátame pre celú bázu, nielen pre vybranú cestu.
print(f"Pocet otazok: {q_count}")
print(f"Pocet odpovedi: {a_count}")
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__":