Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
6 changes: 6 additions & 0 deletions advent_of_code/day_22/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
if __name__ == "__main__":
from .solution import Puzzle
from advent_of_code.runner import attach_cli
from pathlib import Path

attach_cli(Puzzle, Path(__file__).parent)
420 changes: 420 additions & 0 deletions advent_of_code/day_22/assets/input_big.txt

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions advent_of_code/day_22/assets/input_small.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
on x=-5..47,y=-31..22,z=-19..33
on x=-44..5,y=-27..21,z=-14..35
on x=-49..-1,y=-11..42,z=-10..38
on x=-20..34,y=-40..6,z=-44..1
off x=26..39,y=40..50,z=-2..11
on x=-41..5,y=-41..6,z=-36..8
off x=-43..-33,y=-45..-28,z=7..25
on x=-33..15,y=-32..19,z=-34..11
off x=35..47,y=-46..-34,z=-11..5
on x=-14..36,y=-6..44,z=-16..29
on x=-57795..-6158,y=29564..72030,z=20435..90618
on x=36731..105352,y=-21140..28532,z=16094..90401
on x=30999..107136,y=-53464..15513,z=8553..71215
on x=13528..83982,y=-99403..-27377,z=-24141..23996
on x=-72682..-12347,y=18159..111354,z=7391..80950
on x=-1060..80757,y=-65301..-20884,z=-103788..-16709
on x=-83015..-9461,y=-72160..-8347,z=-81239..-26856
on x=-52752..22273,y=-49450..9096,z=54442..119054
on x=-29982..40483,y=-108474..-28371,z=-24328..38471
on x=-4958..62750,y=40422..118853,z=-7672..65583
on x=55694..108686,y=-43367..46958,z=-26781..48729
on x=-98497..-18186,y=-63569..3412,z=1232..88485
on x=-726..56291,y=-62629..13224,z=18033..85226
on x=-110886..-34664,y=-81338..-8658,z=8914..63723
on x=-55829..24974,y=-16897..54165,z=-121762..-28058
on x=-65152..-11147,y=22489..91432,z=-58782..1780
on x=-120100..-32970,y=-46592..27473,z=-11695..61039
on x=-18631..37533,y=-124565..-50804,z=-35667..28308
on x=-57817..18248,y=49321..117703,z=5745..55881
on x=14781..98692,y=-1341..70827,z=15753..70151
on x=-34419..55919,y=-19626..40991,z=39015..114138
on x=-60785..11593,y=-56135..2999,z=-95368..-26915
on x=-32178..58085,y=17647..101866,z=-91405..-8878
on x=-53655..12091,y=50097..105568,z=-75335..-4862
on x=-111166..-40997,y=-71714..2688,z=5609..50954
on x=-16602..70118,y=-98693..-44401,z=5197..76897
on x=16383..101554,y=4615..83635,z=-44907..18747
off x=-95822..-15171,y=-19987..48940,z=10804..104439
on x=-89813..-14614,y=16069..88491,z=-3297..45228
on x=41075..99376,y=-20427..49978,z=-52012..13762
on x=-21330..50085,y=-17944..62733,z=-112280..-30197
on x=-16478..35915,y=36008..118594,z=-7885..47086
off x=-98156..-27851,y=-49952..43171,z=-99005..-8456
off x=2032..69770,y=-71013..4824,z=7471..94418
on x=43670..120875,y=-42068..12382,z=-24787..38892
off x=37514..111226,y=-45862..25743,z=-16714..54663
off x=25699..97951,y=-30668..59918,z=-15349..69697
off x=-44271..17935,y=-9516..60759,z=49131..112598
on x=-61695..-5813,y=40978..94975,z=8655..80240
off x=-101086..-9439,y=-7088..67543,z=33935..83858
off x=18020..114017,y=-48931..32606,z=21474..89843
off x=-77139..10506,y=-89994..-18797,z=-80..59318
off x=8476..79288,y=-75520..11602,z=-96624..-24783
on x=-47488..-1262,y=24338..100707,z=16292..72967
off x=-84341..13987,y=2429..92914,z=-90671..-1318
off x=-37810..49457,y=-71013..-7894,z=-105357..-13188
off x=-27365..46395,y=31009..98017,z=15428..76570
off x=-70369..-16548,y=22648..78696,z=-1892..86821
on x=-53470..21291,y=-120233..-33476,z=-44150..38147
off x=-93533..-4276,y=-16170..68771,z=-104985..-24507
1 change: 1 addition & 0 deletions advent_of_code/day_22/assets/solution_big_one.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
648681
1 change: 1 addition & 0 deletions advent_of_code/day_22/assets/solution_big_two.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1302784472088899
1 change: 1 addition & 0 deletions advent_of_code/day_22/assets/solution_small_one.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
474140
1 change: 1 addition & 0 deletions advent_of_code/day_22/assets/solution_small_two.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2758514936282235
180 changes: 180 additions & 0 deletions advent_of_code/day_22/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
from collections import Counter
from typing import Iterable, Literal, NamedTuple, Optional
from dataclasses import dataclass
import re
from advent_of_code.runner import PuzzleTemplate

CUBE_REGEX = re.compile(r"-?\d+")


class Point(NamedTuple):
x: int
y: int
z: int


def intersects(a_max: Point, a_min: Point, b_max: Point, b_min: Point) -> bool:
# thanks random stranger on the internet: https://stackoverflow.com/a/53488289/8320732
return (
a_max.x >= b_min.x
and a_min.x <= b_max.x
and a_max.y >= b_min.y
and a_min.y <= b_max.y
and a_max.z >= b_min.z
and a_min.z <= b_max.z
)


