juergenhoetzel / adventofcode2022

Advent of Code 2022 Solutions in Python

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Advent of Code 2022 solutions in 🐍 Python.

Day 1

from pathlib import Path
INPUT_FILE = "input1.txt"
elf_foods = [
sum([int(line) for line in section.splitlines() if line])
for section in Path(INPUT_FILE).read_text().split("\n\n")
]
part1 = max(elf_foods)
part2 = sum(sorted(elf_foods, reverse=True)[:3])
print(f"Part1: {part1}")
print(f"Part2: {part2}")

Day 2

from pathlib import Path
SCORES = {
"A": {"Y": 6, "X": 3, "Z": 0},
"B": {"Y": 3, "X": 0, "Z": 6},
"C": {"Y": 0, "X": 6, "Z": 3},
}
def points(c):
return ord(c) - ord("X") + 1
def score(opponent, me):
return SCORES[opponent][me] + points(me)
SCORES2 = {
"A": {"Y": 1 + 3, "X": 3 + 0, "Z": 2 + 6},
"B": {"Y": 2 + 3, "X": 1 + 0, "Z": 3 + 6},
"C": {"Y": 3 + 3, "X": 2 + 0, "Z": 1 + 6},
}
input_pairs = [line.split(" ") for line in Path("input2.txt").read_text().splitlines()]
part1 = sum([score(opponent, me) for (opponent, me) in input_pairs])
part2 = sum([SCORES2[opponent][strategy] for (opponent, strategy) in input_pairs])
print(f"Part1: {part1}")
print(f"Part2: {part2}")

Day 3

