166 lines
5.2 KiB
Python
166 lines
5.2 KiB
Python
import runpy
|
|
from pathlib import Path
|
|
|
|
import typer
|
|
|
|
app = typer.Typer()
|
|
|
|
|
|
@app.command("add", help="Add a new problem to the projecteuler repository.")
|
|
def add_newproblem(num: int, name: str | None = None) -> None:
|
|
"""Add a new problem to the projecteuler repository."""
|
|
typer.echo(f"Adding problem {num} to the projecteuler repository.")
|
|
if name:
|
|
title = f"{num:04d}.{name}"
|
|
else:
|
|
title = f"{num:04d}"
|
|
Path(f"solutions/{title}").mkdir(parents=True, exist_ok=True)
|
|
file_name = f"solutions/{title}/euler_{num}.py"
|
|
Path(file_name).touch()
|
|
with open(file_name, "w") as f:
|
|
f.write("""'''
|
|
|
|
'''
|
|
import time
|
|
from functools import wraps
|
|
from typing import Any, Callable, TypeVar
|
|
|
|
F = TypeVar("F", bound=Callable[..., Any])
|
|
|
|
|
|
def benchmark(repeat: int = 1) -> Callable[[F], F]:
|
|
'''
|
|
重复运行目标函数并计算平均耗时。
|
|
|
|
平均耗时会被存储在 wrapper.avg_time 和 wrapper.total_time 中,
|
|
同时会打印到控制台。函数的返回值不受影响。
|
|
'''
|
|
if repeat < 1:
|
|
raise ValueError("repeat 必须 >= 1")
|
|
|
|
def decorator(func: F) -> F:
|
|
@wraps(func)
|
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
total = 0.0
|
|
result = None
|
|
|
|
for _ in range(repeat):
|
|
start = time.perf_counter()
|
|
result = func(*args, **kwargs)
|
|
end = time.perf_counter()
|
|
total += end - start
|
|
|
|
wrapper.avg_time = total / repeat # type: ignore[attr-defined]
|
|
wrapper.total_time = total # type: ignore[attr-defined]
|
|
|
|
print(
|
|
f"[Benchmark] {func.__name__} | 重复 {repeat} 次 | "
|
|
f"平均: {wrapper.avg_time:.6f}s | 总计: {wrapper.total_time:.6f}s" # type: ignore[attr-defined]
|
|
)
|
|
return result
|
|
|
|
# 初始化属性,避免调用前访问报错
|
|
wrapper.avg_time = 0.0 # type: ignore[attr-defined]
|
|
wrapper.total_time = 0.0 # type: ignore[attr-defined]
|
|
return wrapper # type: ignore[return-value]
|
|
|
|
return decorator
|
|
""")
|
|
|
|
|
|
@app.command("list", help="List all problems in the projecteuler repository.")
|
|
def list_problems(every: bool = False):
|
|
"""List all problems in the projecteuler repository."""
|
|
if every:
|
|
typer.echo("Listing all problems in the projecteuler repository.")
|
|
else:
|
|
typer.echo("Listing near problems in the projecteuler repository.")
|
|
for path in Path("solutions").iterdir():
|
|
if path.is_dir():
|
|
if every:
|
|
tmp = list(path.iterdir())
|
|
tmp = all(ff.is_dir() for ff in tmp)
|
|
if tmp:
|
|
for ff in path.iterdir():
|
|
if ff.is_dir():
|
|
typer.echo(f"{ff.name}")
|
|
else:
|
|
typer.echo(f"{path.name}")
|
|
else:
|
|
if "_" in path.name:
|
|
typer.echo(f"{path.name} (completed)")
|
|
else:
|
|
typer.echo(f"{path.name}")
|
|
|
|
|
|
@app.command("solution", help="Run a solution for a given problem.")
|
|
def run_solution(
|
|
num: int,
|
|
special: None | str = None,
|
|
list_solutions: bool = False,
|
|
) -> None:
|
|
# Find target folders that match the problem number
|
|
target_folders = []
|
|
for folder in Path("solutions").iterdir():
|
|
if not folder.is_dir():
|
|
continue
|
|
# Check for exact match (e.g., "0001")
|
|
if folder.name.startswith(f"{num:04d}"):
|
|
target_folders.append(folder)
|
|
break
|
|
# Check for range folders (e.g., "0001_0050")
|
|
if "_" in folder.name:
|
|
try:
|
|
start_str, end_str = folder.name.split("_")
|
|
start_num = int(start_str)
|
|
end_num = int(end_str)
|
|
if start_num <= num <= end_num:
|
|
for subfolder in folder.iterdir():
|
|
if subfolder.is_dir() and subfolder.name.startswith(
|
|
f"{num:04d}"
|
|
):
|
|
target_folders.append(subfolder)
|
|
break
|
|
except (ValueError, IndexError):
|
|
continue
|
|
|
|
if not target_folders:
|
|
typer.echo(f"No folder found for problem {num}")
|
|
return
|
|
|
|
target_folder = target_folders[0]
|
|
|
|
if list_solutions:
|
|
# List all Python files in the target folder
|
|
for file in target_folder.iterdir():
|
|
if file.is_file() and file.suffix == ".py":
|
|
typer.echo(f"{file.name}")
|
|
return
|
|
|
|
# Determine the filename to run
|
|
filename = f"euler_{num}.py"
|
|
if special:
|
|
filename = f"euler_{num}_{special}.py"
|
|
|
|
file_path = target_folder / filename
|
|
if not file_path.exists():
|
|
typer.echo(f"Solution file not found: {file_path}")
|
|
return
|
|
|
|
# Run the solution
|
|
runpy.run_path(file_path.resolve().as_posix(), run_name="__main__")
|
|
|
|
|
|
@app.command("version", help="Display the version of the projecteuler CLI.")
|
|
def version():
|
|
"""Display the version of the projecteuler CLI."""
|
|
typer.echo("projecteuler solution version 0.1.0")
|
|
|
|
|
|
def main():
|
|
app()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|