Files
Markov/src/rulebook.py
ElectronixTM 666657926b feat: сделал первую версию CLI
Проект в целом получил свой MVP
2025-03-16 03:52:07 +03:00

114 lines
4.0 KiB
Python

"""
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