You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
396 lines
9.8 KiB
396 lines
9.8 KiB
import copy
|
|
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 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.LinkedListNode | 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.LinkedListNode | None) -> stdlib.LinkedListNode | None:
|
|
by_val: list[tuple[int, stdlib.LinkedListNode]] = []
|
|
ret: stdlib.LinkedListNode | 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
|
|
|
|
|
|
def connect_binary_tree_right(
|
|
root: stdlib.ConnectableBinaryTreeNode | None,
|
|
) -> tuple[stdlib.ConnectableBinaryTreeNode | None, list[int | None]]:
|
|
if root is None:
|
|
return None, []
|
|
|
|
by_level = binary_tree_by_level(copy.deepcopy(root))
|
|
by_level = typing.cast(dict[int, list[stdlib.ConnectableBinaryTreeNode]], by_level)
|
|
serialized: list[int | None] = []
|
|
|
|
print("")
|
|
|
|
if 0 not in by_level or len(by_level[0]) == 0:
|
|
return None, []
|
|
|
|
connected_root = by_level[0][0]
|
|
|
|
for level, nodes in sorted(by_level.items(), key=lambda p: p[0]):
|
|
for i in range(len(nodes)):
|
|
serialized.append(nodes[i].val)
|
|
|
|
if len(nodes) > i + 1:
|
|
print(f"{'-' * level}> connecting {nodes[i].val} -> {nodes[i + 1].val}")
|
|
nodes[i].next = nodes[i + 1]
|
|
|
|
serialized.append(None)
|
|
|
|
return connected_root, serialized
|
|
|
|
|
|
def binary_tree_by_level(
|
|
root: stdlib.BinaryTreeNode,
|
|
) -> dict[int, list[stdlib.BinaryTreeNode]]:
|
|
combined: dict[int, list[stdlib.BinaryTreeNode]] = {}
|
|
|
|
for path in collect_binary_tree_levels(0, root):
|
|
level, node = path
|
|
combined.setdefault(level, [])
|
|
combined[level].insert(0, node)
|
|
|
|
return combined
|
|
|
|
|
|
def collect_binary_tree_levels(
|
|
level: int, node: stdlib.BinaryTreeNode | None
|
|
) -> typing.Iterator[tuple[int, stdlib.BinaryTreeNode]]:
|
|
if node is None:
|
|
return
|
|
|
|
yield (level, node)
|
|
yield from collect_binary_tree_levels(level + 1, node.right)
|
|
yield from collect_binary_tree_levels(level + 1, node.left)
|
|
|
|
|
|
def sum_binary_tree_path_ints(root: stdlib.BinaryTreeNode | None) -> int:
|
|
path_ints: list[int] = []
|
|
|
|
for path in collect_binary_tree_paths(root):
|
|
path_ints.append(int("".join([str(node.val) for node in path])))
|
|
|
|
return sum(path_ints)
|
|
|
|
|
|
def binary_tree_paths_as_lists(
|
|
paths: list[list[stdlib.BinaryTreeNode]],
|
|
) -> list[list[int]]:
|
|
paths_vals: list[list[int]] = []
|
|
|
|
for path in paths:
|
|
paths_vals.append([node.val for node in path])
|
|
|
|
return paths_vals
|
|
|
|
|
|
def collect_binary_tree_paths(
|
|
node: stdlib.BinaryTreeNode | None,
|
|
) -> typing.Iterator[list[stdlib.BinaryTreeNode]]:
|
|
if node is None:
|
|
return
|
|
|
|
if node.right is None and node.left is None:
|
|
yield [node]
|
|
return
|
|
|
|
if node.right is not None:
|
|
for path in collect_binary_tree_paths(node.right):
|
|
yield [node] + path
|
|
|
|
if node.left is not None:
|
|
for path in collect_binary_tree_paths(node.left):
|
|
yield [node] + path
|
|
|
|
|
|
def binary_tree_from_list(inlist: list[int | None]) -> stdlib.BinaryTreeNode | None:
|
|
if len(inlist) == 0:
|
|
return None
|
|
|
|
nodes: list[stdlib.BinaryTreeNode | None] = [
|
|
typing.cast(stdlib.BinaryTreeNode | None, stdlib.TreeNode.from_int(i))
|
|
for i in inlist
|
|
]
|
|
nodes_copy = nodes[::-1]
|
|
root = nodes_copy.pop()
|
|
|
|
for node in nodes:
|
|
if node is None:
|
|
continue
|
|
|
|
if len(nodes_copy) == 0:
|
|
break
|
|
|
|
node.left = nodes_copy.pop()
|
|
|
|
if len(nodes_copy) > 0:
|
|
node.right = nodes_copy.pop()
|
|
|
|
return root
|
|
|
|
|
|
def binary_tree_from_preorder_inorder(
|
|
preorder: list[int], inorder: list[int]
|
|
) -> stdlib.BinaryTreeNode | None:
|
|
preorder_reversed = preorder[::-1]
|
|
|
|
def subtree(left: list[int], right: list[int]) -> stdlib.BinaryTreeNode:
|
|
root: stdlib.BinaryTreeNode = typing.cast(
|
|
stdlib.BinaryTreeNode, stdlib.TreeNode(preorder_reversed.pop())
|
|
)
|
|
|
|
if len(left) > 1:
|
|
split_pos = left.index(preorder_reversed[-1])
|
|
root.left = subtree(left[:split_pos], left[split_pos + 1 :])
|
|
elif len(left) == 1:
|
|
preorder_reversed.remove(left[0])
|
|
root.left = typing.cast(stdlib.BinaryTreeNode, stdlib.TreeNode(left[0]))
|
|
|
|
if len(right) > 1:
|
|
split_pos = right.index(preorder_reversed[-1])
|
|
root.right = subtree(right[:split_pos], right[split_pos + 1 :])
|
|
elif len(right) == 1:
|
|
preorder_reversed.remove(right[0])
|
|
root.right = typing.cast(stdlib.BinaryTreeNode, stdlib.TreeNode(right[0]))
|
|
|
|
return root
|
|
|
|
split_pos = inorder.index(preorder[0])
|
|
|
|
return subtree(inorder[:split_pos], inorder[split_pos + 1 :])
|
|
|
|
|
|
class JumpSpace(typing.NamedTuple):
|
|
pos: int
|
|
val: int
|
|
moves: list["JumpSpace"]
|
|
|
|
@classmethod
|
|
def from_board(
|
|
cls, pos: int = 0, board: typing.Iterable[int] = ()
|
|
) -> typing.Optional["JumpSpace"]:
|
|
board = list(board)
|
|
|
|
if len(board) == 0:
|
|
return None
|
|
|
|
space = cls(pos, board[pos], [])
|
|
space.collect(board)
|
|
return space
|
|
|
|
def collect(self, board: list[int]) -> None:
|
|
del self.moves[:]
|
|
|
|
if self.pos > len(board) or len(board) == 0:
|
|
return
|
|
|
|
for n in range(self.pos + 1, self.pos + self.val + 1):
|
|
if n >= len(board):
|
|
break
|
|
|
|
self.moves.append(typing.cast(JumpSpace, JumpSpace.from_board(n, board)))
|
|
|
|
def jump_paths(self) -> list[list[int]]:
|
|
ret: list[list[int]] = [[self.pos]]
|
|
|
|
for next_space in self.moves:
|
|
for path in next_space.jump_paths():
|
|
ret.append([self.pos] + path)
|
|
|
|
return ret
|
|
|
|
|
|
def collect_complete_jump_paths_from_board(board: list[int]) -> list[list[int]]:
|
|
return [
|
|
p
|
|
for p in collect_jump_paths_from_board(board)
|
|
if len(p) > 0 and p[-1] >= len(board) - 1
|
|
]
|
|
|
|
|
|
def collect_jump_paths_from_board(board: list[int]) -> list[list[int]]:
|
|
space = JumpSpace.from_board(0, board)
|
|
if space is None:
|
|
return []
|
|
|
|
return space.jump_paths()
|
|
|
|
|
|
# NOTE: the expensive way goes like this
|
|
# complete_paths = collect_complete_jump_paths_from_board(board)
|
|
|
|
# if len(complete_paths) == 0:
|
|
# return -1
|
|
|
|
# return min([len(p) - 1 for p in complete_paths])
|
|
|
|
|
|
def count_min_jumps_from_board(board: list[int]) -> int:
|
|
return len(collect_min_jumps_from_board(board))
|
|
|
|
|
|
def collect_min_jumps_from_board(board: list[int]) -> list[int]:
|
|
if len(board) < 3:
|
|
return list(range(1, len(board)))
|
|
|
|
jumps: list[int] = []
|
|
range_begin: int = 0
|
|
val = board[range_begin]
|
|
range_end: int = range_begin + val + 1
|
|
|
|
while range_end < len(board):
|
|
potential_jumps = board[range_begin:range_end]
|
|
|
|
scored_jumps = [
|
|
(val + range_begin + i, val, range_begin + i)
|
|
for i, val in enumerate(potential_jumps)
|
|
]
|
|
_, val, space = max(scored_jumps)
|
|
|
|
jumps.append(space)
|
|
|
|
range_begin = space
|
|
range_end = range_begin + val + 1
|
|
|
|
return jumps + [len(board) - 1]
|
|
|
|
|
|
def h_index(citations: list[int]) -> int:
|
|
last_qualified = None
|
|
|
|
for i, citation_count in enumerate(list(sorted(citations, reverse=True))):
|
|
if citation_count >= i + 1:
|
|
last_qualified = i + 1
|
|
else:
|
|
break
|
|
|
|
return last_qualified or 0
|