avp26/u8/program.py

215 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()