Compare commits
6 Commits
ee96c1336f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ceb67a7fd | |||
| 40ac031eb4 | |||
| 0d1eff4f00 | |||
| cd00f46b77 | |||
| 217e770a25 | |||
| f132355ad5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
|
Pipfile
|
||||||
__pycache__
|
__pycache__
|
||||||
*.txt
|
*.txt
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -2,8 +2,9 @@ My solutions to the Advent of Code 2023 programming challenges.
|
|||||||
|
|
||||||
Thanks to Eric Wastl for creating this enjoyable event.
|
Thanks to Eric Wastl for creating this enjoyable event.
|
||||||
|
|
||||||
Requires `lib.py` from [aocpy](https://git.felixm.de/felixm/aocpy) repository.
|
- Requires `lib.py` from [aocpy](https://git.felixm.de/felixm/aocpy) repository.
|
||||||
|
- Requires `sympy` for day 24.
|
||||||
|
- Requires `matplotlib` and `networkx` for hands-on day 25.
|
||||||
|
|
||||||
# Times
|
# Times
|
||||||
|
|
||||||
@@ -38,8 +39,15 @@ Requires `lib.py` from [aocpy](https://git.felixm.de/felixm/aocpy) repository.
|
|||||||
the input conjunction gate pretty early, but then messed up the
|
the input conjunction gate pretty early, but then messed up the
|
||||||
implementation and thought it wasn't gonna work. Spent a half day thinking up
|
implementation and thought it wasn't gonna work. Spent a half day thinking up
|
||||||
something else before returning to the idea and it worked flawlessly.
|
something else before returning to the idea and it worked flawlessly.
|
||||||
- Day 21:
|
- Day 21: Part 1 was straightforward, but part 2 maybe the hardest problem this
|
||||||
|
year.
|
||||||
- Day 22: Not too hard, but definitely way too slow for leaderboard.
|
- Day 22: Not too hard, but definitely way too slow for leaderboard.
|
||||||
- Day 23:
|
- Day 23: I found this fun because it required some creativity for part 2. Slow
|
||||||
- Day 24:
|
af, of course.
|
||||||
- Day 25:
|
- Day 24: Solve problem with sympy. I first used numpy to solve part 1 and it
|
||||||
|
was much faster than using sympy, but I lost that solution when switching to
|
||||||
|
sympy. Takes about three minutes to run for part 1 and then part 2 is under a
|
||||||
|
second.
|
||||||
|
- Day 25: I cheeky solved this by plotting the graph and manually removing the
|
||||||
|
nodes. I should probably try to write an algorith that does that, but meh.
|
||||||
|
Manually plotting requires matplotlib and networkx packages.
|
||||||
|
|||||||
192
d21.py
192
d21.py
@@ -1,7 +1,7 @@
|
|||||||
from lib import *
|
from lib import *
|
||||||
|
import os
|
||||||
|
|
||||||
EXAMPLE = """
|
EXAMPLE = """...........
|
||||||
...........
|
|
||||||
.....###.#.
|
.....###.#.
|
||||||
.###.##..#.
|
.###.##..#.
|
||||||
..#.#...#..
|
..#.#...#..
|
||||||
@@ -14,13 +14,11 @@ EXAMPLE = """
|
|||||||
...........
|
...........
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def solve(i: Input, second=False):
|
def solve(input: Input):
|
||||||
res = 0
|
g = input.grid2()
|
||||||
g = i.grid2()
|
|
||||||
s = g.find('S')[0]
|
s = g.find('S')[0]
|
||||||
g[s] = 'O'
|
g[s] = 'O'
|
||||||
# steps = 64
|
steps = 64
|
||||||
steps = 26501365
|
|
||||||
seen = set()
|
seen = set()
|
||||||
for i in range(steps):
|
for i in range(steps):
|
||||||
os = tuple(g.find('O'))
|
os = tuple(g.find('O'))
|
||||||
@@ -38,18 +36,178 @@ def solve(i: Input, second=False):
|
|||||||
g[nb] = 'O'
|
g[nb] = 'O'
|
||||||
return len(g.find('O'))
|
return len(g.find('O'))
|
||||||
|
|
||||||
|
|
||||||
|
def plot(xs, poss):
|
||||||
|
os.system("clear")
|
||||||
|
rcoords = [x[0] for x in xs]
|
||||||
|
ccoords = [x[1] for x in xs]
|
||||||
|
rmin = min(rcoords)
|
||||||
|
rmax = max(rcoords)
|
||||||
|
cmin = min(ccoords)
|
||||||
|
cmax = max(ccoords)
|
||||||
|
for r in range(rmin, rmax + 1):
|
||||||
|
s = ""
|
||||||
|
for c in range(cmin, cmax + 1):
|
||||||
|
if (r, c) in xs:
|
||||||
|
s += "#"
|
||||||
|
elif (r, c) in poss:
|
||||||
|
s += "O"
|
||||||
|
else:
|
||||||
|
s += " "
|
||||||
|
print(s)
|
||||||
|
|
||||||
|
def move(xs, roff, coff):
|
||||||
|
rcoords = [x[0] for x in xs]
|
||||||
|
ccoords = [x[1] for x in xs]
|
||||||
|
rd = max(rcoords) - min(rcoords) + 3
|
||||||
|
cd = max(ccoords) - min(ccoords) + 3
|
||||||
|
newxs = [(x[0] + roff * rd, x[1] + coff * cd) for x in xs]
|
||||||
|
return set(newxs)
|
||||||
|
|
||||||
|
def iter(poss, stones):
|
||||||
|
nposs = set()
|
||||||
|
for r, c in poss:
|
||||||
|
for ro, co in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
|
||||||
|
nr, nc = r + ro, c + co
|
||||||
|
if not (nr, nc) in stones:
|
||||||
|
nposs.add((nr, nc))
|
||||||
|
return nposs
|
||||||
|
|
||||||
|
def get_bounds(size, ro, co):
|
||||||
|
rmin = size * ro
|
||||||
|
rmax = size + size * ro
|
||||||
|
cmin = size * co
|
||||||
|
cmax = size + size * co
|
||||||
|
return rmin, rmax, cmin, cmax
|
||||||
|
|
||||||
|
def count(poss, size, ro, co):
|
||||||
|
rmin, rmax, cmin, cmax = get_bounds(size, ro, co)
|
||||||
|
res = 0
|
||||||
|
for (r, c) in poss:
|
||||||
|
if (rmin <= r < rmax) and (cmin <= c < cmax):
|
||||||
|
res += 1
|
||||||
|
return res
|
||||||
|
|
||||||
|
def solve2(ip: Input):
|
||||||
|
base_stones = set()
|
||||||
|
poss = set()
|
||||||
|
|
||||||
|
size = len(ip.lines())
|
||||||
|
assert size == len(ip.lines()[0])
|
||||||
|
|
||||||
|
for r, row in enumerate(ip.lines()):
|
||||||
|
for c, col in enumerate(row):
|
||||||
|
if col == "#":
|
||||||
|
base_stones.add((r, c))
|
||||||
|
if col == "S":
|
||||||
|
poss.add((r, c))
|
||||||
|
|
||||||
|
stones = base_stones.copy()
|
||||||
|
|
||||||
|
off = 19 // 2
|
||||||
|
for ro in range(-off, off + 1):
|
||||||
|
for co in range(-off, off + 1):
|
||||||
|
stones |= move(base_stones, ro, co)
|
||||||
|
|
||||||
|
|
||||||
|
hists = {}
|
||||||
|
for ro in range(-off, off + 1):
|
||||||
|
for co in range(-off, off + 1):
|
||||||
|
hists[(ro, co)] = []
|
||||||
|
|
||||||
|
#for step in range(590):
|
||||||
|
# if step % 1 == 0:
|
||||||
|
# sanity = 0
|
||||||
|
# os.system("clear")
|
||||||
|
# for ro in range(-off, off + 1):
|
||||||
|
# s = ""
|
||||||
|
# for co in range(-off, off + 1):
|
||||||
|
# v = count(poss, size, ro, co)
|
||||||
|
# sanity += v
|
||||||
|
# if v > 0:
|
||||||
|
# hists[(ro, co)].append(v)
|
||||||
|
# s += f"{v:6}"
|
||||||
|
# else:
|
||||||
|
# s += 6 * " "
|
||||||
|
# print(s)
|
||||||
|
# # input(f"{step=} {step//size=} {len(poss)} ({sanity}) cont...")
|
||||||
|
# print(f"{step=} {step//size=} {len(poss)} ({sanity}) cont...")
|
||||||
|
# poss = iter(poss, stones)
|
||||||
|
|
||||||
|
# 66, 197, 328 459 # cycle starts
|
||||||
|
# 196, 327, 458, 589 # targets
|
||||||
|
def calc(len, xs):
|
||||||
|
if len % 2 == 0:
|
||||||
|
return len // 2 * sum(xs)
|
||||||
|
else:
|
||||||
|
return len // 2 * sum(xs) + xs[0]
|
||||||
|
|
||||||
|
target = 196
|
||||||
|
target = 327
|
||||||
|
target = 458
|
||||||
|
target = 589
|
||||||
|
target = 26501365
|
||||||
|
# for target in [196, 327, 458, 589]:
|
||||||
|
print()
|
||||||
|
print(target)
|
||||||
|
cycle = 131
|
||||||
|
c = target // cycle
|
||||||
|
d = (target // cycle) * 2 + 1 - 2
|
||||||
|
print(f"{c=} {d=}")
|
||||||
|
res = 0
|
||||||
|
res += 5698 + 5703 + 5709 + 5704 # corners
|
||||||
|
res += c * 964 + c * 984 + c * 968 + c * 978 # outer
|
||||||
|
res += (c - 1) * 6637 + (c - 1) * 6624 + (c - 1) * 6643 + (c - 1) * 6619 # inner
|
||||||
|
|
||||||
|
for i in range(d, 0, -2):
|
||||||
|
res += calc(i, [7623, 7558])
|
||||||
|
|
||||||
|
for i in range(d - 2, 0, -2):
|
||||||
|
res += calc(i, [7623, 7558])
|
||||||
|
|
||||||
|
print(res)
|
||||||
|
return res
|
||||||
|
|
||||||
|
# def get_till(xs, ts):
|
||||||
|
# ts = ts[:]
|
||||||
|
# r = []
|
||||||
|
# for x in xs:
|
||||||
|
# r.append(x)
|
||||||
|
# if x in ts:
|
||||||
|
# ts.remove(x)
|
||||||
|
# if ts == []:
|
||||||
|
# break
|
||||||
|
# return r
|
||||||
|
|
||||||
|
osz_values = hists[(0, 4)][-2:]
|
||||||
|
# se = get_till(hists[0, 5], osz_values)
|
||||||
|
# sn = get_till(hists[-5, 0], osz_values)
|
||||||
|
# ss = get_till(hists[5, 0], osz_values)
|
||||||
|
# sw = get_till(hists[0, -5], osz_values)
|
||||||
|
# print(se)
|
||||||
|
# print(sn)
|
||||||
|
# print(sw)
|
||||||
|
# print(ss)
|
||||||
|
|
||||||
|
# sne = get_till(hists[-5, 5], osz_values)
|
||||||
|
# sse = get_till(hists[5, 5], osz_values)
|
||||||
|
# ssw = get_till(hists[5, -5], osz_values)
|
||||||
|
# snw = get_till(hists[-5, -5], osz_values)
|
||||||
|
# print(sne)
|
||||||
|
# print(sse)
|
||||||
|
# print(ssw)
|
||||||
|
# print(snw)
|
||||||
|
|
||||||
|
# for i in range(3, 10):
|
||||||
|
# print(hists[(0, i)][:5])
|
||||||
|
# print(hists[(0, -i)][:5])
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
DAY_INPUT = "i21.txt"
|
DAY_INPUT = "i21.txt"
|
||||||
|
# print("Example 1:", solve(Input(EXAMPLE)))
|
||||||
print("Example 1:", solve(Input(EXAMPLE)))
|
# print("Solution 1:", solve(Input(DAY_INPUT)))
|
||||||
print("Solution 1:", solve(Input(DAY_INPUT)))
|
# print("Example 2:", solve2(Input(EXAMPLE)))
|
||||||
return
|
print("Solution 2:", solve2(Input(DAY_INPUT)))
|
||||||
|
|
||||||
print("Example 2:", solve(Input(EXAMPLE), True))
|
|
||||||
return
|
|
||||||
|
|
||||||
print("Solution 2:", solve(Input(DAY_INPUT), True))
|
|
||||||
return
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
146
d23.py
Normal file
146
d23.py
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
from lib import *
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
EXAMPLE = """#.#####################
|
||||||
|
#.......#########...###
|
||||||
|
#######.#########.#.###
|
||||||
|
###.....#.>.>.###.#.###
|
||||||
|
###v#####.#v#.###.#.###
|
||||||
|
###.>...#.#.#.....#...#
|
||||||
|
###v###.#.#.#########.#
|
||||||
|
###...#.#.#.......#...#
|
||||||
|
#####.#.#.#######.#.###
|
||||||
|
#.....#.#.#.......#...#
|
||||||
|
#.#####.#.#.#########v#
|
||||||
|
#.#...#...#...###...>.#
|
||||||
|
#.#.#v#######v###.###v#
|
||||||
|
#...#.>.#...>.>.#.###.#
|
||||||
|
#####v#.#.###v#.#.###.#
|
||||||
|
#.....#...#...#.#.#...#
|
||||||
|
#.#########.###.#.#.###
|
||||||
|
#...###...#...#...#.###
|
||||||
|
###.###.#.###v#####v###
|
||||||
|
#...#...#.#.>.>.#.>.###
|
||||||
|
#.###.###.#.###.#.#v###
|
||||||
|
#.....###...###...#...#
|
||||||
|
#####################.#
|
||||||
|
"""
|
||||||
|
|
||||||
|
SLOPES = {
|
||||||
|
"^": (-1, 0),
|
||||||
|
">": (0, 1),
|
||||||
|
"v": (1, 0),
|
||||||
|
"<": (0, -1),
|
||||||
|
}
|
||||||
|
|
||||||
|
def first(input):
|
||||||
|
g = input.grid2()
|
||||||
|
start = (0, 1)
|
||||||
|
end = (g.n_rows - 1, g.n_cols - 2)
|
||||||
|
longest = 0
|
||||||
|
paths = [(set([start]), start)]
|
||||||
|
while True:
|
||||||
|
new_paths = []
|
||||||
|
for p in paths:
|
||||||
|
hist, pos = p
|
||||||
|
for d in g.COORDS_ORTH:
|
||||||
|
nb = add2(pos, d)
|
||||||
|
if nb[0] < 0 or nb[0] >= g.n_rows or nb[1] < 0 or nb[1] >= g.n_cols:
|
||||||
|
continue
|
||||||
|
c = g[nb]
|
||||||
|
if c in SLOPES.keys() and d != SLOPES[c]:
|
||||||
|
continue
|
||||||
|
if c == "#" or nb in hist:
|
||||||
|
continue
|
||||||
|
if nb == end:
|
||||||
|
l = len(hist)
|
||||||
|
if l > longest:
|
||||||
|
longest = l
|
||||||
|
continue
|
||||||
|
nhist = hist.copy()
|
||||||
|
nhist.add(nb)
|
||||||
|
new_paths.append((nhist, nb))
|
||||||
|
paths = new_paths
|
||||||
|
if len(paths) == 0:
|
||||||
|
break
|
||||||
|
return longest
|
||||||
|
|
||||||
|
def solve(input: Input, second=False):
|
||||||
|
if not second:
|
||||||
|
return first(input)
|
||||||
|
|
||||||
|
g = input.grid2()
|
||||||
|
start = (0, 1)
|
||||||
|
end = (g.n_rows - 1, g.n_cols - 2)
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
q = deque([[start, (1, 1)]])
|
||||||
|
|
||||||
|
# The intuition is that we can brute force much quicker if we have a pure
|
||||||
|
# graph instead of following the maze along the whole time. So, we create
|
||||||
|
# a graph from the maze and then brute force on the maze.
|
||||||
|
sg = {start: set()} # {node: {(node, dist), ...}}
|
||||||
|
|
||||||
|
while q:
|
||||||
|
trail = q.popleft()
|
||||||
|
pos = trail[-1]
|
||||||
|
while True:
|
||||||
|
nbs = []
|
||||||
|
for d in g.COORDS_ORTH:
|
||||||
|
nb = add2(pos, d)
|
||||||
|
if nb[0] < 0 or nb[0] >= g.n_rows or nb[1] < 0 or nb[1] >= g.n_cols:
|
||||||
|
continue
|
||||||
|
if g[nb] == "#" or nb == trail[-2]:
|
||||||
|
continue
|
||||||
|
nbs.append(nb)
|
||||||
|
if len(nbs) == 1:
|
||||||
|
pos = nbs[0]
|
||||||
|
trail.append(pos)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not pos in sg:
|
||||||
|
sg[pos] = set()
|
||||||
|
|
||||||
|
dist = len(trail) - 1
|
||||||
|
sg[trail[0]].add((pos, dist))
|
||||||
|
sg[pos].add((trail[0], dist))
|
||||||
|
|
||||||
|
seen.add(pos)
|
||||||
|
for nb in nbs:
|
||||||
|
if not nb in seen:
|
||||||
|
seen.add(nb)
|
||||||
|
q.append([pos, nb])
|
||||||
|
|
||||||
|
# for key, value in sg.items():
|
||||||
|
# print(key, value)
|
||||||
|
|
||||||
|
# Brute force in bf order.
|
||||||
|
longest = 0
|
||||||
|
q = deque([(set(), start, 0)])
|
||||||
|
while q:
|
||||||
|
hist, pos, dist = q.popleft()
|
||||||
|
if pos == end:
|
||||||
|
if dist > longest:
|
||||||
|
longest = dist
|
||||||
|
continue
|
||||||
|
|
||||||
|
for nb, d in sg[pos]:
|
||||||
|
if nb in hist:
|
||||||
|
continue
|
||||||
|
nhist = hist.copy()
|
||||||
|
nhist.add(nb)
|
||||||
|
q.append((nhist, nb, dist + d))
|
||||||
|
|
||||||
|
return longest
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
DAY_INPUT = "i23.txt"
|
||||||
|
# print("Example 1:", solve(Input(EXAMPLE)))
|
||||||
|
# print("Solution 1:", solve(Input(DAY_INPUT)))
|
||||||
|
print("Example 2:", solve(Input(EXAMPLE), True))
|
||||||
|
print("Solution 2:", solve(Input(DAY_INPUT), True))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
74
d24.py
Normal file
74
d24.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
from lib import *
|
||||||
|
import sympy as sp
|
||||||
|
|
||||||
|
EXAMPLE = """19, 13, 30 @ -2, 1, -2
|
||||||
|
18, 19, 22 @ -1, -1, -2
|
||||||
|
20, 25, 34 @ -2, -2, -4
|
||||||
|
12, 31, 28 @ -1, -2, -1
|
||||||
|
20, 19, 15 @ 1, -5, -3
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def solve1(input: Input):
|
||||||
|
if len(input.lines()) == 5:
|
||||||
|
lb = 7
|
||||||
|
ub = 27
|
||||||
|
else:
|
||||||
|
lb = 200000000000000
|
||||||
|
ub = 400000000000000
|
||||||
|
|
||||||
|
# On paper:
|
||||||
|
# (px - sx1) / vx1 = (py - sy1) / vy1
|
||||||
|
# (px - sx1) * vy1 = (py - sy1) * vx1
|
||||||
|
# (px - sx1) * vy1 - (py - sy1) * vx1 = 0
|
||||||
|
res = 0
|
||||||
|
eqs = [str_to_ints(l) for l in input.lines()]
|
||||||
|
for i, eq1 in enumerate(eqs):
|
||||||
|
for eq2 in eqs[:i]:
|
||||||
|
sx1, sy1, _, vx1, vy1, _ = eq1
|
||||||
|
sx2, sy2, _, vx2, vy2, _ = eq2
|
||||||
|
|
||||||
|
px, py = sp.symbols("px py")
|
||||||
|
es = [
|
||||||
|
vy1 * (px - sx1) - vx1 * (py - sy1),
|
||||||
|
vy2 * (px - sx2) - vx2 * (py - sy2),
|
||||||
|
]
|
||||||
|
r = sp.solve(es)
|
||||||
|
if not r:
|
||||||
|
continue
|
||||||
|
|
||||||
|
x, y = r[px], r[py]
|
||||||
|
if lb <= x <= ub and lb <= y < ub:
|
||||||
|
t1 = (x - sx1) / vx1
|
||||||
|
t2 = (x - sx2) / vx2
|
||||||
|
if (t1 > 0 and t2 > 0):
|
||||||
|
res += 1
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def solve2(input: Input):
|
||||||
|
eqs = [str_to_ints(l) for l in input.lines()]
|
||||||
|
px, py, pz, vxo, vyo, vzo = sp.symbols("px py pz vxo vyo vzo")
|
||||||
|
es = []
|
||||||
|
|
||||||
|
# The first six equations are enough to find a solution for my problem set.
|
||||||
|
# Might have to be increased depending on input.
|
||||||
|
for i, (x, y, z, vx, vy, vz) in enumerate(eqs[:6]):
|
||||||
|
t = sp.symbols(f"t{i}")
|
||||||
|
es.append(px + vxo * t - x - vx * t)
|
||||||
|
es.append(py + vyo * t - y - vy * t)
|
||||||
|
es.append(pz + vzo * t - z - vz * t)
|
||||||
|
r = sp.solve(es)[0]
|
||||||
|
return r[px] + r[py] + r[pz]
|
||||||
|
|
||||||
|
def main():
|
||||||
|
DAY_INPUT = "i24.txt"
|
||||||
|
print("Solution 1:", solve1(Input(EXAMPLE)))
|
||||||
|
print("Solution 1:", solve1(Input(DAY_INPUT)))
|
||||||
|
print("Example 2:", solve2(Input(EXAMPLE)))
|
||||||
|
print("Solution 2:", solve2(Input(DAY_INPUT)))
|
||||||
|
return
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
87
d25.py
Normal file
87
d25.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
from lib import *
|
||||||
|
from random import choice
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
|
||||||
|
# def plot(graph):
|
||||||
|
# import networkx as nx
|
||||||
|
# import matplotlib
|
||||||
|
# import matplotlib.pyplot as plt
|
||||||
|
# G = nx.Graph()
|
||||||
|
# for node, connected_nodes in graph.items():
|
||||||
|
# for connected_node in connected_nodes:
|
||||||
|
# G.add_edge(node, connected_node)
|
||||||
|
# # pos = nx.spring_layout(G, k=2.0, iterations=20) # Adjust k as needed
|
||||||
|
# pos = nx.shell_layout(G)
|
||||||
|
# nx.draw(G, with_labels=True, node_color='lightblue', edge_color='gray', node_size=2000, font_size=15, font_weight='bold')
|
||||||
|
# matplotlib.use('qtagg')
|
||||||
|
# plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
def solve(input: Input):
|
||||||
|
graph = {}
|
||||||
|
edges = {}
|
||||||
|
|
||||||
|
for line in input.lines():
|
||||||
|
src, dsts = line.split(":")
|
||||||
|
dsts = dsts.strip().split(" ")
|
||||||
|
|
||||||
|
if not src in graph:
|
||||||
|
graph[src] = []
|
||||||
|
|
||||||
|
for dst in dsts:
|
||||||
|
graph[src].append(dst)
|
||||||
|
if not dst in graph:
|
||||||
|
graph[dst] = []
|
||||||
|
graph[dst].append(src)
|
||||||
|
|
||||||
|
edge = tuple(sorted([src, dst]))
|
||||||
|
edges[edge] = 0
|
||||||
|
|
||||||
|
for _ in range(100):
|
||||||
|
first_node = choice(list(graph.keys()))
|
||||||
|
seen = set([first_node])
|
||||||
|
visit = deque([first_node])
|
||||||
|
while visit:
|
||||||
|
node = visit.popleft()
|
||||||
|
for nb in graph[node]:
|
||||||
|
if not nb in seen:
|
||||||
|
seen.add(nb)
|
||||||
|
visit.append(nb)
|
||||||
|
edge = tuple(sorted([node, nb]))
|
||||||
|
edges[edge] += 1
|
||||||
|
|
||||||
|
# Orignally, I used `plot(graph)` to visually find the nodes that have to
|
||||||
|
# be removed. I then came up with this heuristic approach. The idea is that
|
||||||
|
# we have to cross one of the three nodes when we do a breadth first
|
||||||
|
# search. By repeatedly doing that we can identify the "bridges" as the
|
||||||
|
# three edges that are used the most often.
|
||||||
|
most_visited = sorted(edges.items(), key=lambda t: t[1], reverse=True)[:3]
|
||||||
|
|
||||||
|
# to_remove = (("plt", "mgb"), ("jxm", "qns"), ("dbt", "tjd")) # found visually
|
||||||
|
# for node, count in most_visited:
|
||||||
|
# print(node, count) # should print the same as `to_remove`
|
||||||
|
|
||||||
|
for (a, b), _ in most_visited:
|
||||||
|
graph[a].remove(b)
|
||||||
|
graph[b].remove(a)
|
||||||
|
|
||||||
|
to_visit = [choice(list(graph.keys()))]
|
||||||
|
seen = set(to_visit)
|
||||||
|
while to_visit:
|
||||||
|
node = to_visit.pop()
|
||||||
|
for nb in graph[node]:
|
||||||
|
if not nb in seen:
|
||||||
|
seen.add(nb)
|
||||||
|
to_visit.append(nb)
|
||||||
|
|
||||||
|
return len(seen) * (len(graph) - len(seen))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
DAY_INPUT = "i25.txt"
|
||||||
|
print("Solution 1:", solve(Input(DAY_INPUT)), "(hands-free)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user