✨ feat(main.py):添加CLI工具支持问题管理功能
📝 docs(README.md):更新项目说明并添加main.py使用指南 ⬆️ chore(pyproject.toml):添加typer依赖以支持CLI功能 ✅ test(solutions):添加0036号问题的解决方案文件
This commit is contained in:
11
README.md
11
README.md
@@ -3,12 +3,21 @@
|
|||||||
主要记录一下解决 [Project Euler](https://projecteuler.net/) 问题的方法,自娱自乐成分较大,
|
主要记录一下解决 [Project Euler](https://projecteuler.net/) 问题的方法,自娱自乐成分较大,
|
||||||
也会有一些新奇的想法。主要使用 Python 解决,也会有 Haskell 、 Rust 或者 Lean4 的解决方法。
|
也会有一些新奇的想法。主要使用 Python 解决,也会有 Haskell 、 Rust 或者 Lean4 的解决方法。
|
||||||
|
|
||||||
项目使用 `uv` 进行管理,毕竟主要还是使用 Python来写的。
|
项目使用 `uv` 进行管理,毕竟绝大多数问题都是Python来写的解决方法。
|
||||||
|
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
||||||
**KEY POINT :**
|
**KEY POINT :**
|
||||||
|
|
||||||
要把数学回归数学,而不是回归程序。
|
要把数学回归数学,而不是回归程序。
|
||||||
从数学角度去考虑问题,用数学化简计算逻辑。
|
从数学角度去考虑问题,用数学化简计算逻辑。
|
||||||
|
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
**`main.py` 的使用**
|
||||||
|
|
||||||
|
为了便于管理和使用,简单创建了一个脚本。
|
||||||
|
这个脚本只有三个主要功能,一个是创建新问题的文件,一个是列出已创建问题,还有一个是运行指定问题的python解法。
|
||||||
|
|||||||
68
main.py
68
main.py
@@ -1,6 +1,68 @@
|
|||||||
def main():
|
import runpy
|
||||||
print("Hello from projecteuler!")
|
from pathlib import Path
|
||||||
|
from profile import run
|
||||||
|
from tarfile import SOLARIS_XHDTYPE
|
||||||
|
|
||||||
|
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)
|
||||||
|
Path(f"solutions/{title}/euler_{num}.py").touch()
|
||||||
|
|
||||||
|
|
||||||
|
@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) -> None:
|
||||||
|
folders = list(Path("solutions").iterdir())
|
||||||
|
folders = [
|
||||||
|
folder
|
||||||
|
for folder in folders
|
||||||
|
if folder.is_dir() and folder.name.startswith(f"{num:04d}")
|
||||||
|
]
|
||||||
|
runpy.run_path(
|
||||||
|
(folders[0] / f"euler_{num}.py").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")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
app()
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ dependencies = [
|
|||||||
"mplusa>=0.0.3",
|
"mplusa>=0.0.3",
|
||||||
"numpy>=2.3.5",
|
"numpy>=2.3.5",
|
||||||
"sympy>=1.14.0",
|
"sympy>=1.14.0",
|
||||||
|
"typer-slim[standard]>=0.21.0",
|
||||||
]
|
]
|
||||||
|
|||||||
74
solutions/0036.Palindromes/euler_36.py
Normal file
74
solutions/0036.Palindromes/euler_36.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
"""
|
||||||
|
The decimal number, 585 = 1001001001_2 (binary), is palindromic in both bases.
|
||||||
|
|
||||||
|
Find the sum of all numbers, less than one million, which are palindromic in base 10 and base 2.
|
||||||
|
|
||||||
|
(Please note that the palindromic number, in either base, may not include leading zeros.)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def timer(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
start_time = time.time()
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
end_time = time.time()
|
||||||
|
print(f"{func.__name__} took time: {end_time - start_time:.6f} seconds")
|
||||||
|
return result
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def is_palindromic(num: int) -> bool:
|
||||||
|
return str(num) == str(num)[::-1]
|
||||||
|
|
||||||
|
|
||||||
|
@timer
|
||||||
|
def main_simple(limit: int = 1000000) -> None:
|
||||||
|
res = []
|
||||||
|
for num in range(0, limit + 1):
|
||||||
|
if is_palindromic(num):
|
||||||
|
if is_palindromic(int(bin(num)[2:])):
|
||||||
|
res.append(num)
|
||||||
|
print(sum(res))
|
||||||
|
|
||||||
|
|
||||||
|
def palindromes_of_length(length: int) -> list[int]:
|
||||||
|
"""生成所有指定位数的回文数"""
|
||||||
|
if length == 1:
|
||||||
|
return list(range(10))
|
||||||
|
|
||||||
|
result = []
|
||||||
|
# 前半部分的起始和结束
|
||||||
|
start = 10 ** ((length - 1) // 2)
|
||||||
|
end = 10 ** ((length + 1) // 2)
|
||||||
|
|
||||||
|
for half in range(start, end):
|
||||||
|
s = str(half)
|
||||||
|
if length % 2 == 0:
|
||||||
|
# 偶数位:123 → 123321
|
||||||
|
palindrome = s + s[::-1]
|
||||||
|
else:
|
||||||
|
# 奇数位:123 → 12321
|
||||||
|
palindrome = s + s[-2::-1]
|
||||||
|
result.append(int(palindrome))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@timer
|
||||||
|
def main_quick(limit: int = 1000000) -> None:
|
||||||
|
res = []
|
||||||
|
length = len(str(limit - 1))
|
||||||
|
for num in range(1, length + 1):
|
||||||
|
for palindrome in palindromes_of_length(num):
|
||||||
|
if is_palindromic(int(bin(palindrome)[2:])):
|
||||||
|
res.append(palindrome)
|
||||||
|
print(sum(res))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
limit = 1000000
|
||||||
|
main_simple(limit)
|
||||||
|
main_quick(limit)
|
||||||
103
uv.lock
generated
103
uv.lock
generated
@@ -2,6 +2,48 @@ version = 1
|
|||||||
revision = 3
|
revision = 3
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown-it-py"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "mdurl" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mdurl"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mplusa"
|
name = "mplusa"
|
||||||
version = "0.0.3"
|
version = "0.0.3"
|
||||||
@@ -91,6 +133,7 @@ dependencies = [
|
|||||||
{ name = "mplusa" },
|
{ name = "mplusa" },
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
{ name = "sympy" },
|
{ name = "sympy" },
|
||||||
|
{ name = "typer-slim", extra = ["standard"] },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
@@ -98,6 +141,38 @@ requires-dist = [
|
|||||||
{ name = "mplusa", specifier = ">=0.0.3" },
|
{ name = "mplusa", specifier = ">=0.0.3" },
|
||||||
{ name = "numpy", specifier = ">=2.3.5" },
|
{ name = "numpy", specifier = ">=2.3.5" },
|
||||||
{ name = "sympy", specifier = ">=1.14.0" },
|
{ name = "sympy", specifier = ">=1.14.0" },
|
||||||
|
{ name = "typer-slim", extras = ["standard"], specifier = ">=0.21.0" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.19.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rich"
|
||||||
|
version = "14.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "markdown-it-py" },
|
||||||
|
{ name = "pygments" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shellingham"
|
||||||
|
version = "1.5.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -111,3 +186,31 @@ sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2
|
|||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
|
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typer-slim"
|
||||||
|
version = "0.21.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "click" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f9/3b/2f60ce16f578b1db5b8816d37d6a4d9786b33b76407fc8c13b0b86312c31/typer_slim-0.21.0.tar.gz", hash = "sha256:f2dbd150cfa0fead2242e21fa9f654dfc64773763ddf07c6be9a49ad34f79557", size = 106841, upload-time = "2025-12-25T09:54:55.998Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/84/e97abf10e4a699194ff07fd586ec7f4cf867d9d04bead559a65f9e7aff84/typer_slim-0.21.0-py3-none-any.whl", hash = "sha256:92aee2188ac6fc2b2924bd75bb61a340b78bd8cd51fd9735533ce5a856812c8e", size = 47174, upload-time = "2025-12-25T09:54:54.609Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
standard = [
|
||||||
|
{ name = "rich" },
|
||||||
|
{ name = "shellingham" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.15.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||||
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user