-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtemplate.py
More file actions
174 lines (130 loc) Β· 5.5 KB
/
Copy pathtemplate.py
File metadata and controls
174 lines (130 loc) Β· 5.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import functools
import heapq
import itertools
import os
import re
import string
import sys
import time
from collections import defaultdict, deque
from collections.abc import Iterable
from pprint import pprint
import networkx
import z3
from aoc_utils_runarmod import copy_answer, get_data, request_submit, write_solution
from more_itertools import (
collapse, # flatten all levels of nested iterables
consume, # exhaust an iterable
distinct_combinations, # distinct combinations of elements from an iterable
distinct_permutations, # distinct permutations of elements from an iterable
islice_extended, # extended slicing capabilities (islice with [::] and negative indices)
mark_ends, # mark the first and last element of an iterable ((is_first, is_last, val), (is_first, is_last, val), ...)
minmax, # find the minimum and maximum of an iterable (same as lambda x: (min(x), max(x)))
peekable, # peek ahead in an iterable
sliding_window, # return a sliding window of an iterable
time_limited, # time-limited execution of a function
)
def parseLine(line):
return line
def parseLines(lines):
return [parseLine(line) for line in lines]
def nums(line: str) -> tuple[int, ...]:
return tuple(map(int, re.findall(r"-?\d+", line)))
def numsNested(
data: str | list[str] | list[list[str]],
) -> tuple[int | tuple[int, ...], ...]:
if isinstance(data, str):
return nums(data)
if not hasattr(data, "__iter__"):
raise ValueError("Data must be a tuple/list/iterable or a string")
return tuple(e[0] if len(e) == 1 else e for e in filter(len, map(numsNested, data)))
class Solution:
def __init__(self, test=False):
self.test = test
data = get_data("CHANGE_YEAR", "CHANGE_DATE", test=test).strip("\n").split("\n")
self.data = numsNested(data)
# self.data = parseLines(data)
# self.data = list(map(list, self.data))
def part1(self):
return None
def part2(self):
return None
def main():
start = time.perf_counter()
test = Solution(test=True)
test1 = test.part1()
test2 = test.part2()
print(f"(TEST) Part 1: {test1}, \t{'correct :)' if test1 == None else 'wrong :('}")
print(f"(TEST) Part 2: {test2}, \t{'correct :)' if test2 == None else 'wrong :('}")
solution = Solution()
part1 = solution.part1()
part2 = solution.part2()
print(part1_text := f"Part 1: {part1}")
print(part2_text := f"Part 2: {part2}")
print(f"\nTotal time: {time.perf_counter() - start: .4f} sec")
copy_answer(part1, part2)
write_solution(os.path.dirname(os.path.realpath(__file__)), part1_text, part2_text)
request_submit("CHANGE_YEAR", "CHANGE_DATE", part1, part2)
def neighbors4(point: tuple[int, ...], jump=1):
for i in range(len(point)):
for diff in (-jump, jump):
yield point[:i] + (point[i] + diff,) + point[i + 1 :]
def neighbors4_inside(point: tuple[int, ...], ranges: tuple[range, ...], jump=1):
for i in range(len(point)):
for diff in (-jump, jump):
if point[i] + diff in ranges[i]:
yield point[:i] + (point[i] + diff,) + point[i + 1 :]
def neighbors8(point: tuple[int, ...], jump=1):
for diff in itertools.product((-jump, 0, jump), repeat=len(point)):
if any(diff):
yield tuple(point[i] + diff[i] for i in range(len(point)))
def neighbors8_inside(point: tuple[int, ...], ranges: tuple[range, ...], jump=1):
for diff in itertools.product((-jump, 0, jump), repeat=len(point)):
new_point = tuple(point[i] + diff[i] for i in range(len(point)))
if any(diff) and all(new_point[i] in ranges[i] for i in range(len(point))):
yield new_point
def manhattan(p1: tuple[int, ...], p2: tuple[int, ...]):
return sum(abs(a - b) for a, b in zip(p1, p2))
def get_close_points(
point: tuple[int, int], max_distance: int, include_self: bool = True
):
"""
Get all points within a certain manhattan distance from a point
"""
x, y = point
if include_self:
yield x, y
for distance in range(max_distance + 1):
for offset in range(distance):
invOffset = distance - offset
yield x + offset, y + invOffset
yield x + invOffset, y - offset
yield x - offset, y - invOffset
yield x - invOffset, y + offset
def networkx_from_grid[T](
grid: list[list[T]], passable_chars: set[T]
) -> networkx.Graph:
G = networkx.Graph()
w, h = len(grid[0]), len(grid)
for x, y in itertools.product(range(w), range(h)):
if grid[y][x] not in passable_chars:
continue
for nx, ny in neighbors4_inside((x, y), (range(w), range(h))):
if grid[ny][nx] not in passable_chars:
continue
G.add_edge((x, y), (nx, ny))
return G
def char_positions[T](grid: list[list[T]]) -> dict[T, set[tuple[int, int]]]:
w, h = len(grid[0]), len(grid)
positions = defaultdict(set)
for x, y in itertools.product(range(w), range(h)):
positions[grid[y][x]].add((x, y))
return positions
def get_first_position[T](grid: list[list[T]], target: T) -> tuple[int, int] | None:
w, h = len(grid[0]), len(grid)
for x, y in itertools.product(range(w), range(h)):
if grid[y][x] == target:
return (x, y)
return None
if __name__ == "__main__":
main()