from pathlib import Path
def priority(c):
if c.isupper():
return ord(c) - ord("A") + 27
return ord(c) - ord("a") + 1
part1 = sum(
[
priority(*(set(line[:s]) & set(line[s:]))) # should be exactile one
for line in Path("input3.txt").read_text().splitlines()
if (s := len(line) // 2)
]
)
print(f"Part1: {part1}")
lines = [
line
for line in Path("input3.txt").read_text().splitlines()
if (s := len(line) // 2)
]
part2 = sum(
[
priority(*set.intersection(*[set(sack) for sack in lines[i : (i + 3)]]))
for i in range(0, len(lines), 3)
]
)
print(f"Part2: {part2}")

Day 4

import re
from pathlib import Path
def parse_line(line):
f1, t1, f2, t2 = [
int(s) for s in re.match("^([0-9]+)-([0-9]+),([0-9]+)-([0-9]+)$", line).groups()
]
return set(range(f1, t1 + 1)), set(range(f2, t2 + 1))
def is_included(s1, s2):
return s1 & s2 in (s1, s2)
part1 = sum(
[
is_included(*parse_line(line))
for line in Path("input4.txt").read_text().splitlines()
]
)
part2 = sum(
[
len(set.intersection(*parse_line(line))) >= 1
for line in Path("input4.txt").read_text().splitlines()
]
)
print(f"Part1: {part1}")
print(f"Part2: {part2}")

Day 5

from pathlib import Path
from dataclasses import dataclass
import re
@dataclass
class State:
stacks: list[list[str]]
def move(self, n: int, src: int, dst: int, reverse=True):
items = self.stacks[src - 1][-n:]
if reverse:
items = reversed(items)
self.stacks[src - 1] = self.stacks[src - 1][:-n]
self.stacks[dst - 1] = [*self.stacks[dst - 1], *items]
def __str__(self):
"Top representation of stacks"
return "".join([stack[-1] for stack in self.stacks if len(stack)])
def parse_input() -> (State, tuple[int, int, int]):
sections = Path("input5.txt").read_text().split("\n\n")
state_lines = sections[0].splitlines()
crate_positions = [
match.span()[0] for match in (re.finditer("[0-9]+", state_lines[-1]))
]
stacks: list[str] = [[] for _ in range(len(crate_positions))]
for crate_str in state_lines[:-1]:
for offset, i in zip(crate_positions, range(len(crate_positions))):
if len(crate_str) > offset and (c := crate_str[offset]).isalpha():
stacks[i] = [c, *stacks[i]]
commands = [
[
int(s)
for s in re.match("move ([0-9]+) from ([0-9]+) to ([0-9+])", line).groups()
]
for line in sections[1].splitlines()
]
return State(stacks), commands
state, movements = parse_input()
state2 = State([*state.stacks])
for movement in movements:
state.move(*movement)
state2.move(*movement, False)
print(f"Part1: {state}")
print(f"Part2: {state2}")

Day 6

from pathlib import Path
line = Path("input6.txt").read_text().splitlines()[0]
for p, n in ((1, 4), (2, 14)):
offset = [len(set(line[i : i + n])) for i in range(len(line) - n)].index(n)
print(f"Part{p}: {offset+n}")

Day 7

from pathlib import Path
import re
SIZE_RE = re.compile("([0-9]+) (.*)$")
DIR_RE = re.compile("dir (.*)$")
COMMAND_RE = re.compile(r"(ls|cd )(.*)$")
size = 0
p = Path("/")
sizes = {}
current_command = None
s = Path("input7.txt").read_text()
cmd_outputs = re.split(r"\$ ", s, flags=re.MULTILINE)[1:]
dir_tree = {Path("/"): []}
cur_path = Path("/")
for cmd_output in cmd_outputs:
cmd_s = cmd_output.splitlines()[0]
output_lines = cmd_output.splitlines()[1:]
m = COMMAND_RE.match(cmd_s)
cmd, arg = m.groups()
if cmd == "ls":
if len(dir_tree.get(cur_path, [])):
raise ValueError(f"{cur_path} entries already exist")
dir_tree[cur_path] = []
for output in output_lines:
if m := SIZE_RE.match(output):
size, name = int(m.group(1)), m.group(2)
dir_tree[cur_path].append((size, name))
elif m := DIR_RE.match(output):
name = m.group(1)
dir_tree[cur_path].append((0, name))
else:
raise ValueError("Invalid ls line {output}")
elif cmd == "cd ":
cur_path = (cur_path / arg).resolve()
dir_tree[cur_path] = dir_tree.get(cur_path, [])
else:
raise ValueError(f"Invalid Command '{cmd}'")
def dir_size(p):
acc = 0
for size, name in dir_tree.get(p):
if size:
acc += size
else:
acc += dir_size(p / Path(name))
return acc
sum1 = sum([x for p in dir_tree.keys() if (x := dir_size(p)) <= 100_000])
print(f"Part1: {sum1}")
DISK_SIZE = 70000000
used_space = dir_size(Path("/"))
free_space = DISK_SIZE - used_space
to_delete = 30000000 - free_space
min2 = min([x for p in dir_tree.keys() if (x := dir_size(p)) >= to_delete])
print(f"Part2: {min2}")

Day 8

from pathlib import Path
grid = [[int(c) for c in line] for line in Path("input8.txt").read_text().splitlines()]
def visible_nodes(grid):
nodes = []
for y in range(1, len(grid) - 1):
for x in range(1, len(grid[y]) - 1):
height = grid[y][x]
inv_y_t = any((grid[y1][x] >= height for y1 in range(0, y)))
inv_y_b = any((grid[y1][x] >= height for y1 in range(y + 1, len(grid))))
inv_x_l = any((grid[y][x1] >= height for x1 in range(0, x)))
inv_x_r = any((grid[y][x1] >= height for x1 in range(x + 1, len(grid[y]))))
visible = not all(
[
inv_y_b,
inv_y_t,
inv_x_r,
inv_x_l,
]
)
if visible:
nodes.append((y, x, height, visible))
return nodes
def sight(grid, y, x):
height = grid[y][x]
ranges = [
zip([y] * len(grid), range(x - 1, -1, -1)),
zip([y] * len(grid), range(x + 1, len(grid))),
zip(range(y - 1, -1, -1), [x] * len(grid)),
zip(range(y + 1, len(grid)), [x] * len(grid)),
]
total = 1
for r in ranges:
acc = 0
for y, x in r:
acc += 1
if grid[y][x] >= height:
break
total *= acc
return total
def score_nodes(grid):
for y in range(1, len(grid) - 1):
for x in range(1, len(grid[y]) - 1):
yield sight(grid, x, y)
n = len(visible_nodes(grid)) + len(grid) * 4 - 4
print(f"Part1: {n}")
print(f"Part2: {max(score_nodes(grid))}")

Day 9

from pathlib import Path
movements = [
(cols[0], int(cols[1]))
for line in Path("input9.txt").read_text().splitlines()
if (cols := line.split())
]
t_x = 0
t_y = 0
h_x = 0
h_y = 0
visited_positions = set((t_x, t_y))
for (direction, n) in movements:
for _ in range(n):
match direction:
case "R":
h_x += 1
case "U":
h_y -= 1
case "D":
h_y += 1
case "L":
h_x -= 1
case _:
raise ValueError("Invalid move: {direction}")
dist_x = h_x - t_x
dist_y = h_y - t_y
match (dist_x, dist_y):
case (2, 0):
t_x += 1
visited_positions.add((t_x, t_y))
case (-2, 0):
t_x -= 1
visited_positions.add((t_x, t_y))
case (2, 1) | (1, 2):
t_x += 1
t_y += 1
visited_positions.add((t_x, t_y))
case (2, -1) | (1, -2):
t_x += 1
t_y -= 1
visited_positions.add((t_x, t_y))
case (0, 2):
t_y += 1
visited_positions.add((t_x, t_y))
case (0, -2):
t_y -= 1
visited_positions.add((t_x, t_y))
case (-1, 2) | (-2, 1):
t_x -= 1
t_y += 1
visited_positions.add((t_x, t_y))
case (-2, -1) | (-1, -2):
t_x -= 1
t_y -= 1
visited_positions.add((t_x, t_y))
case (1, 1) | (-1, -1) | (0, 1) | (1, 0) | (0, -1) | (-1, 0) | (1, -1) | (
0,
0,
) | (
-1,
1,
):
pass
case _:
raise ValueError(f"Invalid move {direction} {dist_x, dist_y}")
print(len(visited_positions))

Day 10

from pathlib import Path
lines = Path("input10.txt").read_text().splitlines()
cycle = 1
r = 1
strengths = {}
positions = {}
for line in lines:
cmd = line.split()
strengths[cycle] = r * cycle
positions[cycle] = r
if cmd[0] == "addx":
n = int(cmd[1])
strengths[cycle + 1] = r * (cycle + 1)
positions[cycle + 1] = r
r += n
strengths[cycle + 2] = r * (cycle + 2)
positions[cycle + 2] = r
cycle += 2
elif cmd[0] == "noop":
cycle += 1
offsets = [20, 60, 100, 140, 180, 220]
print(f"Part1: {sum([strengths[offset] for offset in offsets])}")
print("Part2:")
for i in range(1, 241):
if i % 40 == 1:
print()
pos = positions[i]
if i % 40 in (pos + 1, pos + 2, pos):
print("#", end="")
else:
print(".", end="")

Day 11

import re
from dataclasses import dataclass
from functools import reduce
from operator import add, mul
from pathlib import Path
@dataclass
class Monkey:
n: int
items: list[int]
operation: str
x: str
mod: int
monkey1: int
monkey2: int
inspections: int = 0
def inspect(self, monkeys: list[Monkey], mod=None):
for item in self.items:
self.inspections += 1
if self.operation == "*":
op = mul
else:
op = add
if self.x == "old":
item = op(item, item)
else:
item = op(item, int(self.x))
if mod:
item = item % mod
else:
item = item // 3
if item % self.mod == 0:
monkeys[self.monkey1].items.append(item)
else:
monkeys[self.monkey2].items.append(item)
self.items = []
monkey_strs = [
part.strip()
for part in re.split(r"^$", Path("input11.txt").read_text(), flags=re.MULTILINE)
]
def parse_monkey(s: str):
(n, items_str, op, x, mod, monkey1, monkey2) = re.match(
"""Monkey ([0-9]):
*Starting items: ([0-9, ]+)
*Operation: new = old (.) (.+)
*Test: divisible by ([0-9]+)
*If true: throw to monkey ([0-9]+)
*If false: throw to monkey ([0-9]+)""",
s.strip(),
flags=re.MULTILINE,
).groups()
return Monkey(
int(n),
[int(s) for s in items_str.split(", ")],
op,
x,
int(mod),
int(monkey1),
int(monkey2),
)
def parse_monkeys(monkey_strs):
return [
parse_monkey(monkey_str)
for monkey_str in monkey_strs
if monkey_str.startswith("Monkey")
]
monkeys = parse_monkeys(monkey_strs)
def interact(times: int, mod=None):
for i in range(times):
for monkey in monkeys:
monkey.inspect(monkeys, mod)
interact(20)
i1, i2 = sorted(monkey.inspections for monkey in monkeys)[-2:]
print(f"Part1: {i1*i2}")
monkeys = parse_monkeys(monkey_strs)
mod = reduce(mul, [monkey.mod for monkey in monkeys])
interact(10000, mod)
i1, i2 = sorted(monkey.inspections for monkey in monkeys)[-2:]
print(f"Part2: {i1*i2}")

Day 12

from pathlib import Path
from typing import Iterator
class Grid:
lines: list[list[str]]
pos: tuple[int, int]
target: tuple[int, int]
best: int = 10000
def __init__(self, file_name: str):
self.lines = [
list(iter(line)) for line in Path(file_name).read_text().splitlines()
]
for y, line in enumerate(self.lines):
for x, s in enumerate(line):
if s == "S":
self.lines[y][x] = "a"
self.pos = (y, x)
elif s == "E":
self.lines[y][x] = "z"
self.target = (y, x)
def __str__(self):
return f"""player: {self.pos}
target: {self.target}
""" + "\n".join(
["".join(line) for line in self.lines]
)
def elevation(self, src: tuple[int, int], dst: tuple[int, int]):
if (
dst[0] < 0
or dst[0] >= len(self.lines)
or dst[1] >= len(self.lines[dst[0]])
or dst[1] < 0
):
return 1000
diff = ord(self.lines[dst[0]][dst[1]]) - ord(self.lines[src[0]][src[1]])
return diff
def posible_movements(self, pos: tuple[int, int]) -> Iterator[tuple[int, int]]:
for ydiff in (-1, 1):
if self.elevation(pos, (pos[0] + ydiff, pos[1])) <= 1:
yield (pos[0] + ydiff, pos[1])
for xdiff in (-1, 1):
if self.elevation(pos, (pos[0], pos[1] + xdiff)) <= 1:
yield (pos[0], pos[1] + xdiff)
def traverse(self, start_pos=None):
if start_pos:
self.pos = start_pos
distances = {self.pos: (0, None)}
while self.target not in distances:
prev_keys = list(distances.keys())
progress = False
for q in prev_keys:
cost, prev = distances[q]
for new_pos in self.posible_movements(q):
if new_pos not in distances:
distances[new_pos] = cost + 1, q
progress = True
if not progress:
return
return distances
def starting_positions(self):
return [
(y, x)
for y, row in enumerate(self.lines)
for x, c in enumerate(row)
if c == "a"
]
grid = Grid("input12.txt")
m = grid.traverse()
print(f"Part1: {m[grid.target][0]}")
min_2 = min(
[
z[grid.target][0]
for start_pos in grid.starting_positions()
if (z := grid.traverse(start_pos))
]
)
print(f"Part2: {min_2}")

Day 13

from pathlib import Path
from functools import cmp_to_key
import ast
line_pairs = [lines.split() for lines in Path("input13.txt").read_text().split("\n\n")]
input_pairs = [(ast.literal_eval(a), ast.literal_eval(b)) for a, b in line_pairs]
first_pair = input_pairs[0]
def compare(a, b):
if type(a) is int and type(b) is int:
return a - b
if type(a) is int:
return compare([a], b)
if type(b) is int:
return compare(a, [b])
for x, y in zip(a, b):
if r := compare(x, y):
return r
return len(a) - len(b)
s = sum((i + 1 for i, pair in enumerate(input_pairs) if compare(*pair) < 0))
print(f"Part1: {s}")
DIVIDERS = [[[2]], [[6]]]
all_signals = [p for pair in input_pairs for p in pair] + DIVIDERS
sorted_signals = sorted(all_signals, key=cmp_to_key(compare))
s = (sorted_signals.index(DIVIDERS[0]) + 1) * (sorted_signals.index(DIVIDERS[1]) + 1)
print(f"Part2: {s}")

Day 14

https://github.com/juergenhoetzel/adventofcode2022/blob/86d3174db870c7cb3e7e4c2b1c558fe6a5f2d585/day13.py#L1-L72

Day 15

from pathlib import Path
from dataclasses import dataclass
from functools import reduce
import re
SB_PATTERN = re.compile(
r"^Sensor at x=(-?[0-9]+), y=(-?[0-9]+): closest beacon is at x=(-?[0-9]+), y=(-?[0-9]+)$"
)
@dataclass(frozen=True)
class Sensor:
x: int
y: int
distance: int
@dataclass(frozen=True)
class Beacon:
x: int
y: int
beacons: set[Beacon] = set()
sensors: set[Sensor] = set()
def merge_ranges(ranges):
ret = [ranges[0]]
for low, up in ranges[1:]:
if low - 1 <= ret[-1][1]:
ret[-1] = ret[-1][0], max(ret[-1][1], up)
else:
ret.append((low, up))
return ret
for line in Path("input15.txt").read_text().splitlines():
sx, sy, bx, by = [int(x) for x in SB_PATTERN.match(line).groups()]
distance = abs(sx - bx) + abs(sy - by)
sensors.add(Sensor(sx, sy, distance))
beacons.add(Beacon(bx, by))
Y = 2000000
def get_ranges(sensors, y=Y):
ranges = []
for sensor in sensors:
x_len = sensor.distance - abs(sensor.y - y)
if x_len > 0:
if sensor.y == y:
ranges.append((sensor.x - x_len, sensor.x - 1))
ranges.append((sensor.x + 1, sensor.x + x_len))
else:
ranges.append((sensor.x - x_len, sensor.x + x_len))
ranges.sort()
ranges = merge_ranges(ranges)
return ranges
ranges = get_ranges(sensors, Y)
free = reduce(lambda acc, r: acc + r[1] - r[0], ranges, 0)
print(f"Part1: {free}")
MAX_Y = 4000000
# Brute Force :-(
def get_positions(sensors, max_y=MAX_Y):
for y in range(0, max_y):
ranges = get_ranges(sensors, y)
if len(ranges):
for r1, r2 in zip(ranges, ranges[1:]):
for x in range(r1[1] + 1, r2[0]):
if (
x >= 0
and x < max_y
and x not in (sensor.x for sensor in sensors if sensor.y == y)
and x not in (beacon.x for beacon in beacons if beacon.y == y)
):
yield (y, x)
(y, x) = next(get_positions(sensors, MAX_Y))
frequency = y + x * 4000000
print(f"Part2: {frequency}")

Day 16

TODO

Day 17

from pathlib import Path
from dataclasses import dataclass
from itertools import cycle
movements = Path("input17.txt").read_text().strip()
shapes = [
["@@@@"],
[".@.", "@@@", ".@."],
["..@", "..@", "@@@"],
["@", "@", "@", "@"],
["@@", "@@"],
]
start_positions = [()]
def shape_len(shape: list[str]):
return max([s.rindex("@") for s in shape]) + 1
@dataclass
class Sprite:
x: int
y: int
shape: list[str]
class Game:
board = []
current_sprite: Sprite | None
def __init__(self):
self.board = []
self.current_sprite = None
def freepos_y(self):
for i, row in enumerate(self.board):
if "#" in row or "-" in row:
return i - 1
return len(self.board) - 1
def place_item(self, shape: list[str]):
if not self.board or self.freepos_y() - len(shape) < 10: # Ensure enough space
self.board = [" "] * 7 + self.board # extend board
y = self.freepos_y() - 2 - len(shape)
x = 2
self.current_sprite = Sprite(x, y, shape)
def push(self, s: str):
match s:
case "<":
if self.current_sprite.x > 0:
self.current_sprite.x -= 1
if self.colides():
self.current_sprite.x += 1 # revert
case ">":
if self.current_sprite.x + shape_len(self.current_sprite.shape) < 7:
self.current_sprite.x += 1
if self.colides():
self.current_sprite.x -= 1 # revert
case _:
raise ValueError(f"Invalid movement {s}")
def colides(self) -> bool:
s = self.current_sprite
for y in range(0, len(s.shape)):
if y + s.y >= len(self.board):
return True
for x in range(len(s.shape[y])):
if s.shape[y][x] == "@" and self.board[y + s.y][x + s.x] == "#":
return True
return False
def freeze(self):
s = self.current_sprite
for y in range(0, len(s.shape)):
self.board[y + s.y] = (
self.board[y + s.y][: s.x]
+ "".join(
[
"#" if s == "@" else b
for b, s in zip(self.board[y + s.y][s.x :], s.shape[y])
]
)
+ self.board[y + s.y][s.x + len(s.shape[y]) :]
)
self.current_sprite = None
def fall(self) -> bool:
self.current_sprite.y += 1
if self.colides():
self.current_sprite.y -= 1 # revert
self.freeze()
return False
return True
def height(self) -> int:
return len(self.board) - self.freepos_y() - 1
def __str__(self):
merged_rows = []
for y, row in enumerate(self.board):
s = self.current_sprite
if s and y in range(s.y, s.y + len(s.shape)):
current_line = (
self.board[y][: s.x]
+ "".join(
[
b if s == "." else s
for (b, s) in zip(self.board[y][s.x :], s.shape[y - s.y])
]
)
+ self.board[y][s.x + len(s.shape[y - s.y]) :]
)
else:
current_line = self.board[y]
merged_rows.append(current_line)
return "\n".join(merged_rows)
g = Game()
cycled_shapes = cycle(shapes)
i = 0
for move in cycle(movements):
if not g.current_sprite:
shape = next(cycled_shapes)
if i == 2022:
break
i += 1
g.place_item(shape)
g.push(move)
g.fall()
part1 = g.height()
print(f"Part1: {part1}")

Day 18

from pathlib import Path
cubes = {
tuple([int(c) for c in line.split(",")])
for line in Path("input18.txt").read_text().strip().splitlines()
}
total_sides = 0
for cube in cubes:
x, y, z = cube
sides = 0
for i1, i2, i3 in (
(1, 0, 0),
(-1, 0, 0),
(0, 1, 0),
(0, -1, 0),
(0, 0, 1),
(0, 0, -1),
):
if (x + i1, y + i2, z + i3) not in cubes:
sides += 1
total_sides += sides
print(total_sides)

Day 20

from pathlib import Path
xs = [int(line) for line in Path("input20.txt").read_text().splitlines()]
def decrypt(cs: list[int]):
n = len(cs)
ret = [(c, False) for c in cs]
count = 0
while count < n:
for i in range(n):
c, visited = ret[i]
if not visited:
if c == 0:
ret[i] = (c, True)
count += 1
break
target = (i + c) % (n - 1)
ret.pop(i)
if target == 0: # wrap arround
ret.append((c, True))
else:
ret.insert(target, (c, True))
count += 1
break
return [x for x, _ in ret]
plain = decrypt(xs)
i = plain.index(0)
coord_sum = sum([plain[(i + offset) % len(plain)] for offset in (1000, 2000, 3000)])
print(f"Part1: {coord_sum}")

Day 21

from pathlib import Path
from operator import add, mul, floordiv, sub
import time
lines = Path("input21.txt").read_text().splitlines()
monkeys = {}
for line in lines:
match line.split():
case [nameq, name1, ops, name2]:
monkeys[nameq[:-1]] = (name1, ops, name2)
case [nameq, s]:
monkeys[nameq[:-1]] = int(s)
case _:
raise ValueError(f"Invalid line: {line}")
OPSMAP = {"+": add, "*": mul, "/": floordiv, "-": sub}
def yell(monkey, opsmap=OPSMAP):
match monkeys.get(monkey):
case (name1, op, name2):
return OPSMAP[op](yell(name1), yell(name2))
case n:
return n
print(f"Part1: {yell('root')}")
# FIXME: Don't change constant
OPSMAP["="] = lambda x, y: (x, y)
monkeys["root"] = (monkeys["root"][0], "=", monkeys["root"][2])
# binary search
low, high = 0, 2**64
while True:
pivot = (low + high) // 2
monkeys["humn"] = pivot
l, r = yell("root")
print(l, r, pivot, l - r)
if l == r:
break
if l < r:
high = pivot
else:
low = pivot
print(f"Part2: {pivot}")

Day 22

from pathlib import Path
import re
COMMANDS_RE = re.compile("([0-9]+|[RL])")
DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0)]
class Game:
col: int
row: int
grid: list[str]
commands: list[str | int]
dir_index: int
def __init__(self, path: Path):
lines = path.read_text().splitlines()
self.grid = lines[:-2]
# ensure all lines have same length
m = max([len(line) for line in self.grid])
self.grid = [line.ljust(m) for line in self.grid]
commands_s = lines[-1]
self.commands = [int(token) if token.isdigit() else token for token in COMMANDS_RE.findall(commands_s)]
self.col = self.start_col(0)
self.row = 0
self.dir_index = 0
def start_col(self, y):
return min(self.grid[y].index("."), self.grid[y].index("#"))
def end_col(self, y):
r_col = self.grid[y][::-1]
return len(r_col) - 1 - min(r_col.index("."), r_col.index("#"))
def start_row(self, x):
column = [row[x] for row in self.grid]
return min(column.index("."), column.index("#") if "#" in column else 1000)
def end_row(self, x):
r_column = [row[x] for row in self.grid][::-1]
return len(r_column) - 1 - min(r_column.index("."), r_column.index("#") if "#" in r_column else 1000)
def __str__(self):
return f"""
{self.col}
{self.row}
""" + "\n".join(
self.grid
)
def move(self) -> bool:
d = DIRECTIONS[self.dir_index]
if d[0]: # move row
new_row = self.row + d[0]
if new_row > self.end_row(self.col):
new_row = self.start_row(self.col)
elif new_row < self.start_row(self.col):
new_row = self.end_row(self.col)
if self.grid[new_row][self.col] == "#":
return False
self.row = new_row
return True
elif d[1]:
new_col = self.col + d[1]
if new_col > self.end_col(self.row):
new_col = self.start_col(self.row)
elif new_col < self.start_col(self.row):
new_col = self.end_col(self.row)
if self.grid[self.row][new_col] == "#":
return False
self.col = new_col
return True
def start(self) -> int:
for command in self.commands:
match command:
case int(command):
while command > 0 and self.move():
command -= 1
case "R":
self.dir_index = (self.dir_index + 1) % len(DIRECTIONS) # turn
case "L":
self.dir_index = (self.dir_index - 1) % len(DIRECTIONS) # turn
return (self.row + 1) * 1000 + 4 * (self.col + 1) + self.dir_index
game = Game(Path("input22.txt"))
print(f"Part1: {game.start()}")

