Compare commits

..

22 commits
main ... 2023

Author SHA1 Message Date
178d96494a Solve 2023:17 p1-2 "Clumsy Crucible"
Revenge, blast from the past etc!

Had to learn Dijkstra's for this one. Was as stuck
as one can be on AOC. This stopped my progress
2023, and kept me busy at least 20-30h this year
(2024) as well. I never came past part 1, but got
part 2 in minutes when I hit home.

Turns out the initial queue was to blame, after
studying Dijkstras, reading hints on the subreddit
and tutorials in blogs. I was off-by-1 in costs,
since I misplaced a read from the grid.

I also struggled a really, really long time with
a bug where I resetted the steps to aggresively.

What helped me to figure it out was to create
simpler test case grids and step-debug them.

Example 1:

241
321

should give a least heat loss of 6 in pt1.

Example 2:

11199
12199
99199
99131
99111

should give a least heat loss of 9 in pt2.
2025-01-05 00:10:26 +01:00
fb468c2199 Solve 2023:16 "The Floor Will Be Lava" 2025-01-05 00:10:26 +01:00
a90269f7f9 Solve 2023:15 "Lens Library"
WALLOFTEXT for part 2, took me 90 minutes to find
this important text:

> Each step begins with a sequence of letters that
> indicate the label of the lens on which the step
> operates. The result of running the HASH algorithm
> on the label indicates the correct box for that
> step.

It also clarifies how part 2 and part 1 relates.
2025-01-05 00:10:26 +01:00
c832e30dcf Solve 2023:14 "Parabolic Reflector Dish" 2025-01-05 00:10:26 +01:00
3de54ce0e9 Solve 2023:13 "Point of Incidence" 2025-01-05 00:10:26 +01:00
4fa1b1e14c Solve 2023:12 "Hot Springs" 2025-01-05 00:10:26 +01:00
9ca8607f8b Solve 2023:11 "Cosmic Expansion" 2025-01-05 00:10:26 +01:00
a1bf11a5ed Fix flake8 errors for 2023:1-10 2025-01-05 00:10:26 +01:00
b681e5cdb7 Solve 2023:10 "Pipe Maze"
Got completely stuck on part 2. Tried some polygon
area calculations, but none provided the correct
answer, most likely due to the unorthodox polygon
points.

I also tried _shoelace method_ without any luck.
Had not heard about that one earlier, it was a good
learning experience even though I vould not use it
here.

By the subreddit, several people had had luck
using the ray method.

> Part 2 using one of my favorite facts from
> graphics engineering: lets say you have an
> enclosed shape, and you want to color every
> pixel inside of it. How do you know if a given
> pixel is inside the shape or not? Well, it
> turns out: if you shoot a ray in any direction
> from the pixel and it crosses the boundary an
> _odd number_ of times, it's _inside_. if it crosses
> an even number of times, it's outside. Works
> for all enclosed shapes, even self-intersecting
> and non-convex ones.
2025-01-05 00:10:26 +01:00
741f7b89d8 Refactor 2023:05
Increasing speed from 66mins to 4mins. Caching the
location value in the code to keep things at highest
speed.

From the subreddit, the algorithm looks like this.

1. Start att location 0
2. Traverse the whole process backwards, by
   reversing process steps and flipping dest/src
   positions.
3. Output is not a location, instead it's a seed.
4. if seed is in any seed range, use seed to get
   location as in step 1.
5. If not, increase location by 1 and repeat 2-4.
2025-01-05 00:10:26 +01:00
08288964bd Remove parse_input helper 2025-01-05 00:10:26 +01:00
96c4c1383a Solve 2023:09 "Mirage Maintenance" 2025-01-05 00:10:26 +01:00
540fa253da Solve 2023:08 "Haunted Wasteland"
Part 2 would have taken 10-15 hours with brute force.

After I figured out the puzzle input had circular
A-Z paths, it was plain as day that LCM was the
solution to the problem.

https://en.wikipedia.org/wiki/Least_common_multiple
2025-01-05 00:10:26 +01:00
fa3416cf40 Solve 2023:07 "Camel Cards" 2025-01-05 00:10:26 +01:00
a1a69d7114 Solve 2023:06 "Wait for it" 2025-01-05 00:10:26 +01:00
Anders Englöf Ytterström
ad05c8fab4 Solve 2023:05 "If You Give A Seed A Fertilizer"
Part 2 takes 66 minutes to run. There is some smart
things to realize here.
2025-01-05 00:10:26 +01:00
Anders Englöf Ytterström
cbd4bf50e1 Solve 2023:04 "Scratchcards"
On a train that according to swedish tradition
was late. Not a good environment to focus.

Got stuck 2 times:

- Initial code asumed the | was always after the 5th
  number, because of the example. Puzzle input had
  it at pos 10. Classic AoC mistake.
- I had a hard time trying to understand the score
  count, I insisted there was meant to be a +1 at
  some point.

  > That means card 1 is worth 8 points (1 for
  > the first match, then doubled three times for
  > each of the three matches after the first)

I should instead have just looked at the numbers.
2025-01-05 00:10:26 +01:00
7290f28ef7 Solve 2023:03 "Gear Ratios"
Overslept, took about 55 mins.
2025-01-05 00:10:26 +01:00
6001a6bb59 Solve 2023:02 "Cube Conundrum"
Very intermediate pythonic solution,
regex would have made the code more compact.

But since 2023:01 decreased the regex courage,
This code will do.
2025-01-05 00:10:26 +01:00
aae14797ea Solve 2023:01 "Trebuchet?!"
Turns out re methods are non-overlapping. And in
true AoC manners, no provided test cases had
overlaps.

Luckily for me, some of the last lines in the input
contained the string "oneight", so I was able to
find it out quite fast.

Revisions:

1) Reverse strings to find last digit
2) Use isdigit() and skip regex.
3) Use regexp with positive look-ahead.
2025-01-05 00:10:26 +01:00
661f18dca4 Prep Advent of Code 2023 2025-01-05 00:10:26 +01:00
bad86889d6 Revert "2023 solutions (#6)"
This reverts commit b6e048e4a7.
2025-01-05 00:09:28 +01:00
51 changed files with 485 additions and 626 deletions

View file

