""" Rulebook class is deisgned to work with strings. this class applies markov algorightm to given string """ from dataclasses import dataclass from typing import ClassVar from rule import Rule, EMPTY_SYMBOL @dataclass class Rulebook: MAX_DEPTH: ClassVar[int] = 100 rules: list[Rule] def transform(self, string: str) -> str: """ Applies the next appropriate rule to given string. The priority of rules is determined by their indexes in list of rules. Lesser the index - higher the priority. Return transformed string """ for _ in range(self.MAX_DEPTH): rule = self.find_appropriate_rule(string) if rule is None: return string string = self._apply_rule(rule, string) if rule.is_blocking: return string raise ValueError("The amount of transformations exceeded " f"{self.MAX_DEPTH}. You can change maximum " "amount by setting class variable MAX_DEPTH," " but may be something wrong with your input") def logged_transform(self, string: str) -> tuple[str | None, str]: """ Does exactly the same thing as transform, but logging the whole process, so it can be used to create reports NOTE: It's literally COPY + PASTE in it's worst, but I don't like the idea of splitting anything apart, because of the specificity of the task Returns the result of transformations and full report in str respectively """ CENTER_WIDTH: int = 30 log = "НАЧАЛО ЛИСТИНГА".center(CENTER_WIDTH, "=") + '\n' log += self._log_rules() log += f"Ввод: \"{string}\"\n" for _ in range(self.MAX_DEPTH): log += string + "\n" rule = self.find_appropriate_rule(string) if rule is None: log += "NO_MORE_RULES".center(CENTER_WIDTH, "_") + '\n' return string, log log += " " * string.find(rule.operand) + self._log_rule(rule) + '\n' string = self._apply_rule(rule, string) if rule.is_blocking: log += string + '\n' log += "FINAL_RULE".center(CENTER_WIDTH, "_") + '\n' return string, log log += "OVERFLOW".center(CENTER_WIDTH, "_") + '\n' return None, log def _log_rule(self, rule) -> str: arrow = "->|" if rule.is_blocking else "->" return f"{rule.operand} {arrow} {rule.target}" def _log_rules(self) -> str: log = "" for num, rule in enumerate(self.rules, start=1): #log += f"\t{num} : {rule.operand} {arrow} {rule.target}\n" log += " " * 4 + f"{num} : " + self._log_rule(rule) + "\n" return log def find_appropriate_rule(self, string) -> Rule | None: """ Searches for appropriate rule in rules list and if found one returns it, otherwise returns None """ for rule in self.rules: if self._is_rule_appropriate(rule, string): return rule def _apply_rule(self, rule: Rule, string: str) -> str: """ Tries to apply given rule to string. Doesn't check wether the rule is appropriate or not. Inapplicability leads to undefined behaviour """ if rule.operand == EMPTY_SYMBOL: string = EMPTY_SYMBOL + string string = ( string .replace(rule.operand, rule.target, 1) .replace(EMPTY_SYMBOL, '') ) return string def _is_rule_appropriate(self, rule: Rule, string: str) -> bool: """ Concrete realization of rule applicability check. Moved to separate function mostly for extensibility purposes """ # Hardest part is always about empty set if rule.operand == EMPTY_SYMBOL: return True return rule.operand in string