Modificacion en plantilla para que acepte nuevos patrones de soluciones

This commit is contained in:
2026-05-20 21:06:33 +02:00
parent b5fb1445fd
commit 77f92130bf

View File

@@ -5,21 +5,77 @@ Creation of templates for Exercitium problems
import ast import ast
import re import re
import shutil
from argparse import ArgumentParser from argparse import ArgumentParser
from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
@dataclass(frozen=True) @dataclass(frozen=True)
class Example: class Example:
input_value: object input_values: tuple[object, ...]
expected_value: object expected_value: object
def load_instructions(path: Path) -> str: def load_instructions(path: Path) -> str:
return path.read_text(encoding="utf-8").strip() return path.read_text(encoding="utf-8").strip()
def split_top_level_values(text: str) -> list[str]:
parts: list[str] = []
current: list[str] = []
depth = 0
quote_char: str | None = None
escaped = False
for char in text.strip():
if quote_char is not None:
current.append(char)
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote_char:
quote_char = None
continue
if char in {"'", '"'}:
quote_char = char
current.append(char)
continue
if char in "([{":
depth += 1
current.append(char)
continue
if char in ")]}":
depth -= 1
current.append(char)
continue
if char.isspace() and depth == 0:
if current:
parts.append("".join(current))
current = []
continue
current.append(char)
if quote_char is not None or depth != 0:
raise ValueError(f"Cannot parse input values: {text}")
if current:
parts.append("".join(current))
return parts
def parse_input_values(text: str) -> tuple[object, ...]:
parts = split_top_level_values(text)
if not parts:
raise ValueError("No input values found")
return tuple(ast.literal_eval(part) for part in parts)
def parse_instructions(text: str) -> tuple[str, list[Example]]: def parse_instructions(text: str) -> tuple[str, list[Example]]:
examples: list[Example] = [] examples: list[Example] = []
@@ -43,7 +99,7 @@ def parse_instructions(text: str) -> tuple[str, list[Example]]:
examples.append( examples.append(
Example( Example(
input_value=ast.literal_eval(match.group(2).strip()), input_values=parse_input_values(match.group(2).strip()),
expected_value=ast.literal_eval(right.strip()), expected_value=ast.literal_eval(right.strip()),
) )
) )
@@ -53,6 +109,8 @@ def parse_instructions(text: str) -> tuple[str, list[Example]]:
return function_name, examples return function_name, examples
def build_names(prefix: str, count: int) -> list[str]:
return [f"{prefix}_{index}" for index in range(1, count + 1)]
def infer_python_type(value: object) -> str: def infer_python_type(value: object) -> str:
if isinstance(value, bool): if isinstance(value, bool):
@@ -64,6 +122,9 @@ def infer_python_type(value: object) -> str:
if isinstance(value, list): if isinstance(value, list):
inner_type = infer_python_type(value[0]) if value else "object" inner_type = infer_python_type(value[0]) if value else "object"
return f"list[{inner_type}]" return f"list[{inner_type}]"
if isinstance(value, tuple):
inner_types = ", ".join(infer_python_type(item) for item in value)
return f"tuple[{inner_types}]"
return "object" return "object"
@@ -90,6 +151,9 @@ def infer_rust_type(value: object) -> str:
if isinstance(value, list): if isinstance(value, list):
inner_type = infer_rust_type(value[0]) if value else "i32" inner_type = infer_rust_type(value[0]) if value else "i32"
return f"Vec<{inner_type}>" return f"Vec<{inner_type}>"
if isinstance(value, tuple):
inner_types = ", ".join(infer_rust_type(item) for item in value)
return f"({inner_types})"
raise TypeError(f"Unsupported Rust type: {value!r}") raise TypeError(f"Unsupported Rust type: {value!r}")
@@ -109,6 +173,8 @@ def render_julia(value: object) -> str:
return f'"{value}"' return f'"{value}"'
if isinstance(value, list): if isinstance(value, list):
return "[" + ", ".join(render_julia(item) for item in value) + "]" return "[" + ", ".join(render_julia(item) for item in value) + "]"
if isinstance(value, tuple):
return "(" + ", ".join(render_julia(item) for item in value) + ")"
return str(value) return str(value)
@@ -129,51 +195,167 @@ def render_rust(value: object) -> str:
return f'"{value}"' return f'"{value}"'
if isinstance(value, list): if isinstance(value, list):
return "vec![" + ", ".join(render_rust(item) for item in value) + "]" return "vec![" + ", ".join(render_rust(item) for item in value) + "]"
if isinstance(value, tuple):
return "(" + ", ".join(render_rust(item) for item in value) + ")"
return str(value) return str(value)
def build_python_checks(function_name: str, examples: list[Example]) -> str: def build_python_checks(function_name: str, examples: list[Example]) -> str:
return "\n\n".join( blocks: list[str] = []
[
f"check = {render_python(example.input_value)}\n" for example in examples:
f"print({function_name}(check)) # {render_python(example.expected_value)}" lines: list[str] = []
for example in examples names = build_names("check", len(example.input_values))
]
for name, value in zip(names, example.input_values):
lines.append(f"{name} = {render_python(value)}")
lines.append(
f"print({function_name}({', '.join(names)}))"
f" # {render_python(example.expected_value)}"
) )
blocks.append("\n".join(lines))
return "\n\n".join(blocks)
def build_julia_checks(function_name: str, examples: list[Example]) -> str: def build_julia_checks(function_name: str, examples: list[Example]) -> str:
return "\n\n".join( blocks: list[str] = []
[
f"check = {render_julia(example.input_value)}\n"
f"println({function_name}(check)) # {render_julia(example.expected_value)}"
for example in examples
]
)
for example in examples:
lines: list[str] = []
names = build_names("check", len(example.input_values))
for name, value in zip(names, example.input_values):
lines.append(f"{name} = {render_julia(value)}")
lines.append(
f"println({function_name}({', '.join(names)}))"
f" # {render_julia(example.expected_value)}"
)
blocks.append("\n".join(lines))
return "\n\n".join(blocks)
def render_rust_call_arg(name: str, value: object) -> str:
if isinstance(value, list):
return f"&{name}"
return name
def build_rust_checks(function_name: str, examples: list[Example]) -> str: def build_rust_checks(function_name: str, examples: list[Example]) -> str:
return "\n\n".join( blocks: list[str] = []
[
f" let check = {render_rust(example.input_value)};\n" for example in examples:
f' println!("{{:?}}", {function_name}(&check)); // {render_python(example.expected_value)}' lines: list[str] = []
for example in examples names = build_names("check", len(example.input_values))
]
for name, value in zip(names, example.input_values):
lines.append(f" let {name} = {render_rust(value)};")
call_args = ", ".join(
render_rust_call_arg(name, value)
for name, value in zip(names, example.input_values)
) )
lines.append(
f' println!("{{:?}}", {function_name}({call_args}));'
f" // {render_python(example.expected_value)}"
)
blocks.append("\n".join(lines))
return "\n\n".join(blocks)
def build_go_checks(function_name: str, examples: list[Example]) -> str: def build_go_checks(function_name: str, examples: list[Example]) -> str:
checks: list[str] = [] blocks: list[str] = []
for index, example in enumerate(examples): for example in examples:
assign_op = ":=" if index == 0 else "=" lines: list[str] = [" {"]
checks.append( input_names = build_names("check", len(example.input_values))
f" check {assign_op} {render_go(example.input_value)}\n"
f" fmt.Println({function_name}(check)) // {render_python(example.expected_value)}" for name, value in zip(input_names, example.input_values):
lines.append(f" {name} := {render_go(value)}")
if isinstance(example.expected_value, tuple):
result_names = build_names("result", len(example.expected_value))
lines.append(
f" {', '.join(result_names)} := "
f"{function_name}({', '.join(input_names)})"
)
lines.append(
f" fmt.Println({', '.join(result_names)})"
f" // {render_python(example.expected_value)}"
)
else:
lines.append(
f" fmt.Println({function_name}({', '.join(input_names)}))"
f" // {render_python(example.expected_value)}"
) )
return "\n\n".join(checks) lines.append(" }")
blocks.append("\n".join(lines))
return "\n\n".join(blocks)
def default_python_value(value: object) -> str:
if isinstance(value, bool):
return "False"
if isinstance(value, int):
return "0"
if isinstance(value, str):
return '""'
if isinstance(value, list):
return "[]"
if isinstance(value, tuple):
return "(" + ", ".join(default_python_value(item) for item in value) + ")"
return "None"
def default_julia_value(value: object) -> str:
if isinstance(value, bool):
return "false"
if isinstance(value, int):
return "0"
if isinstance(value, str):
return '""'
if isinstance(value, list):
return "[]"
if isinstance(value, tuple):
return "(" + ", ".join(default_julia_value(item) for item in value) + ")"
return "nothing"
def default_rust_value(value: object) -> str:
if isinstance(value, bool):
return "false"
if isinstance(value, int):
return "0"
if isinstance(value, str):
return '""'
if isinstance(value, list):
return "vec![]"
if isinstance(value, tuple):
return "(" + ", ".join(default_rust_value(item) for item in value) + ")"
raise TypeError(f"Unsupported Rust type: {value!r}")
def default_go_value(value: object) -> str:
if isinstance(value, bool):
return "false"
if isinstance(value, int):
return "0"
if isinstance(value, str):
return '""'
if isinstance(value, list):
return f"{infer_go_type(value)}{{}}"
if isinstance(value, tuple):
return ", ".join(default_go_value(item) for item in value)
raise TypeError(f"Unsupported Go type: {value!r}")
def infer_go_signature_return(value: object) -> str:
if isinstance(value, tuple):
return "(" + ", ".join(infer_go_type(item) for item in value) + ")"
return infer_go_type(value)
def comment_block(prefix: str, text: str) -> str: def comment_block(prefix: str, text: str) -> str:
return "\n".join( return "\n".join(
@@ -181,19 +363,34 @@ def comment_block(prefix: str, text: str) -> str:
for line in text.splitlines() for line in text.splitlines()
) )
def create_if_missing(
output_path: Path,
creator: Callable[[Path, str, list[Example]], None],
function_name: str,
examples: list[Example],
) -> None:
if output_path.exists():
return
output_path.parent.mkdir(parents=True, exist_ok=True)
creator(output_path, function_name, examples)
def create_python( def create_python(
problem_py: Path, problem_py: Path,
function_name: str, function_name: str,
examples: list[Example], examples: list[Example],
) -> None: ) -> None:
input_type = infer_python_type(examples[0].input_value) input_names = build_names("value", len(examples[0].input_values))
params = ", ".join(
f"{name}: {infer_python_type(value)}"
for name, value in zip(input_names, examples[0].input_values)
)
return_type = infer_python_type(examples[0].expected_value) return_type = infer_python_type(examples[0].expected_value)
template = "\n".join( template = "\n".join(
[ [
f"def {function_name}(values: {input_type}) -> {return_type}:", f"def {function_name}({params}) -> {return_type}:",
" return []", f" return {default_python_value(examples[0].expected_value)}",
"", "",
build_python_checks(function_name, examples), build_python_checks(function_name, examples),
"", "",
@@ -207,10 +404,12 @@ def create_julia(
function_name: str, function_name: str,
examples: list[Example], examples: list[Example],
) -> None: ) -> None:
params = ", ".join(build_names("value", len(examples[0].input_values)))
template = "\n".join( template = "\n".join(
[ [
f"function {function_name}(values)", f"function {function_name}({params})",
" return []", f" return {default_julia_value(examples[0].expected_value)}",
"end", "end",
"", "",
build_julia_checks(function_name, examples), build_julia_checks(function_name, examples),
@@ -225,14 +424,17 @@ def create_rust(
function_name: str, function_name: str,
examples: list[Example], examples: list[Example],
) -> None: ) -> None:
input_type = infer_rust_param_type(examples[0].input_value) input_names = build_names("value", len(examples[0].input_values))
params = ", ".join(
f"{name}: {infer_rust_param_type(value)}"
for name, value in zip(input_names, examples[0].input_values)
)
return_type = infer_rust_type(examples[0].expected_value) return_type = infer_rust_type(examples[0].expected_value)
template = "\n".join( template = "\n".join(
[ [
f"fn {function_name}(values: {input_type}) -> {return_type} {{", f"fn {function_name}({params}) -> {return_type} {{",
" let _ = values;", f" {default_rust_value(examples[0].expected_value)}",
" vec![]",
"}", "}",
"", "",
"fn main() {", "fn main() {",
@@ -249,8 +451,12 @@ def create_go(
function_name: str, function_name: str,
examples: list[Example], examples: list[Example],
) -> None: ) -> None:
input_type = infer_go_type(examples[0].input_value) input_names = build_names("value", len(examples[0].input_values))
return_type = infer_go_type(examples[0].expected_value) params = ", ".join(
f"{name} {infer_go_type(value)}"
for name, value in zip(input_names, examples[0].input_values)
)
return_type = infer_go_signature_return(examples[0].expected_value)
template = "\n".join( template = "\n".join(
[ [
@@ -258,9 +464,8 @@ def create_go(
"", "",
'import "fmt"', 'import "fmt"',
"", "",
f"func {function_name}(values {input_type}) {return_type} " + "{", f"func {function_name}({params}) {return_type} " + "{",
" _ = values", f" return {default_go_value(examples[0].expected_value)}",
f" return {return_type}" + "{}",
"}", "}",
"", "",
"func main() {", "func main() {",
@@ -288,18 +493,14 @@ if __name__ == "__main__":
instructions = load_instructions(base_dir / "instructions.txt") instructions = load_instructions(base_dir / "instructions.txt")
problem_stem = f"{args.problem_number:03d}" problem_stem = f"{args.problem_number:03d}"
problem_py = base_dir / f"{problem_stem}.py" problem_py = base_dir / "Python" / f"{problem_stem}.py"
problem_jl = base_dir / f"{problem_stem}.jl" problem_jl = base_dir / "Julia" / f"{problem_stem}.jl"
problem_rs = base_dir / f"{problem_stem}.rs" problem_rs = base_dir / "Rust" / f"{problem_stem}.rs"
problem_go = base_dir / f"{problem_stem}.go" problem_go = base_dir / "Go" / f"{problem_stem}.go"
function_name, examples = parse_instructions(instructions) function_name, examples = parse_instructions(instructions)
create_python(problem_py, function_name, examples)
create_julia(problem_jl, function_name, examples)
create_rust(problem_rs, function_name, examples)
create_go(problem_go, function_name, examples)
shutil.move(problem_py, base_dir / "Python") create_if_missing(problem_py, create_python, function_name, examples)
shutil.move(problem_jl, base_dir / "Julia") create_if_missing(problem_jl, create_julia, function_name, examples)
shutil.move(problem_rs, base_dir / "Rust") create_if_missing(problem_rs, create_rust, function_name, examples)
shutil.move(problem_go, base_dir / "Go") create_if_missing(problem_go, create_go, function_name, examples)