| """Deterministic text transformations."""
|
|
|
| from __future__ import annotations
|
|
|
| import ast
|
| import codecs
|
| import operator
|
| import re
|
|
|
| _ARITHMETIC = {
|
| ast.Add: operator.add,
|
| ast.Sub: operator.sub,
|
| ast.Mult: operator.mul,
|
| ast.Div: operator.truediv,
|
| ast.FloorDiv: operator.floordiv,
|
| ast.Mod: operator.mod,
|
| ast.Pow: operator.pow,
|
| ast.USub: operator.neg,
|
| ast.UAdd: operator.pos,
|
| }
|
|
|
| _OPPOSITES = {
|
| "left": "right",
|
| "right": "left",
|
| "up": "down",
|
| "down": "up",
|
| "true": "false",
|
| "false": "true",
|
| "yes": "no",
|
| "no": "yes",
|
| "open": "closed",
|
| "closed": "open",
|
| }
|
|
|
|
|
| def _safe_arithmetic(expression: str) -> int | float:
|
| def evaluate(node):
|
| if isinstance(node, ast.Expression):
|
| return evaluate(node.body)
|
| if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
|
| return node.value
|
| if isinstance(node, ast.BinOp) and type(node.op) in _ARITHMETIC:
|
| return _ARITHMETIC[type(node.op)](evaluate(node.left), evaluate(node.right))
|
| if isinstance(node, ast.UnaryOp) and type(node.op) in _ARITHMETIC:
|
| return _ARITHMETIC[type(node.op)](evaluate(node.operand))
|
| raise ValueError("Unsafe arithmetic expression")
|
|
|
| return evaluate(ast.parse(expression, mode="eval"))
|
|
|
|
|
| def solve_text_transformation(question: str) -> str | None:
|
| """Solve transformations only when the operation is unambiguous."""
|
| reversed_question = question[::-1]
|
| lower_reversed = reversed_question.lower()
|
| if "write" in lower_reversed and "answer" in lower_reversed:
|
|
|
| quoted = re.findall(r'["β]([^"β]+)["β]', reversed_question)
|
| if quoted:
|
| literal = quoted[-1]
|
| if "opposite" in lower_reversed:
|
| return _OPPOSITES.get(literal.casefold())
|
| return literal
|
| match = re.search(r"write\s+(?:the\s+)?(?:word\s+)?([a-z-]+)", lower_reversed)
|
| if match:
|
| return match.group(1)
|
|
|
| quoted = re.findall(r'["β]([^"β]+)["β]', question)
|
| source = quoted[0] if quoted else None
|
| lowered = question.lower()
|
| if source and re.search(r"\breverse\b", lowered):
|
| return source[::-1]
|
| if source and re.search(r"\buppercase\b", lowered):
|
| return source.upper()
|
| if source and re.search(r"\blowercase\b", lowered):
|
| return source.lower()
|
| if source and re.search(r"\balphabeti[sz]e\b", lowered):
|
| return " ".join(sorted(source.split(), key=str.casefold))
|
| if source and re.search(r"\brot\s*-?13\b", lowered):
|
| return codecs.decode(source, "rot_13")
|
| if source and re.search(r"\bsort\b.*\bnumeric", lowered):
|
| numbers = re.findall(r"[-+]?\d+(?:\.\d+)?", source)
|
| return ", ".join(sorted(numbers, key=float))
|
| arithmetic = re.search(
|
| r"(?:calculate|compute|evaluate|what is)\s+([\d\s+*/().%-]+)\??\s*$",
|
| question,
|
| re.IGNORECASE,
|
| )
|
| if arithmetic:
|
| result = _safe_arithmetic(arithmetic.group(1).strip())
|
| return (
|
| str(int(result))
|
| if isinstance(result, float) and result.is_integer()
|
| else str(result)
|
| )
|
| extraction = re.search(
|
| r"extract\s+the\s+(\d+)(?:st|nd|rd|th)\s+word\s+from\s+[\"β]([^\"β]+)",
|
| question,
|
| re.IGNORECASE,
|
| )
|
| if extraction:
|
| index = int(extraction.group(1)) - 1
|
| words = extraction.group(2).split()
|
| return words[index] if 0 <= index < len(words) else None
|
| return None
|
|
|