@ -7,9 +7,8 @@ except ValueError:
name = None
if day_no and name:
with open("solutions/day_{}.py".format(day_no.zfill(2)), "w") as s:
s.write(
"""
with open('solutions/day_{}.py'.format(day_no.zfill(2)), 'w') as s:
s.write('''
from solutions import BaseSolution
@ -29,14 +28,9 @@ class Solution(BaseSolution):
if __name__ == '__main__':
solution = Solution()
solution.show_results()
""".strip().format(
day=day_no, day_no=day_no.zfill(2), name=name
)
+ "\n"
)
with open("tests/day_{}_tests.py".format(day_no.zfill(2)), "w") as t:
t.write(
"""
'''.strip().format(day=day_no, day_no=day_no.zfill(2), name=name) + '\n')
with open('tests/day_{}_tests.py'.format(day_no.zfill(2)), 'w') as t:
t.write('''
import unittest
from solutions.day_{day_no} import Solution
@ -52,24 +46,18 @@ class Day{day_no}TestCase(unittest.TestCase):
if __name__ == '__main__':
unittest.main()
""".strip().format(
day_no=day_no.zfill(2)
)
+ "\n"
)
with open("inputs/{}.txt".format(day_no.zfill(2)), "w") as i:
i.write("")
'''.strip().format(day_no=day_no.zfill(2)) + '\n')
with open('inputs/{}.txt'.format(day_no.zfill(2)), 'w') as i:
i.write('')
exit(0)
print(
"\nAdvent of Code 2017" "\n###################" "\n\nby Anders Ytterström (@madr)"
)
print('\nAdvent of Code 2017'
'\n###################'
'\n\nby Anders Ytterström (@madr)')
for i in [str(n).zfill(2) for n in range(1, 26)]:
try:
solution = __import__(
"solutions.day_{}".format(i), globals(), locals(), ["Solution"], 0
).Solution()
solution = __import__('solutions.day_{}'.format(i), globals(), locals(), ['Solution'], 0).Solution()
solution.show_results()
except IOError:
pass

View file

@ -4,22 +4,20 @@ class BaseSolution:
trim_input = True
def parse_input(self, filename):
filepath = "./inputs/{}".format(filename)
with open(filepath, "r") as f:
filepath = './inputs/{}'.format(filename)
with open(filepath, 'r') as f:
self.puzzle_input = f.read()
if self.trim_input:
self.puzzle_input = self.puzzle_input.strip()
def show_results(self):
self.parse_input(self.input_file)
print(
"\n\n{}\n{}\n\nPart 1: {}\nPart 2: {}".format(
str(self),
"-" * len(str(self)),
self.solve(self.puzzle_input),
self.solve_again(self.puzzle_input),
)
)
print('\n\n{}\n{}\n\nPart 1: {}\nPart 2: {}'.format(
str(self),
'-' * len(str(self)),
self.solve(self.puzzle_input),
self.solve_again(self.puzzle_input),
))
def solve(self, puzzle_input):
raise NotImplemented

View file

@ -2,24 +2,21 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "01.txt"
input_file = '01.txt'
def __str__(self):
return "Day 1: Inverse Captcha"
return 'Day 1: Inverse Captcha'
def solve(self, puzzle_input, distance=1):
pi_length = len(puzzle_input)
return sum(
int(puzzle_input[pos])
for pos in range(pi_length)
if puzzle_input[pos] == puzzle_input[(pos + distance) % pi_length]
)
return sum(int(puzzle_input[pos]) for pos in range(pi_length) if
puzzle_input[pos] == puzzle_input[(pos + distance) % pi_length])
def solve_again(self, puzzle_input):
distance = len(puzzle_input) // 2
return self.solve(puzzle_input, distance)
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,10 +2,10 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "02.txt"
input_file = '02.txt'
def __str__(self):
return "Day 2: Corruption Checksum"
return 'Day 2: Corruption Checksum'
def _get_rows(self, puzzle_input):
return [list(map(int, rows.split())) for rows in puzzle_input.splitlines()]
@ -29,6 +29,6 @@ class Solution(BaseSolution):
return sum(self.get_even_divisible(columns) for columns in rows)
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,10 +2,10 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "03.txt"
input_file = '03.txt'
def __str__(self):
return "Day 3: Spiral Memory"
return 'Day 3: Spiral Memory'
def _get_rounds(self, value):
n = 0
@ -32,6 +32,6 @@ class Solution(BaseSolution):
return 279138
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,15 +2,15 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "04.txt"
input_file = '04.txt'
def __str__(self):
return "Day 4: High-Entropy Passphrases"
return 'Day 4: High-Entropy Passphrases'
def validate(self, passphrase, extended=False):
words = passphrase.split()
if extended:
words = ["".join(sorted(w)) for w in words]
words = [''.join(sorted(w)) for w in words]
return sorted(list(set(words))) == sorted(words)
def solve(self, puzzle_input):
@ -20,6 +20,6 @@ class Solution(BaseSolution):
return sum(self.validate(p, True) for p in puzzle_input.splitlines())
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,10 +2,10 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "05.txt"
input_file = '05.txt'
def __str__(self):
return "Day 5: A Maze of Twisty Trampolines, All Alike"
return 'Day 5: A Maze of Twisty Trampolines, All Alike'
def solve(self, puzzle_input, strange_jumps=False):
pos = 0
@ -23,6 +23,6 @@ class Solution(BaseSolution):
return self.solve(puzzle_input, True)
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,10 +2,10 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "06.txt"
input_file = '06.txt'
def __str__(self):
return "Day 6: Memory Reallocation"
return 'Day 6: Memory Reallocation'
def redistribute(self, banks):
banks = list(banks)
@ -39,10 +39,10 @@ class Solution(BaseSolution):
def solve_again(self, puzzle_input):
seen = self._allocate(puzzle_input)
seen_last = " ".join(str(n) for n in seen[-1])
seen_last = ' '.join(str(n) for n in seen[-1])
return len(self._allocate(seen_last)) - 1
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -18,8 +18,8 @@ class Program:
name, weight = data.split()
except ValueError:
name, weight, _, *disc = data.split()
weight = int(weight[1 : len(weight) - 1])
disc = tuple(p.replace(",", "") for p in disc)
weight = int(weight[1:len(weight) - 1])
disc = tuple(p.replace(',', '') for p in disc)
return name, weight, disc
def has_disc(self):
@ -30,31 +30,29 @@ class Program:
class Solution(BaseSolution):
input_file = "07.txt"
input_file = '07.txt'
def __str__(self):
return "Day 7: Recursive Circus"
return 'Day 7: Recursive Circus'
def _get_programs(self, puzzle_input):
return [Program(data) for data in puzzle_input.splitlines()]
def _get_discs(self, disc, programs):
subdisc = [
{"own_weight": p.weight, "obj": p} for p in programs if p.name in disc
]
subdisc = [{'own_weight': p.weight, 'obj': p} for p in programs if p.name in disc]
for t in subdisc:
t["weight"] = t["own_weight"]
if t["obj"].has_disc():
t["disc"] = self._get_discs(t["obj"].disc, programs)
t["weight"] += sum([st["weight"] for st in t["disc"]])
del t["obj"]
t['weight'] = t['own_weight']
if t['obj'].has_disc():
t['disc'] = self._get_discs(t['obj'].disc, programs)
t['weight'] += sum([st['weight'] for st in t['disc']])
del (t['obj'])
return subdisc
def _find_unbalanced_disc(self, disc):
disc = sorted(disc, key=lambda t: t["weight"])
if not disc[0]["weight"] < disc[1]["weight"]:
disc = sorted(disc, key=lambda t: t["weight"], reverse=True)
return disc[0], disc[1]["weight"] - disc[0]["weight"]
disc = sorted(disc, key=lambda t: t['weight'])
if not disc[0]['weight'] < disc[1]['weight']:
disc = sorted(disc, key=lambda t: t['weight'], reverse=True)
return disc[0], disc[1]['weight'] - disc[0]['weight']
def solve(self, puzzle_input):
programs = self._get_programs(puzzle_input)
@ -69,30 +67,28 @@ class Solution(BaseSolution):
unseen = p.unseen_discs(seen)
if len(unseen) > 0:
seen += unseen
bottom_program = list(
filter(lambda p: p.name not in seen, programs_with_discs)
)[0]
bottom_program = list(filter(lambda p: p.name not in seen, programs_with_discs))[0]
return bottom_program
def solve_again(self, puzzle_input):
programs = self._get_programs(puzzle_input)
bottom_program = self.solve(puzzle_input)
disc_tree = {
"own_weight": bottom_program.weight,
"disc": self._get_discs(bottom_program.disc, programs),
'own_weight': bottom_program.weight,
'disc': self._get_discs(bottom_program.disc, programs)
}
diff = -1
unbalanced = True
while unbalanced:
disc, new_diff = self._find_unbalanced_disc(disc_tree["disc"])
disc, new_diff = self._find_unbalanced_disc(disc_tree['disc'])
if new_diff == 0:
unbalanced = False
else:
disc_tree = disc
diff = new_diff
return disc_tree["own_weight"] + diff
return disc_tree['own_weight'] + diff
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,22 +2,22 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "08.txt"
input_file = '08.txt'
registry = {}
def __str__(self):
return "Day 8: I Heard You Like Registers"
return 'Day 8: I Heard You Like Registers'
def _should_modify(self, x, comp, y):
if comp in ("==", "!=", ">=", "<=", ">", "<"):
return eval("{:d} {} {:d}".format(x, comp, y))
if comp in ('==', '!=', '>=', '<=', '>', '<'):
return eval('{:d} {} {:d}'.format(x, comp, y))
return False
def _update_registry(self, registry, instruction):
r, action, n, _, k, comp, v = instruction.split()
current = registry.get(r, 0)
if self._should_modify(registry.get(k, 0), comp, int(v)):
registry[r] = current + int(n) if action == "inc" else current - int(n)
registry[r] = current + int(n) if action == 'inc' else current - int(n)
def solve(self, puzzle_input):
registry = {}
@ -35,6 +35,6 @@ class Solution(BaseSolution):
return max_value
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -4,30 +4,30 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "09.txt"
input_file = '09.txt'
def __str__(self):
return "Day 9: Stream Processing"
return 'Day 9: Stream Processing'
def solve(self, puzzle_input):
level = 0
groups = []
stream = re.sub(r"!.", "", puzzle_input)
stream = re.sub(r"<[^>]*>", "", stream)
stream = re.sub(r'!.', '', puzzle_input)
stream = re.sub(r'<[^>]*>', '', stream)
for c in stream:
if c == "{":
if c == '{':
level += 1
if c == "}":
if c == '}':
groups.append(level)
level -= 1
return sum(groups)
def solve_again(self, puzzle_input):
stream = re.sub(r"!.", "", puzzle_input)
garbage = re.findall(r"<([^>]*)>", stream)
stream = re.sub(r'!.', '', puzzle_input)
garbage = re.findall(r'<([^>]*)>', stream)
return sum([len(g) for g in garbage])
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,14 +2,14 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "10.txt"
input_file = '10.txt'
list = []
l = 0
skip_size = 0
pos = 0
def __str__(self):
return "Day 10: Knot Hash"
return 'Day 10: Knot Hash'
def reset(self, l=256):
self.list = list(range(l))
@ -28,7 +28,7 @@ class Solution(BaseSolution):
def solve(self, puzzle_input, r=256):
self.reset(r)
for sublist_length in map(int, puzzle_input.split(",")):
for sublist_length in map(int, puzzle_input.split(',')):
self.reverse(sublist_length)
return self.list[0] * self.list[1]
@ -38,13 +38,10 @@ class Solution(BaseSolution):
for _ in range(64):
for sublist_length in puzzle_input:
self.reverse(sublist_length)
dense_hash = [
eval("^".join(list(map(str, self.list[seq : seq + 16]))))
for seq in range(0, r, 16)
]
return "".join(["{:x}".format(int(i)).zfill(2) for i in dense_hash])
dense_hash = [eval('^'.join(list(map(str, self.list[seq:seq+16])))) for seq in range(0, r, 16)]
return ''.join(['{:x}'.format(int(i)).zfill(2) for i in dense_hash])
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,20 +2,20 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "11.txt"
input_file = '11.txt'
furthest_away = 0
# https://www.redblobgames.com/grids/hexagons/#coordinates
DIRECTIONS = {
"n": lambda x, y, z: (x, y + 1, z - 1),
"ne": lambda x, y, z: (x + 1, y, z - 1),
"se": lambda x, y, z: (x + 1, y - 1, z),
"s": lambda x, y, z: (x, y - 1, z + 1),
"sw": lambda x, y, z: (x - 1, y, z + 1),
"nw": lambda x, y, z: (x - 1, y + 1, z),
'n': lambda x, y, z: (x, y + 1, z - 1),
'ne': lambda x, y, z: (x + 1, y, z - 1),
'se': lambda x, y, z: (x + 1, y - 1, z),
's': lambda x, y, z: (x, y - 1, z + 1),
'sw': lambda x, y, z: (x - 1, y, z + 1),
'nw': lambda x, y, z: (x - 1, y + 1, z),
}
def __str__(self):
return "Day 11: Hex Ed"
return 'Day 11: Hex Ed'
def _get_end(self, steps):
x = 0
@ -28,14 +28,14 @@ class Solution(BaseSolution):
return abs(x), abs(y), abs(z)
def solve(self, puzzle_input):
x, y, z = self._get_end(puzzle_input.split(","))
x, y, z = self._get_end(puzzle_input.split(','))
return max(x, y, z)
def solve_again(self, puzzle_input):
_, *__ = self._get_end(puzzle_input.split(","))
_, *__ = self._get_end(puzzle_input.split(','))
return self.furthest_away
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,17 +2,17 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "12.txt"
input_file = '12.txt'
seen = []
def __str__(self):
return "Day 12: Digital Plumber"
return 'Day 12: Digital Plumber'
def _walk(self, i, programs):
line = next(filter(lambda l: l.startswith("{} <->".format(i)), programs))
line = next(filter(lambda l: l.startswith('{} <->'.format(i)), programs))
piped = line.split()[2:]
self.seen.add(i)
for p in [int(p.replace(",", "")) for p in piped]:
for p in [int(p.replace(',', '')) for p in piped]:
if p not in self.seen:
self._walk(p, programs)
@ -34,6 +34,6 @@ class Solution(BaseSolution):
return groups
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -4,12 +4,12 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "13.txt"
input_file = '13.txt'
layers = []
scanners = []
def __str__(self):
return "Day 13: Packet Scanners"
return 'Day 13: Packet Scanners'
def _move_scanners(self):
for p, l in enumerate(self.layers):
@ -22,10 +22,7 @@ class Solution(BaseSolution):
self.scanners = [[0, 1] for l in self.layers]
def _setup(self, puzzle_input):
pi = [
tuple(map(int, line.strip().split(": ")))
for line in puzzle_input.splitlines()
]
pi = [tuple(map(int, line.strip().split(': '))) for line in puzzle_input.splitlines()]
self.layers = [0 for _ in range(pi[-1][0] + 1)]
self._init_scanners()
for k, v in pi:
@ -58,16 +55,13 @@ class Solution(BaseSolution):
def solve_again(self, puzzle_input):
# todo: rewrite!
lines = [line.split(": ") for line in puzzle_input.splitlines()]
lines = [line.split(': ') for line in puzzle_input.splitlines()]
heights = {int(pos): int(height) for pos, height in lines}
wait = next(
wait
for wait in itertools.count()
if not any(self._scan(heights[pos], wait + pos) == 0 for pos in heights)
)
wait for wait in itertools.count() if not any(self._scan(heights[pos], wait + pos) == 0 for pos in heights))
return wait
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -3,18 +3,18 @@ from solutions.day_10 import Solution as KnotHash
class Solution(BaseSolution):
input_file = "14.txt"
input_file = '14.txt'
def __str__(self):
return "Day 14: Disk Defragmentation"
return 'Day 14: Disk Defragmentation'
def _get_grid(self, pi):
grid = ""
grid = ''
ks = KnotHash()
for n in range(128):
s = "-".join([pi, str(n)])
s = '-'.join([pi, str(n)])
knothash = ks.solve_again(s)
grid += "{:0128b}".format(int(knothash, 16))
grid += '{:0128b}'.format(int(knothash, 16))
return grid
def _find_regions(self, squares):
@ -31,10 +31,10 @@ class Solution(BaseSolution):
get_adjacent_square(i - 128)
if i % 128 < 127:
get_adjacent_square(i + 1)
if i < 128**2 - 128:
if i < 128 ** 2 - 128:
get_adjacent_square(i + 128)
for i in range(128**2):
for i in range(128 ** 2):
if i in seen or i not in squares:
continue
regions += 1
@ -47,10 +47,10 @@ class Solution(BaseSolution):
def solve_again(self, puzzle_input):
grid = self._get_grid(puzzle_input)
squares = [i for i, s in enumerate(list(grid)) if s == "1"]
squares = [i for i, s in enumerate(list(grid)) if s == '1']
return self._find_regions(squares)
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,10 +2,10 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "15.txt"
input_file = '15.txt'
def __str__(self):
return "Day 15: Dueling Generators"
return 'Day 15: Dueling Generators'
def _calc(self, x, f, m=1):
x = (x * f) % 2147483647
@ -21,7 +21,7 @@ class Solution(BaseSolution):
for _ in range(40 * 10**6):
a = self._calc(a, af)
b = self._calc(b, bf)
if "{:b}".format(a)[-16:] == "{:b}".format(b)[-16:]:
if '{:b}'.format(a)[-16:] == '{:b}'.format(b)[-16:]:
j += 1
return j
@ -29,14 +29,14 @@ class Solution(BaseSolution):
af, bf = (16807, 48271)
a, b = [int(pi.split()[-1]) for pi in puzzle_input.splitlines()]
j = 0
for _ in range(5 * 10**6):
for _ in range(5 * 10 ** 6):
a = self._calc(a, af, 4)
b = self._calc(b, bf, 8)
if "{:b}".format(a)[-16:] == "{:b}".format(b)[-16:]:
if '{:b}'.format(a)[-16:] == '{:b}'.format(b)[-16:]:
j += 1
return j
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,24 +2,24 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "16.txt"
input_file = '16.txt'
def __str__(self):
return "Day 16: Permutation Promenade"
return 'Day 16: Permutation Promenade'
def _move(self, programs, m, i):
l = len(programs)
if m == "s":
if m == 's':
r = int(i)
return programs[-r:] + programs[: l - r]
if m == "x":
x, y = [int(s) for s in i.split("/")]
return programs[-r:] + programs[:l - r]
if m == 'x':
x, y = [int(s) for s in i.split('/')]
z = programs[x]
programs[x] = programs[y]
programs[y] = z
return programs
if m == "p":
xp, yp = i.split("/")
if m == 'p':
xp, yp = i.split('/')
x = programs.index(xp)
y = programs.index(yp)
z = programs[x]
@ -34,22 +34,22 @@ class Solution(BaseSolution):
def solve(self, puzzle_input, n=16):
programs = [chr(c) for c in range(97, 97 + n)]
moves = puzzle_input.split(",")
return "".join(self._dance(programs, moves))
moves = puzzle_input.split(',')
return ''.join(self._dance(programs, moves))
def solve_again(self, puzzle_input, n=16):
moves = puzzle_input.split(",")
moves = puzzle_input.split(',')
initial = [chr(c) for c in range(97, 97 + n)]
programs = list(self.solve(puzzle_input))
dances = 1
while not programs == initial:
programs = self._dance(programs, moves)
dances += 1
for _ in range(10**9 % dances):
for _ in range(10 ** 9 % dances):
programs = self._dance(programs, moves)
return "".join(programs)
return ''.join(programs)
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,10 +2,10 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "17.txt"
input_file = '17.txt'
def __str__(self):
return "Day 17: Spinlock"
return 'Day 17: Spinlock'
def solve(self, puzzle_input):
n = int(puzzle_input)
@ -20,13 +20,13 @@ class Solution(BaseSolution):
pos = 0
n = int(puzzle_input)
last_seen = 0
for i in range(1, 5 * 10**7 + 1):
for i in range(1, 5 * 10 ** 7 + 1):
pos = (pos + n) % i + 1
if pos == 1:
last_seen = i
return last_seen
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,16 +2,16 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "18.txt"
input_file = '18.txt'
sound_freq = 0
queue = [], []
sent = [0, 0]
def __str__(self):
return "Day 18: Duet"
return 'Day 18: Duet'
def _run(self, line, registry, swag_mode=True):
actions = "add jgz mod mul rcv set snd"
actions = 'add jgz mod mul rcv set snd'
a, *kv = line.split()
if len(kv) == 2:
k, v = kv
@ -19,9 +19,9 @@ class Solution(BaseSolution):
k = kv[0]
v = None
if a in actions:
if a == "add" and k in registry:
if a == 'add' and k in registry:
registry[k] += registry[v] if v in registry else int(v)
if a == "jgz": # damn you, 'jgz 1 3'
if a == 'jgz': # damn you, 'jgz 1 3'
try:
k = int(k)
except ValueError:
@ -31,25 +31,25 @@ class Solution(BaseSolution):
except ValueError:
v = registry[v] if v in registry else 1
return v if k > 0 else 1
if a == "mod" and k in registry:
if a == 'mod' and k in registry:
registry[k] %= registry[v] if v in registry else int(v)
if a == "mul" and k in registry:
if a == 'mul' and k in registry:
registry[k] *= registry[v] if v in registry else int(v)
if a == "set":
if a == 'set':
registry[k] = registry[v] if v in registry else int(v)
if swag_mode: # Part 1: scientific wild-ass guess
if a == "rcv" and registry[k] != 0:
if a == 'rcv' and registry[k] != 0:
return self.STOP_SIGNAL
if a == "snd" and k in registry:
if a == 'snd' and k in registry:
self.sound_freq = registry[k]
else: # part 2, actual instructions
if a == "rcv":
if len(self.queue[registry["_id"]]) == 0:
if a == 'rcv':
if len(self.queue[registry['_id']]) == 0:
return 0
registry[k] = self.queue[registry["_id"]].pop(0)
if a == "snd":
self.sent[registry["_id"]] += 1
q = (registry["_id"] + 1) % 2
registry[k] = self.queue[registry['_id']].pop(0)
if a == 'snd':
self.sent[registry['_id']] += 1
q = (registry['_id'] + 1) % 2
kk = registry[k] if k in registry else int(k)
self.queue[q].append(kk)
return 1
@ -65,7 +65,7 @@ class Solution(BaseSolution):
return self.sound_freq
def solve_again(self, puzzle_input):
registry = {"p": 0, "_id": 0}, {"p": 1, "_id": 1}
registry = {'p': 0, '_id': 0}, {'p': 1, '_id': 1}
lines = puzzle_input.splitlines()
i = 0
j = 0
@ -89,6 +89,6 @@ class Solution(BaseSolution):
return self.sent[1]
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -2,48 +2,48 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "19.txt"
input_file = '19.txt'
trim_input = False
def __str__(self):
return "Day 19: A Series of Tubes"
return 'Day 19: A Series of Tubes'
def _walk_maze(self, puzzle_input):
DIRECTIONS = {
"D": (1, 0),
"U": (-1, 0),
"R": (0, 1),
"L": (0, -1),
'D': (1, 0),
'U': (-1, 0),
'R': (0, 1),
'L': (0, -1),
}
maze = puzzle_input.splitlines()
pos = (0, list(maze[0]).index("|"))
d = DIRECTIONS["D"]
paths = "-|"
pos = (0, list(maze[0]).index('|'))
d = DIRECTIONS['D']
paths = '-|'
steps = 0
seen = ""
seen = ''
def _nc(nu):
np = (pos[0] + nu[0], pos[1] + nu[1])
if np[0] < len(maze) and np[1] < len(maze[np[0]]):
return maze[np[0]][np[1]]
else:
return " "
return ' '
while True:
pos = (pos[0] + d[0], pos[1] + d[1])
c = _nc((0, 0))
steps += 1
if c == "+":
if c == '+':
nc = _nc(d)
if nc == " ":
if nc == ' ':
for v in DIRECTIONS.values():
if -v[0] == d[0] and -v[1] == d[1]:
continue
nc = _nc(v)
if nc != " ":
if nc != ' ':
d = v
break
elif c == " ":
elif c == ' ':
break
elif c not in paths:
seen += c
@ -58,6 +58,6 @@ class Solution(BaseSolution):
return steps
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -6,18 +6,15 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "20.txt"
input_file = '20.txt'
def __str__(self):
return "Day 20: Particle Swarm"
return 'Day 20: Particle Swarm'
def _get_particle(self, line):
return [
list(map(int, coordinate.split(",")))
for coordinate in re.findall(r".=<([^>]+)>", line)
]
return [list(map(int, coordinate.split(','))) for coordinate in re.findall(r'.=<([^>]+)>', line)]
def solve(self, puzzle_input, ticks=10**4):
def solve(self, puzzle_input, ticks=10 ** 4):
particles = [self._get_particle(line) for line in puzzle_input.splitlines()]
distances = [[] for _ in range(len(particles))]
for _ in range(ticks):
@ -30,13 +27,10 @@ class Solution(BaseSolution):
p[1] += v[1]
p[2] += v[2]
distances[i].append(sum(map(abs, p)))
d = sorted(
map(lambda d: (d[0], sum(d[1]) / len(d[1])), enumerate(distances)),
key=lambda x: x[1],
)
d = sorted(map(lambda d: (d[0], sum(d[1]) / len(d[1])), enumerate(distances)), key=lambda x: x[1])
return d[0][0]
def solve_again(self, puzzle_input, ticks=10**3):
def solve_again(self, puzzle_input, ticks=10 ** 3):
particles = [self._get_particle(line) for line in puzzle_input.splitlines()]
for _ in range(ticks):
positions = collections.defaultdict(list)
@ -48,7 +42,7 @@ class Solution(BaseSolution):
p[0] += v[0]
p[1] += v[1]
p[2] += v[2]
k = "-".join(map(str, p))
k = '-'.join(map(str, p))
positions[k].append(particle)
for duplicates in positions.values():
if len(duplicates) > 1:
@ -57,6 +51,6 @@ class Solution(BaseSolution):
return len(particles)
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -7,9 +7,9 @@ class Rule:
size = 0
def __init__(self, line):
pattern, output = line.split(" => ")
self.pattern = pattern.replace("/", "")
self.output = output.replace("/", "")
pattern, output = line.split(' => ')
self.pattern = pattern.replace('/', '')
self.output = output.replace('/', '')
self.size = int(len(self.pattern) ** 0.5)
self.patterns = [
@ -22,18 +22,13 @@ class Rule:
]
def __repr__(self):
return "[{}] {} -> {}".format(self.size, self.pattern, self.output)
return '[{}] {} -> {}'.format(self.size, self.pattern, self.output)
def _r(self, b):
return "".join(
map(
lambda g: "".join(g),
zip(*[b[s : s + self.size] for s in range(0, len(b), self.size)][::-1]),
)
)
return ''.join(map(lambda g: ''.join(g), zip(*[b[s:s + self.size] for s in range(0, len(b), self.size)][::-1])))
def _f(self, b):
return b[-self.size :] + b[self.size : len(b) - self.size] + b[0 : self.size]
return b[-self.size:] + b[self.size:len(b) - self.size] + b[0:self.size]
def matches(self, canvas):
return canvas in self.patterns
@ -43,13 +38,13 @@ class Rule:
class Solution(BaseSolution):
input_file = "21.txt"
input_file = '21.txt'
def __str__(self):
return "Day 21: Fractal Art"
return 'Day 21: Fractal Art'
def _get_block(self, canvas, size, n=0):
s = ""
s = ''
cl = int(len(canvas) ** 0.5)
x = n % (cl // size)
y = n // (cl // size)
@ -61,22 +56,20 @@ class Solution(BaseSolution):
def _join_blocks(self, blocks):
bl = len(blocks)
c = int(bl**0.5)
c = int(bl ** 0.5)
rl = int(len(blocks[0]) ** 0.5)
canvas = ""
canvas = ''
for i in range(0, bl, c):
for j in range(rl):
canvas += "".join(
[block[j * rl : (j + 1) * rl] for block in blocks[i : i + c]]
)
canvas += ''.join([block[j * rl:(j + 1) * rl] for block in blocks[i:i + c]])
return canvas
def solve(self, puzzle_input, iterations=5):
canvas = ".#...####"
canvas = '.#...####'
rules = [Rule(l.strip()) for l in puzzle_input.splitlines()]
for _ in range(iterations):
size = 2 if len(canvas) % 2 == 0 else 3
blocks = len(canvas) // (size**2)
blocks = len(canvas) // (size ** 2)
cb = []
for b in range(blocks):
bc = self._get_block(canvas, size, b)
@ -85,12 +78,12 @@ class Solution(BaseSolution):
r = rule[0]
cb.append(r.enhance(bc))
canvas = self._join_blocks(cb)
return collections.Counter(canvas)["#"]
return collections.Counter(canvas)['#']
def solve_again(self, puzzle_input):
return self.solve(puzzle_input, 18)
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -4,10 +4,10 @@ from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "22.txt"
input_file = '22.txt'
def __str__(self):
return "Day 22: Sporifica Virus"
return 'Day 22: Sporifica Virus'
infected = 0
@ -50,7 +50,7 @@ class Solution(BaseSolution):
dirs = dirs[1:] + [dirs[0]]
elif state == "#":
dirs = [dirs[3]] + dirs[:3]
elif state == "F":
elif state == 'F':
dirs = dirs[2:] + dirs[:2]
return dirs
@ -61,16 +61,16 @@ class Solution(BaseSolution):
i, p = existing
return amap, p[2]
else:
amap.append([pos[0], pos[1], "."])
return amap, "."
amap.append([pos[0], pos[1], '.'])
return amap, '.'
def _update_state(self, amap, pos):
t = lambda x: x[1][0] == pos[0] and x[1][1] == pos[1]
existing = next(filter(t, enumerate(amap)))
i, p = existing
if p[2] == ".":
if p[2] == '.':
self.infected += 1
amap[i][2] = "." if p[2] == "#" else "#"
amap[i][2] = '.' if p[2] == '#' else '#'
return amap
def _move(self, pos, d):
@ -80,12 +80,12 @@ class Solution(BaseSolution):
t = lambda x: x[1][0] == pos[0] and x[1][1] == pos[1]
existing = next(filter(t, enumerate(amap)))
i, p = existing
if p[2] == ".":
ns = "W"
elif p[2] == "W":
if p[2] == '.':
ns = 'W'
elif p[2] == 'W':
self.infected += 1
ns = "#"
elif p[2] == "#":
elif p[2] == '#':
ns = "F"
else:
ns = "."
@ -93,6 +93,6 @@ class Solution(BaseSolution):
return amap
if __name__ == "__main__":
if __name__ == '__main__':
solution = Solution()
solution.show_results()

View file

@ -1,138 +0,0 @@
from solutions import BaseSolution
from collections import defaultdict
class Solution(BaseSolution):
input_file = "23.txt"
sound_freq = 0
queue = [], []
sent = [0, 0]
def __str__(self):
return "Day 23: Coprocessor Conflagration"
def solve(self, puzzle_input):
R = defaultdict(int)
P = puzzle_input.splitlines()
i = 0
M = 0
while i < len(P):
j = 1
w, r, v = P[i].split()
match w:
case "set":
R[r] = R[v] if v in "abcdefgh" else int(v)
case "sub":
R[r] -= R[v] if v in "abcdefgh" else int(v)
case "mul":
M += 1
R[r] *= R[v] if v in "abcdefgh" else int(v)
case "jnz":
x = R[r] if r in "abcdefgh" else int(r)
if x != 0:
j = R[v] if v in "abcdefgh" else int(v)
i += j
return M
def solve_again(self, puzzle_input):
def isprime(num):
for n in range(2, int(num**0.5) + 1):
if num % n == 0:
return False
return True
b = 99 * 100 + 100_000
c = b + 17_000 + 1
return sum(not isprime(n) for n in range(b, c, 17))
def python_reconstruction(self, puzzle_input):
R = defaultdict(int)
R["a"] = 1
# 00: set b 99
R["b"] = 99
# 01: set c b
R["c"] = R["b"]
# 02: jnz a 2
if R["a"] == 0:
# 03: jnz 1 5
pass
else:
# 04: mul b 100
# 05: sub b -100000
R["b"] = R["b"] * 100 + 100_000
# 06: set c b
# 07: sub c -17000
R["c"] = R["b"] + 17_000
while True:
# 08: set f 1
# 09: set d 2
R["f"] = 1
R["d"] = 2
while True:
# 10: set e 2
R["e"] = 2
while True:
# 11: set g d
# 12: mul g e
# 13: sub g b
R["g"] = R["d"] * R["e"] - R["b"]
# 14: jnz g 2
if R["g"] == 0:
# 15: set f 0
R["f"] = 0
# 16: sub e -1
R["e"] += 1
# 17: set g e
# 18: sub g b
R["g"] = R["e"] - R["b"]
# 19: jnz g -8
if R["g"] == 0:
break
# 20: sub d -1
R["d"] += 1
# 21: set g d
# 22: sub g b
R["g"] = R["d"] - R["b"]
# 23: jnz g -13
if R["g"] == 0:
break
# 24: jnz f 2
if R["f"] == 0:
# 25: sub h -1
R["h"] += 1
# 26: set g b
# 27: sub g c
R["g"] = R["b"] - R["c"]
# 28: jnz g 2
if R["g"] == 0:
# 29: jnz 1 3
break
else:
# 30: sub b -17
R["b"] += 17
# 31: jnz 1 -23
return R["h"]
if __name__ == "__main__":
solution = Solution()
solution.show_results()

View file

@ -1,51 +0,0 @@
from solutions import BaseSolution
import re
class Solution(BaseSolution):
input_file = "24.txt"
def __str__(self):
return "Day 24: Electromagnetic Moat"
def solve(self, puzzle_input):
p1, _ = self._solve(puzzle_input)
return p1
def solve_again(self, puzzle_input):
_, p2 = self._solve(puzzle_input)
return p2
def _solve(self, puzzle_input):
components = [
tuple(map(int, re.findall(r"\d+", line))) for line in puzzle_input.split()
]
Q = [(c, []) for c in components if 0 in c]
S = 0
L = 0
LS = 0
while Q:
c, s = Q.pop()
if c in s:
S = max(S, sum(x + y for x, y in s))
if len(s) >= L:
L = len(s)
LS = max(LS, L * 1_000_000 + sum(x + y for x, y in s))
continue
cc = set(c) if not s else set(c) - set(s[-1])
if not cc:
cc = set(c)
for p1, p2 in components:
if p1 > 0 and p1 in cc:
Q.append(((p1, p2), s + [c]))
if p2 > 0 and p2 in cc:
Q.append(((p1, p2), s + [c]))
return S, LS % 1_000_000
if __name__ == "__main__":
solution = Solution()
solution.show_results()

View file

@ -1,46 +0,0 @@
from solutions import BaseSolution
from collections import defaultdict
import re
class Solution(BaseSolution):
input_file = "25.txt"
def __str__(self):
return "Day 25: The Halting Problem"
def solve(self, puzzle_input):
T = defaultdict(int)
p = 0
hd, *states = puzzle_input.split("\n\n")
S = int(re.findall(r"(\d+) steps", hd)[0])
s = re.findall(r"state (\w)\.", hd)[0]
P = {}
for state in states:
k = re.findall(r"state (\w)\:", state)[0]
v = [int(n) for n in re.findall(r"value (\d)\.", state)]
d = re.findall(r"(left|right)\.", state)
t = re.findall(r"state (\w)\.", state)
P[k] = (v[0], d[0], t[0], v[1], d[1], t[1])
for _ in range(S):
v0, d0, s0, v1, d1, s1 = P[s]
match T[p]:
case 0:
T[p] = v0
s = s0
p += 1 if d0 == "right" else -1
case 1:
T[p] = v1
s = s1
p += 1 if d1 == "right" else -1
return sum(T.values())
def solve_again(self, puzzle_input):
return "*"
if __name__ == "__main__":
solution = Solution()
solution.show_results()

View file

@ -8,18 +8,18 @@ class Day1TestCase(unittest.TestCase):
self.solution = Solution()
def test_sums_equal_pairs(self):
assert self.solution.solve("1122") == 3
assert self.solution.solve("1111") == 4
assert self.solution.solve("1234") == 0
assert self.solution.solve("91212129") == 9
assert self.solution.solve('1122') == 3
assert self.solution.solve('1111') == 4
assert self.solution.solve('1234') == 0
assert self.solution.solve('91212129') == 9
def test_sums_equal_pairs_halvway_around(self):
assert self.solution.solve_again("1212") == 6
assert self.solution.solve_again("1221") == 0
assert self.solution.solve_again("123425") == 4
assert self.solution.solve_again("123123") == 12
assert self.solution.solve_again("12131415") == 4
assert self.solution.solve_again('1212') == 6
assert self.solution.solve_again('1221') == 0
assert self.solution.solve_again('123425') == 4
assert self.solution.solve_again('123123') == 12
assert self.solution.solve_again('12131415') == 4
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -13,19 +13,19 @@ class Day2TestCase(unittest.TestCase):
assert self.solution.get_diff([2, 4, 6, 8]) == 6
def test_calculates_checksum(self):
puzzle_input = "\n".join(["5 1 9 5", "7 5 3", "2 4 6 8"])
puzzle_input = '\n'.join(['5 1 9 5', '7 5 3', '2 4 6 8'])
assert self.solution.solve(puzzle_input) == 18
def test_calculates_row_even_divisible(self):
puzzle_input = "\n".join(["5 9 2 8", "9 4 7 3", "3 8 6 5"])
puzzle_input = '\n'.join(['5 9 2 8', '9 4 7 3', '3 8 6 5'])
assert self.solution.get_even_divisible([5, 9, 2, 8]) == 4
assert self.solution.get_even_divisible([9, 4, 7, 3]) == 3
assert self.solution.get_even_divisible([3, 8, 6, 5]) == 2
def test_calculates_row_result_sum(self):
puzzle_input = "\n".join(["5 9 2 8", "9 4 7 3", "3 8 6 5"])
puzzle_input = '\n'.join(['5 9 2 8', '9 4 7 3', '3 8 6 5'])
assert self.solution.solve_again(puzzle_input) == 9
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,11 +8,11 @@ class Day3TestCase(unittest.TestCase):
self.solution = Solution()
def test_shortest_manhattan_distance(self):
assert self.solution.solve("1") == 0
assert self.solution.solve("12") == 3
assert self.solution.solve("23") == 2
assert self.solution.solve("1024") == 31
assert self.solution.solve('1') == 0
assert self.solution.solve('12') == 3
assert self.solution.solve('23') == 2
assert self.solution.solve('1024') == 31
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -9,22 +9,22 @@ class Day4TestCase(unittest.TestCase):
def test_passphrase_has_only_unique_words(self):
passphrases = [
"aa bb cc dd ee",
"aa bb cc dd aa",
"aa bb cc dd aaa",
'aa bb cc dd ee',
'aa bb cc dd aa',
'aa bb cc dd aaa',
]
assert self.solution.validate(passphrases[0]) == True
assert self.solution.validate(passphrases[1]) == False
assert self.solution.validate(passphrases[2]) == True
assert self.solution.solve("\n".join(passphrases)) == 2
assert self.solution.solve('\n'.join(passphrases)) == 2
def test_passphrase_has_no_anagrams(self):
passphrases = [
"abcde fghij",
"abcde xyz ecdab",
"a ab abc abd abf abj",
"iiii oiii ooii oooi oooo",
"oiii ioii iioi iiio",
'abcde fghij',
'abcde xyz ecdab',
'a ab abc abd abf abj',
'iiii oiii ooii oooi oooo',
'oiii ioii iioi iiio',
]
assert self.solution.validate(passphrases[0], True) == True
assert self.solution.validate(passphrases[1], True) == False
@ -33,5 +33,5 @@ class Day4TestCase(unittest.TestCase):
assert self.solution.validate(passphrases[4], True) == False
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,29 +8,13 @@ class Day5TestCase(unittest.TestCase):
self.solution = Solution()
def test_calculate_exit_distance(self):
puzzle_input = "\n".join(
[
"0",
"3",
"0",
"1",
"-3",
]
)
puzzle_input = '\n'.join(['0', '3', '0', '1', '-3',])
assert self.solution.solve(puzzle_input) == 5
def test_calculate_stranger_exit_distance(self):
puzzle_input = "\n".join(
[
"0",
"3",
"0",
"1",
"-3",
]
)
puzzle_input = '\n'.join(['0', '3', '0', '1', '-3',])
assert self.solution.solve_again(puzzle_input) == 10
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,7 +8,7 @@ class Day6TestCase(unittest.TestCase):
self.solution = Solution()
def test_count_redistribution_cycles(self):
puzzle_input = "0 2 7 0"
puzzle_input = '0 2 7 0'
banks = list(map(int, puzzle_input.split()))
assert self.solution.redistribute(banks) == (2, 4, 1, 2)
assert self.solution.redistribute((2, 4, 1, 2)) == (3, 1, 2, 3)
@ -18,9 +18,9 @@ class Day6TestCase(unittest.TestCase):
assert self.solution.solve(puzzle_input) == 5
def test_count_redistribution_cycles_again(self):
puzzle_input = "0 2 7 0"
puzzle_input = '0 2 7 0'
assert self.solution.solve_again(puzzle_input) == 4
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -5,7 +5,7 @@ from solutions.day_07 import Solution, Program
class Day7TestCase(unittest.TestCase):
def setUp(self):
self.puzzle_input = """
self.puzzle_input = '''
pbga (66)
xhth (57)
ebii (61)
@ -19,24 +19,24 @@ class Day7TestCase(unittest.TestCase):
ugml (68) -> gyxo, ebii, jptl
gyxo (61)
cntj (57)
""".strip()
'''.strip()
self.solution = Solution()
def test_find_bottom_tower(self):
p = Program("ugml (68) -> gyxo, ebii, jptl")
assert p.name == "ugml"
p = Program('ugml (68) -> gyxo, ebii, jptl')
assert p.name == 'ugml'
assert p.weight == 68
assert p.disc == ("gyxo", "ebii", "jptl")
p = Program("jptl (61)")
assert p.name == "jptl"
assert p.disc == ('gyxo', 'ebii', 'jptl')
p = Program('jptl (61)')
assert p.name == 'jptl'
assert p.weight == 61
assert p.disc == ()
assert self.solution.solve(self.puzzle_input).name == "tknk"
assert self.solution.solve(self.puzzle_input).name == 'tknk'
def test_find_weight_correction(self):
corrected = self.solution.solve_again(self.puzzle_input)
assert corrected == 60
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,23 +8,23 @@ class Day8TestCase(unittest.TestCase):
self.solution = Solution()
def test_largest_registry_value(self):
puzzle_input = """
puzzle_input = '''
b inc 5 if a > 1
a inc 1 if b < 5
c dec -10 if a >= 1
c inc -20 if c == 10
""".strip()
'''.strip()
assert self.solution.solve(puzzle_input) == 1
def test_largest_ath_registry_value(self):
puzzle_input = """
puzzle_input = '''
b inc 5 if a > 1
a inc 1 if b < 5
c dec -10 if a >= 1
c inc -20 if c == 10
""".strip()
'''.strip()
assert self.solution.solve_again(puzzle_input) == 10
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,24 +8,24 @@ class Day9TestCase(unittest.TestCase):
self.solution = Solution()
def test_calculates_score(self):
assert self.solution.solve("{}") == 1
assert self.solution.solve("{{{}}}") == 6
assert self.solution.solve("{{},{}}") == 5
assert self.solution.solve("{{{},{},{{}}}}") == 16
assert self.solution.solve("{<a>,<a>,<a>,<a>}") == 1
assert self.solution.solve("{{<ab>},{<ab>},{<ab>},{<ab>}}") == 9
assert self.solution.solve("{{<!!>},{<!!>},{<!!>},{<!!>}}") == 9
assert self.solution.solve("{{<a!>},{<a!>},{<a!>},{<ab>}}") == 3
assert self.solution.solve('{}') == 1
assert self.solution.solve('{{{}}}') == 6
assert self.solution.solve('{{},{}}') == 5
assert self.solution.solve('{{{},{},{{}}}}') == 16
assert self.solution.solve('{<a>,<a>,<a>,<a>}') == 1
assert self.solution.solve('{{<ab>},{<ab>},{<ab>},{<ab>}}') == 9
assert self.solution.solve('{{<!!>},{<!!>},{<!!>},{<!!>}}') == 9
assert self.solution.solve('{{<a!>},{<a!>},{<a!>},{<ab>}}') == 3
def test_count_garbage(self):
assert self.solution.solve_again("<>") == 0
assert self.solution.solve_again("<random characters>") == 17
assert self.solution.solve_again("<<<<>") == 3
assert self.solution.solve_again("<{!>}>") == 2
assert self.solution.solve_again("<!!>") == 0
assert self.solution.solve_again("<!!!>>") == 0
assert self.solution.solve_again('<>') == 0
assert self.solution.solve_again('<random characters>') == 17
assert self.solution.solve_again('<<<<>') == 3
assert self.solution.solve_again('<{!>}>') == 2
assert self.solution.solve_again('<!!>') == 0
assert self.solution.solve_again('<!!!>>') == 0
assert self.solution.solve_again('<{o"i!a,<{i<a>') == 10
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -25,16 +25,14 @@ class Day10TestCase(unittest.TestCase):
assert self.solution.list == [3, 4, 2, 1, 0]
assert self.solution.skip_size == 4
assert self.solution.pos == 4
assert self.solution.solve("3,4,1,5", r=5) == 12
assert self.solution.solve('3,4,1,5', r=5) == 12
def test_dense_hash(self):
assert self.solution.solve_again("") == "a2582a3a0e66e6e86e3812dcb672a272"
assert (
self.solution.solve_again("AoC 2017") == "33efeb34ea91902bb2f59c9920caa6cd"
)
assert self.solution.solve_again("1,2,3") == "3efbe78a8d82f29979031a4aa0b16a9d"
assert self.solution.solve_again("1,2,4") == "63960835bcdc130f0b66d7ff4f6a5a8e"
assert self.solution.solve_again('') == 'a2582a3a0e66e6e86e3812dcb672a272'
assert self.solution.solve_again('AoC 2017') == '33efeb34ea91902bb2f59c9920caa6cd'
assert self.solution.solve_again('1,2,3') == '3efbe78a8d82f29979031a4aa0b16a9d'
assert self.solution.solve_again('1,2,4') == '63960835bcdc130f0b66d7ff4f6a5a8e'
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,15 +8,15 @@ class Day11TestCase(unittest.TestCase):
self.solution = Solution()
def test_distance(self):
assert self.solution.solve("ne,ne,ne") == 3
assert self.solution.solve("ne,ne,sw,sw") == 0
assert self.solution.solve("ne,ne,s,s") == 2
assert self.solution.solve("se,sw,se,sw,sw") == 3
assert self.solution.solve('ne,ne,ne') == 3
assert self.solution.solve('ne,ne,sw,sw') == 0
assert self.solution.solve('ne,ne,s,s') == 2
assert self.solution.solve('se,sw,se,sw,sw') == 3
def test_furthest_away(self):
assert self.solution.solve_again("ne,ne,sw,sw") == 2
assert self.solution.solve_again("se,sw,se,sw,sw") == 3
assert self.solution.solve_again('ne,ne,sw,sw') == 2
assert self.solution.solve_again('se,sw,se,sw,sw') == 3
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,7 +8,7 @@ class Day12TestCase(unittest.TestCase):
self.solution = Solution()
def test_connected_to_program0(self):
puzzle_input = """
puzzle_input = '''
0 <-> 2
1 <-> 1
2 <-> 0, 3, 4
@ -16,11 +16,11 @@ class Day12TestCase(unittest.TestCase):
4 <-> 2, 3, 6
5 <-> 6
6 <-> 4, 5
""".strip()
'''.strip()
assert self.solution.solve(puzzle_input) == 6
def test_group_coun(self):
puzzle_input = """
puzzle_input = '''
0 <-> 2
1 <-> 1
2 <-> 0, 3, 4
@ -28,9 +28,9 @@ class Day12TestCase(unittest.TestCase):
4 <-> 2, 3, 6
5 <-> 6
6 <-> 4, 5
""".strip()
'''.strip()
assert self.solution.solve_again(puzzle_input) == 2
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,23 +8,23 @@ class Day13TestCase(unittest.TestCase):
self.solution = Solution()
def test_get_through_firewall(self):
puzzle_input = """
puzzle_input = '''
0: 3
1: 2
4: 4
6: 4
""".strip()
'''.strip()
assert self.solution.solve(puzzle_input) == 24
def test_wait(self):
puzzle_input = """
puzzle_input = '''
0: 3
1: 2
4: 4
6: 4
""".strip()
'''.strip()
assert self.solution.solve_again(puzzle_input) == 10
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,11 +8,11 @@ class Day14TestCase(unittest.TestCase):
self.solution = Solution()
def test_used_squares(self):
assert self.solution.solve("flqrgnkx") == 8108
assert self.solution.solve('flqrgnkx') == 8108
def test_regions(self):
assert self.solution.solve_again("flqrgnkx") == 1242
assert self.solution.solve_again('flqrgnkx') == 1242
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,19 +8,19 @@ class Day15TestCase(unittest.TestCase):
self.solution = Solution()
def test_40m_pairs(self):
puzzle_input = """
puzzle_input = '''
Generator A starts with 65
Generator B starts with 8921
""".strip()
# assert self.solution.solve(puzzle_input) == 588
'''.strip()
#assert self.solution.solve(puzzle_input) == 588
def test_5k_pairs(self):
puzzle_input = """
puzzle_input = '''
Generator A starts with 65
Generator B starts with 8921
""".strip()
'''.strip()
assert self.solution.solve_again(puzzle_input) == 309
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,9 +8,9 @@ class Day16TestCase(unittest.TestCase):
self.solution = Solution()
def test_something(self):
puzzle_input = """s1,x3/4,pe/b""".strip()
assert self.solution.solve(puzzle_input, 5) == "baedc"
puzzle_input = '''s1,x3/4,pe/b'''.strip()
assert self.solution.solve(puzzle_input, 5) == 'baedc'
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,8 +8,8 @@ class Day17TestCase(unittest.TestCase):
self.solution = Solution()
def test_something(self):
assert self.solution.solve("3") == 638
assert self.solution.solve('3') == 638
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,7 +8,7 @@ class Day18TestCase(unittest.TestCase):
self.solution = Solution()
def test_something(self):
puzzle_input = """
puzzle_input = '''
set a 1
add a 2
mul a a
@ -19,11 +19,11 @@ class Day18TestCase(unittest.TestCase):
jgz a -1
set a 1
jgz a -2
""".strip()
'''.strip()
assert self.solution.solve(puzzle_input) == 4
def test_something_else(self):
puzzle_input = """
puzzle_input = '''
snd 1
snd 2
snd p
@ -31,9 +31,9 @@ class Day18TestCase(unittest.TestCase):
rcv b
rcv c
rcv d
""".strip()
'''.strip()
assert self.solution.solve_again(puzzle_input) == 3
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,27 +8,27 @@ class Day19TestCase(unittest.TestCase):
self.solution = Solution()
def test_seen(self):
puzzle_input = """ |
puzzle_input = ''' |
| +--+
A | C
F---|----E|--+
| | | D
+B-+ +--+
"""
assert self.solution.solve(puzzle_input) == "ABCDEF"
'''
assert self.solution.solve(puzzle_input) == 'ABCDEF'
def test_steps(self):
puzzle_input = """ |
puzzle_input = ''' |
| +--+
A | C
F---|----E|--+
| | | D
+B-+ +--+
"""
'''
assert self.solution.solve_again(puzzle_input) == 38
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,21 +8,21 @@ class Day20TestCase(unittest.TestCase):
self.solution = Solution()
def test_shortest_distance_over_time(self):
puzzle_input = """
puzzle_input = '''
p=< 3,0,0>, v=< 2,0,0>, a=<-1,0,0>
p=< 4,0,0>, v=< 0,0,0>, a=<-2,0,0>
""".strip()
'''.strip()
assert self.solution.solve(puzzle_input, 4) == 0
def test_something(self):
puzzle_input = """
puzzle_input = '''
p=<-6,0,0>, v=< 3,0,0>, a=< 0,0,0>
p=<-4,0,0>, v=< 2,0,0>, a=< 0,0,0>
p=<-2,0,0>, v=< 1,0,0>, a=< 0,0,0>
p=< 3,0,0>, v=<-1,0,0>, a=< 0,0,0>
""".strip()
'''.strip()
assert self.solution.solve_again(puzzle_input, 4) == 1
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -8,12 +8,12 @@ class Day21TestCase(unittest.TestCase):
self.solution = Solution()
def test_something(self):
puzzle_input = """
puzzle_input = '''
../.# => ##./#../...
.#./..#/### => #..#/..../..../#..#
""".strip()
'''.strip()
assert self.solution.solve(puzzle_input, 2) == 12
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -26,6 +26,5 @@ class Day22TestCase(unittest.TestCase):
assert self.solution.solve_again(puzzle_input, 100) == 26
assert self.solution.solve_again(puzzle_input, 10000000) == 2511944
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main()

View file

@ -80,3 +80,59 @@ def vdbg(seen, h, w):
"""Print-debug visited positions of a matrix"""
for r in range(h):
print("".join(["#" if (r, c) in seen else "." for c in range(w)]))
"""
1. Create an array that holds the distance of each vertex from the starting
vertex. Initially, set this distance to infinity for all vertices except
the starting vertex which should be set to 0.
2. Create a priority queue (heap) and insert the starting vertex with its
distance of 0.
3. While there are still vertices left in the priority queue, select the vertex
with the smallest recorded distance from the starting vertex and visit its
neighboring vertices.
4. For each neighboring vertex, check if it is visited already or not. If it
isnt visited yet, calculate its tentative distance by adding its weight
to the smallest distance found so far for its parent/previous node
(starting vertex in case of first-level vertices).
5. If this tentative distance is smaller than previously recorded value
(if any), update it in our distances array.
6. Finally, add this visited vertex with its updated distance to our priority
queue and repeat step-3 until we have reached our destination or exhausted
all nodes.
"""
# https://pieriantraining.com/understanding-dijkstras-algorithm-in-python/
def dijkstra_algorithm(graph, start_node):
def min_distance(distances, visited):
min_val = float("inf")
min_index = -1
for i in range(len(distances)):
if distances[i] < min_val and i not in visited:
min_val = distances[i]
min_index = i
return min_index
num_nodes = len(graph)
distances = [float("inf")] * num_nodes
visited = []
distances[start_node] = 0
for i in range(num_nodes):
current_node = min_distance(distances, visited)
visited.append(current_node)
for j in range(num_nodes):
if graph[current_node][j] != 0:
new_distance = distances[current_node] + graph[current_node][j]
if new_distance < distances[j]:
distances[j] = new_distance
return distances

View file

@ -0,0 +1,100 @@
from heapq import heappop, heappush
from output import answer # D, DD, ADJ, ints, mhd, mdbg, vdbg
n = 17
title = "Clumsy Crucible"
@answer(1, "Using std crucible, least heat loss incured: {}")
def part_1(presolved):
return presolved[0]
@answer(2, "Using ultra crucible, least heat loss incured: {}")
def part_2(presolved):
return presolved[1]
def solve(data):
grid = {
(r, c): int(col)
for r, row in enumerate(data.split())
for c, col in enumerate(row)
}
p1 = least_heat_loss(grid, 1, 3)
p2 = least_heat_loss(grid, 4, 10)
return p1, p2
def least_heat_loss(grid, minsteps, maxsteps):
target = max(grid)
seen = set()
queue = [(0, (0, 0), (0, 1), 0)]
while queue:
cost, pos, direction, steps = heappop(queue)
y, x = pos
dy, dx = direction
if pos == target:
return cost
if ((pos, direction, steps)) in seen:
continue
seen.add((pos, direction, steps))
if steps >= minsteps:
cwdy, cwdx = clockwise(*direction)
if (cw := (y + cwdy, x + cwdx)) in grid:
cwy, cwx = cw
heappush(queue, (cost + grid[cw], (cwy, cwx), (cwdy, cwdx), 1))
ccwdy, ccwdx = counterclockwise(*direction)
if (ccw := (y + ccwdy, x + ccwdx)) in grid:
ccwy, ccwx = ccw
heappush(queue, (cost + grid[ccw], (ccwy, ccwx), (ccwdy, ccwdx), 1))
if steps < maxsteps and (fwd := (y + dy, x + dx)) in grid:
fwdy, fwdx = fwd
heappush(queue, (cost + grid[fwd], (fwdy, fwdx), direction, steps + 1))
return -1
def clockwise(y, x):
"""
>>> clockwise(-1, 0)
(0, 1)
>>> clockwise(0, 1)
(1, 0)
>>> clockwise(1, 0)
(0, -1)
>>> clockwise(0, -1)
(-1, 0)
"""
return (x, y) if y == 0 else (x, -y)
def counterclockwise(y, x):
"""
>>> counterclockwise(-1, 0)
(0, -1)
>>> counterclockwise(0, -1)
(1, 0)
>>> counterclockwise(1, 0)
(0, 1)
>>> counterclockwise(0, 1)
(-1, 0)
"""
return (x, y) if x == 0 else (-x, y)
if __name__ == "__main__":
with open("./input/17.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)