mirror of
https://github.com/flancian/garden.git
synced 2026-08-03 13:06:19 +00:00
104 lines
3.2 KiB
Python
Executable File
104 lines
3.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# https://click.palletsprojects.com/en/8.1.x/arguments/
|
|
# https://click.palletsprojects.com/en/8.1.x/options/
|
|
|
|
import click
|
|
import math
|
|
import sys
|
|
|
|
|
|
def get_centered_hex(k):
|
|
"""Returns the k-th centered hexagonal number (1-indexed)."""
|
|
if k < 1:
|
|
return None
|
|
return 3 * k * (k - 1) + 1
|
|
|
|
|
|
def check_centered_hex(n):
|
|
"""Checks if n is a centered hexagonal number.
|
|
Returns (is_hex, k) if it is, or (False, closest_k) if it isn't.
|
|
"""
|
|
if n < 1:
|
|
return False, 1
|
|
|
|
# Solve 3k^2 - 3k + 1 - n = 0
|
|
# k = (3 + sqrt(12n - 3)) / 6
|
|
d = 12 * n - 3
|
|
if d < 0:
|
|
return False, 1
|
|
|
|
s = math.isqrt(d)
|
|
k_approx = (3 + s) // 6
|
|
|
|
# Make sure we check k_approx and its neighbor to find the best fit
|
|
for k in (k_approx, k_approx + 1):
|
|
if get_centered_hex(k) == n:
|
|
return True, k
|
|
|
|
return False, k_approx
|
|
|
|
|
|
class AgoraCmd(click.Command):
|
|
def format_help(self, ctx, formatter):
|
|
click.echo("""Usage:
|
|
- Visit anagora.org/hex to execute this file in the Agora of Flancia.
|
|
- Visit e.g. anagora.org/hex/19 to test if 19 is a centered hexagonal number.
|
|
- In general visit anagora.org/foo, anagora.org/foo/bar to execute e.g. <bin/foo.py bar> from your garden.
|
|
""")
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
try:
|
|
return super(AgoraCmd, self).__call__(
|
|
*args, standalone_mode=False, **kwargs)
|
|
except click.MissingParameter as exc:
|
|
exc.ctx = None
|
|
exc.show(file=sys.stdout)
|
|
click.echo()
|
|
try:
|
|
super(AgoraCmd, self).__call__(['--help'])
|
|
except SystemExit:
|
|
sys.exit(exc.exit_code)
|
|
|
|
|
|
@click.command(cls=AgoraCmd)
|
|
@click.argument('n', type=click.INT)
|
|
def hexnum(n):
|
|
"""Checks if n is a centered hexagonal number and provides nearest neighbors."""
|
|
is_hex, k = check_centered_hex(n)
|
|
|
|
if is_hex:
|
|
click.echo(f"[[{n}]] is the centered hexagonal number #[[{k}]].")
|
|
else:
|
|
click.echo(f"[[{n}]] is *not* a centered hexagonal number.")
|
|
# Find neighbors
|
|
if n < 1:
|
|
h_next = get_centered_hex(1)
|
|
click.echo(f"The first centered hexagonal number is [[{h_next}]] (index #[[1]]).")
|
|
else:
|
|
h1 = get_centered_hex(k)
|
|
h2 = get_centered_hex(k + 1)
|
|
if h1 > n:
|
|
# k was overestimated or we need k-1
|
|
h2 = h1
|
|
k2 = k
|
|
k1 = max(1, k - 1)
|
|
h1 = get_centered_hex(k1)
|
|
else:
|
|
k1 = k
|
|
k2 = k + 1
|
|
|
|
if k1 == k2:
|
|
click.echo(f"The closest centered hexagonal number is [[{h1}]] (index #[[{k1}]]).")
|
|
else:
|
|
click.echo(f"The closest centered hexagonal numbers are [[{h1}]] (index #[[{k1}]]) and [[{h2}]] (index #[[{k2}]]).")
|
|
|
|
# Always show first 20 centered hexagonal numbers for context
|
|
first_20 = [str(get_centered_hex(i)) for i in range(1, 21)]
|
|
first_20_links = ", ".join(f"[[{x}]]" for x in first_20)
|
|
click.echo(f"\nFirst 20 centered hexagonal numbers: {first_20_links}.")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
hexnum()
|
|
|