@dataclass(frozen=True)
class Cube:
max_point: Point
min_point: Point
sign: Literal[1, -1]

@classmethod
def from_string(cls, string: str) -> "Cube":
"""
>>> Cube.from_string('on x=-20..26,y=-36..17,z=-47..7')
Cube(max_point=Point(x=26, y=17, z=7), min_point=Point(x=-20, y=-36, z=-47), sign=1)

>>> Cube.from_string('off x=-48..-32,y=26..41,z=-47..-37')
Cube(max_point=Point(x=-32, y=41, z=-37), min_point=Point(x=-48, y=26, z=-47), sign=-1)
"""
sign, rest = string.split(" ")
x_min, x_max, y_min, y_max, z_min, z_max = map(int, CUBE_REGEX.findall(rest))
max_point = Point(x_max, y_max, z_max)
min_point = Point(x_min, y_min, z_min)
return cls(min_point=min_point, max_point=max_point, sign=1 if sign == "on" else -1)

def crop(
self, max_point: Point = Point(50, 50, 50), min_point: Point = Point(-50, -50, -50)
) -> Optional["Cube"]:
"""
>>> Cube.from_string('off x=-48..-32,y=26..41,z=-47..-37').crop()
Cube(max_point=Point(x=-32, y=41, z=-37), min_point=Point(x=-48, y=26, z=-47), sign=-1)

>>> Cube.from_string('off x=-52..52,y=-52..52,z=-52..52').crop()
Cube(max_point=Point(x=50, y=50, z=50), min_point=Point(x=-50, y=-50, z=-50), sign=-1)

>>> Cube.from_string('off x=100..110,y=100..110,z=100..110').crop()
"""
if not intersects(self.max_point, self.min_point, max_point, min_point):
return None

new_max_point = Point(*map(lambda x: max(-50, min(50, x)), self.max_point))
new_min_point = Point(*map(lambda x: max(-50, min(50, x)), self.min_point))
Comment on lines +64 to +65
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover constant from old version of the code.


return Cube(max_point=new_max_point, min_point=new_min_point, sign=self.sign)

def area(self) -> int:
"""
>>> Cube.from_string('on x=10..12,y=10..12,z=10..12').area()
27

>>> Cube.from_string('off x=10..12,y=10..12,z=10..12').area()
-27
"""
return (
(self.max_point.x - self.min_point.x + 1)
* (self.max_point.y - self.min_point.y + 1)
* (self.max_point.z - self.min_point.z + 1)
) * self.sign

def opposite(self) -> "Cube":
"""
>>> a = Cube.from_string('on x=10..12,y=10..12,z=10..12').opposite()
>>> b = Cube.from_string('off x=10..12,y=10..12,z=10..12')
>>> a == b
True
"""
return Cube(min_point=self.min_point, max_point=self.max_point, sign=-self.sign)

def intersection(self, other: "Cube") -> Optional["Cube"]:
"""
>>> a = Cube.from_string('on x=10..12,y=10..12,z=10..12')
>>> b = Cube.from_string('on x=11..13,y=11..13,z=11..13')
>>> intersection = a.intersection(b)
>>> intersection
Cube(max_point=Point(x=12, y=12, z=12), min_point=Point(x=11, y=11, z=11), sign=-1)
>>> intersection.area()
-8

>>> c = Cube.from_string('on x=5..8,y=5..8,z=5..8')
>>> a.intersection(c)
"""
# test if there is intersection
if not intersects(self.max_point, self.min_point, other.max_point, other.min_point):
return None

# we're adding some cube twice -> subtract it once
# we're subtracting some cube twice -> add it once
if self.sign == other.sign:
new_sign = -self.sign
# we need to switch off part of lit cube
# this cube is negative (it turned of some other cube) -> we need to light it back up
else:
new_sign = other.sign

new_max = Point(
min(self.max_point.x, other.max_point.x),
min(self.max_point.y, other.max_point.y),
min(self.max_point.z, other.max_point.z),
)

new_min = Point(
max(self.min_point.x, other.min_point.x),
max(self.min_point.y, other.min_point.y),
max(self.min_point.z, other.min_point.z),
)

return Cube(min_point=new_min, max_point=new_max, sign=new_sign)

def __str__(self) -> str:
sign = "on" if self.sign == 1 else "off"
return f"{sign} x={self.min_point.x}..{self.max_point.x},y={self.min_point.y}..{self.max_point.y},z={self.min_point.z}..{self.max_point.z}"


@dataclass
class Puzzle(PuzzleTemplate):
cubes: list[Cube]

@classmethod
def from_lines(cls, lines: Iterable[str]) -> "Puzzle":
cubes = [Cube.from_string(line.strip()) for line in lines]

return cls(cubes=cubes)

def solve(self) -> Counter[Cube]:
final_cubes = Counter()

for new_cube in self.cubes:
new_cubes = []

for cube in final_cubes.keys():
if intersection := cube.intersection(new_cube):
new_cubes.append(intersection)

if new_cube.sign == 1:
# we cannot add a negative cube, because everything is turned off
new_cubes.append(new_cube)

for cube in new_cubes:
# if we have one cube in "on" and the same one in "off", they will cancel out
opposite = cube.opposite()
if opposite in final_cubes:
final_cubes[opposite] -= 1
if final_cubes[opposite] == 0:
del final_cubes[opposite]
else:
final_cubes[cube] += 1

return final_cubes

def task_one(self) -> int:
cubes = self.solve()
cropped = (cube.crop() for cube in cubes)
return sum(cube.area() for cube in cropped if cube)

def task_two(self) -> int:
cubes = self.solve()
return sum(cube.area() for cube in cubes)