Day 23

from itertools import cycle, islice
from operator import attrgetter
from pathlib import Path
class Elf:
x: int
y: int
def __init__(self, y, x):
self.x = x
self.y = y
def __repr__(self):
return f"Elf({self.y, self.x})"
def position(self):
return (self.y, self.x)
class Game:
DIRECTIONS = [(-1, -1), (-1, 0), (-1, 1), (1, -1), (1, 0), (1, 1), (0, -1), (0, 1)]
def __init__(self, file="input.txt"):
lines = Path(file).read_text().splitlines()
self.elfs = [Elf(y, x) for y, line in enumerate(lines) for x, c in enumerate(line) if c == "#"]
self.looking_positions = cycle([(-1, -1), (-1, 0), (-1, 1), (1, -1), (1, 0), (1, 1), (-1, -1), (0, -1), (1, -1), (-1, 1), (0, 1), (1, 1)])
def try_move(self, elf, check_positions, elf_positions) -> None | tuple[int, int]:
final_pos = (elf.y + check_positions[1][0], (elf.x + check_positions[1][1]))
is_occupied = any((elf.y + off_y, elf.x + off_x) in elf_positions for off_y, off_x in check_positions)
if not is_occupied:
return final_pos
def is_elf_around(self, elf, elf_positions):
return any([(elf.y + off_y, elf.x + off_x) in elf_positions for off_y, off_x in Game.DIRECTIONS])
def rect_size(self):
y_min = min(self.elfs, key=attrgetter("y")).y
y_max = max(self.elfs, key=attrgetter("y")).y
x_min = min(self.elfs, key=attrgetter("x")).x
x_max = max(self.elfs, key=attrgetter("x")).x
return (y_max - y_min + 1) * (x_max - x_min + 1) - len(self.elfs)
def move(self) -> int:
elf_positions = {elf.position() for elf in self.elfs}
moves = {}
for _ in range(4): # all directions
check_positions = list(islice(self.looking_positions, 3))
for elf in self.elfs:
if (self.is_elf_around(elf, elf_positions)) and (elf not in moves) and (new_pos := self.try_move(elf, check_positions, elf_positions)):
moves[elf] = new_pos
new_positions = list(moves.values())
for elf, (new_y, new_x) in moves.items():
if new_positions.count((new_y, new_x)) == 1: # not crowded
elf.y = new_y
elf.x = new_x
list(islice(self.looking_positions, 3)) # start in next direction next round
return len(new_positions)
if __name__ == "__main__":
g = Game("input23.txt")
for _ in range(10):
g.move()
print(f"Part1: {g.rect_size()}")
g = Game("input23.txt")
count = 1
while g.move():
count += 1
print(f"Part2: {count}")

About

Advent of Code 2022 Solutions in Python


Languages

Language:Python 100.0%