|
|
|
import typing
|
|
|
|
|
|
|
|
import stdlib
|
|
|
|
|
|
|
|
|
|
|
|
def yep(s: str) -> bool:
|
|
|
|
return s.strip().lower().startswith("y")
|
|
|
|
|
|
|
|
|
|
|
|
def guess_bisect_repl(lower: int, upper: int) -> int:
|
|
|
|
mid = lower + ((upper - lower) // 2)
|
|
|
|
|
|
|
|
if yep(input(f"is it {mid}? ")):
|
|
|
|
return mid
|
|
|
|
|
|
|
|
if yep(input(f"higher than {mid}? ")):
|
|
|
|
return guess_bisect_repl(mid, upper)
|
|
|
|
|
|
|
|
return guess_bisect_repl(lower, mid)
|
|
|
|
|
|
|
|
|
|
|
|
def find_sqrt_ish(n: int) -> int:
|
|
|
|
return int(find_bisect(0, n, gen_sqrt_check(n)))
|
|
|
|
|
|
|
|
|
|
|
|
def gen_sqrt_check(n: int) -> typing.Callable[[float], int]:
|
|
|
|
def check(mid: float) -> int:
|
|
|
|
mid_sq: float = mid * mid
|
|
|
|
|
|
|
|
if mid_sq == n:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
if mid_sq < n:
|
|
|
|
return 1
|
|
|
|
|
|
|
|
return -1
|
|
|
|
|
|
|
|
return check
|
|
|
|
|
|
|
|
|
|
|
|
def find_bisect(
|
|
|
|
lower: float, upper: float, check: typing.Callable[[float], int]
|
|
|
|
) -> float:
|
|
|
|
mid: float = lower + ((upper - lower) / 2)
|
|
|
|
|
|
|
|
print(f"lower={lower} mid={mid} upper={upper}")
|
|
|
|
|
|
|
|
if mid == lower or mid == upper or check(mid) == 0:
|
|
|
|
return mid
|
|
|
|
|
|
|
|
if check(mid) == 1:
|
|
|
|
return find_bisect(mid, upper, check)
|
|
|
|
|
|
|
|
return find_bisect(lower, mid, check)
|
|
|
|
|
|
|
|
|
|
|
|
def cartesian_path(p0: tuple[int, int], p1: tuple[int, int]) -> list[tuple[int, int]]:
|
|
|
|
path: list[tuple[int, int]] = []
|
|
|
|
|
|
|
|
if p0 < p1:
|
|
|
|
for i in range(p0[1], p1[1]):
|
|
|
|
path.append((i, p0[0]))
|
|
|
|
|
|
|
|
for i in range(p0[0], p1[0]):
|
|
|
|
path.append((p1[1], i))
|
|
|
|
|
|
|
|
else:
|
|
|
|
for i in range(p0[1], p1[1] - 1, -1):
|
|
|
|
path.append((i, p0[0]))
|
|
|
|
|
|
|
|
for i in range(p0[0] - 1, p1[0], -1):
|
|
|
|
path.append((p1[1], i))
|
|
|
|
|
|
|
|
return path
|
|
|
|
|
|
|
|
|
|
|
|
def gen_matrix(width: int, height: int) -> list[list[int]]:
|
|
|
|
return [list(range(width)) for _ in range(height)]
|
|
|
|
|
|
|
|
|
|
|
|
class Roman:
|
|
|
|
SIMPLE = {
|
|
|
|
"I": 1,
|
|
|
|
"V": 5,
|
|
|
|
"X": 10,
|
|
|
|
"L": 50,
|
|
|
|
"C": 100,
|
|
|
|
"D": 500,
|
|
|
|
"M": 1000,
|
|
|
|
}
|
|
|
|
SIMPLE_REVERSE = {v: k for k, v in SIMPLE.items()}
|
|
|
|
COMPOUND = {
|
|
|
|
"IV": 4,
|
|
|
|
"IX": 9,
|
|
|
|
"XL": 40,
|
|
|
|
"XC": 90,
|
|
|
|
"CD": 400,
|
|
|
|
"CM": 900,
|
|
|
|
}
|
|
|
|
PREFIXES = {k[0] for k in COMPOUND.keys()}
|
|
|
|
COMPOUND_REVERSE = {v: k for k, v in COMPOUND.items()}
|
|
|
|
ALL = SIMPLE | COMPOUND
|
|
|
|
ALL_REVERSE = {v: k for k, v in ALL.items()}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def i2r(cls, i: int) -> str:
|
|
|
|
if i > 100_000:
|
|
|
|
raise ValueError(f"{i} is too silly")
|
|
|
|
|
|
|
|
r: list[str] = []
|
|
|
|
|
|
|
|
for int_val, roman_val in sorted(cls.ALL_REVERSE.items(), reverse=True):
|
|
|
|
remainder = i % int_val
|
|
|
|
|
|
|
|
r += [roman_val] * int((i - remainder) / int_val)
|
|
|
|
|
|
|
|
i = remainder
|
|
|
|
|
|
|
|
return "".join(r)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def r2i(cls, r: str) -> int:
|
|
|
|
total = 0
|
|
|
|
offset = 0
|
|
|
|
|
|
|
|
for i in range(len(r)):
|
|
|
|
if i + offset > len(r) - 1:
|
|
|
|
break
|
|
|
|
|
|
|
|
c = r[i + offset]
|
|
|
|
if (
|
|
|
|
c in cls.PREFIXES
|
|
|
|
and (i + offset + 1) < len(r)
|
|
|
|
and c + r[i + offset + 1] in cls.ALL
|
|
|
|
):
|
|
|
|
total += cls.ALL[c + r[i + offset + 1]]
|
|
|
|
offset += 1
|
|
|
|
continue
|
|
|
|
|
|
|
|
total += cls.ALL[c]
|
|
|
|
|
|
|
|
return total
|
|
|
|
|
|
|
|
|
|
|
|
class MinStack:
|
|
|
|
def __init__(self):
|
|
|
|
self._v: list[int] = []
|
|
|
|
self._min: list[int] = []
|
|
|
|
|
|
|
|
def push(self, val: int) -> None:
|
|
|
|
self._v.append(val)
|
|
|
|
self._min.append(min(val, self._min[-1] if self._min else val))
|
|
|
|
|
|
|
|
def pop(self) -> None:
|
|
|
|
self._v.pop(-1)
|
|
|
|
self._min.pop(-1)
|
|
|
|
|
|
|
|
def top(self) -> int:
|
|
|
|
return self._v[-1]
|
|
|
|
|
|
|
|
def getMin(self) -> int: # no qa
|
|
|
|
return self._min[-1]
|
|
|
|
|
|
|
|
|
|
|
|
def linked_list_to_list(head: stdlib.ListNode | None) -> list[int]:
|
|
|
|
seen: set[int] = set()
|
|
|
|
ret: list[int] = []
|
|
|
|
|
|
|
|
while head is not None:
|
|
|
|
if hash(head) in seen:
|
|
|
|
return ret
|
|
|
|
|
|
|
|
seen.add(hash(head))
|
|
|
|
ret.append(head.val)
|
|
|
|
head = head.next
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
def sort_linked_list(head: stdlib.ListNode | None) -> stdlib.ListNode | None:
|
|
|
|
by_val: list[tuple[int, stdlib.ListNode]] = []
|
|
|
|
ret: stdlib.ListNode | None = None
|
|
|
|
|
|
|
|
while head is not None:
|
|
|
|
by_val.append((head.val, head))
|
|
|
|
head = head.next
|
|
|
|
|
|
|
|
cur = ret
|
|
|
|
|
|
|
|
for _, node in sorted(by_val, key=lambda v: v[0]):
|
|
|
|
if cur is None:
|
|
|
|
cur = ret = node
|
|
|
|
continue
|
|
|
|
|
|
|
|
cur.next = node
|
|
|
|
cur = cur.next
|
|
|
|
|
|
|
|
if cur is not None:
|
|
|
|
cur.next = None
|
|
|
|
|
|
|
|
return ret
|