215 lines
6.8 KiB
Python
215 lines
6.8 KiB
Python
import sys
|
||
from dataclasses import dataclass
|
||
|
||
|
||
# Вузол дерева: текст питання або назва тварини, лівий (так) і правий (ні) нащадки.
|
||
# 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
|
||
right: "Node | None" = None
|
||
|
||
|
||
# Виняток для помилкової бази знань.
|
||
# Výnimka pre chybnú bázu znalostí.
|
||
|
||
class DatabaseError(Exception):
|
||
pass
|
||
|
||
|
||
# Читач рядків зі стандартного входу з підтримкою LF і CRLF.
|
||
# Čítač riadkov zo štandardného vstupu s podporou LF aj CRLF.
|
||
|
||
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
|
||
|
||
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("?"):
|
||
|
||
# Питання має рівно двох нащадків: спершу ліве піддерево, потім праве.
|
||
# Otázka má presne dvoch potomkov: najprv ľavý podstrom, potom pravý.
|
||
|
||
node.left = read_node(reader)
|
||
node.right = read_node(reader)
|
||
return node
|
||
|
||
|
||
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):
|
||
|
||
# Рекурсивно рахуємо внутрішні вузли (питання).
|
||
# Rekurzívne počítame vnútorné uzly (otázky).
|
||
|
||
if node is None:
|
||
return 0
|
||
count = count_questions(node.left) + count_questions(node.right)
|
||
if not is_leaf(node):
|
||
count += 1
|
||
return count
|
||
|
||
|
||
def count_answers(node):
|
||
|
||
# Рекурсивно рахуємо листи (кінцеві відповіді).
|
||
# Rekurzívne počítame listy (konečné odpovede).
|
||
|
||
if node is None:
|
||
return 0
|
||
count = count_answers(node.left) + count_answers(node.right)
|
||
if is_leaf(node):
|
||
count += 1
|
||
return count
|
||
|
||
|
||
def search(node, reader):
|
||
|
||
# Виводимо текст поточного вузла.
|
||
# Vypíšeme text aktuálneho uzla.
|
||
|
||
print(node.text)
|
||
|
||
# Якщо знайшли відповідь, пошук завершено.
|
||
# 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():
|
||
|
||
# Збільшуємо ліміт рекурсії для глибоких дерев.
|
||
# 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.")
|
||
|
||
# Читаємо весь вхід як UTF-8.
|
||
# Načítame celý vstup ako UTF-8.
|
||
|
||
data = sys.stdin.buffer.read().decode("utf-8")
|
||
reader = LineReader(data)
|
||
|
||
try:
|
||
root = read_database(reader)
|
||
except DatabaseError:
|
||
print("Chybna databaza")
|
||
return
|
||
|
||
# Кількості рахуємо для всієї бази, а не лише для обраного шляху.
|
||
# 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.")
|
||
|
||
# «Koniec» виводимо лише після знайденої тварини.
|
||
# «Koniec» vypíšeme iba po nájdení zvieraťa.
|
||
|
||
if search(root, reader):
|
||
print("Koniec")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main() |