Skip to content

Commit c91d187

Browse files
committed
Add 2021/22
1 parent 6510a65 commit c91d187

File tree

4 files changed

+117
-137
lines changed

4 files changed

+117
-137
lines changed

README.md

-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ My solutions to [Advent of Code](https://adventofcode.com/).
44

55
- Find where I saved solutions to years 2017, 2019, 2020
66
- Solve part 2 of day 23 of year 2018
7-
- Solve part 2 of day 22 of year 2021
87
- Improve solution to day 13 of year 2022 by using JSON crate
98
- Improve solution to day 16 of year 2022 by merging zero-rate valves
109
- Solve part 2 of day 23 of year 2016 by simplifying the input code instead of leaving the program to run for an hour
1110
- Solve part 2 of day 5 of year 2023 by splitting the biggest range (just one range would effectively halve the execution time)
1211
- An approach that is not suboptimal involves operating on ranges, but I left that to smarter people
13-

year2021/day22/sample_2d.txt

-4
This file was deleted.

year2021/day22/sol2.py

+117-79
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,136 @@
1+
"""Day 22"""
2+
13
import sys
24
import re
35
from dataclasses import dataclass
6+
import functools
47
from typing import List
58

6-
LINE_REGEX = r"^(\w*) x=(.*)\.\.(.*),y=(.*)\.\.(.*)$"
9+
from sortedcontainers import SortedList
10+
11+
LINE_REGEX = r"^(\w*) x=(.*)\.\.(.*),y=(.*)\.\.(.*),z=(.*)\.\.(.*)$"
12+
713

814
@dataclass(frozen=True)
915
class Cuboid:
16+
"""Cuboid abstraction"""
17+
1018
x1: int
1119
x2: int
1220
y1: int
1321
y2: int
14-
state: str
22+
z1: int
23+
z2: int
24+
on: bool
25+
time: int
26+
27+
28+
def parse_state(state_str: str) -> bool:
29+
"""Parse onness"""
30+
if state_str == "on":
31+
return True
32+
if state_str == "off":
33+
return False
34+
raise ValueError(f"Invalid state: {state_str}")
35+
36+
37+
def read_cuboids() -> List[Cuboid]:
38+
"""Read from stdin"""
39+
40+
cuboids: List[Cuboid] = []
41+
for time, line in enumerate(l for l in sys.stdin if not l.startswith("#")):
42+
state, *numeric_input = re.search(LINE_REGEX, line.strip()).groups()
43+
cuboid = Cuboid(*map(int, numeric_input), on=parse_state(state), time=time)
44+
cuboids.append(cuboid)
45+
46+
return cuboids
47+
48+
49+
@dataclass(frozen=True)
50+
@functools.total_ordering
51+
class CuboidEvent:
52+
"""Cuboid start or end"""
53+
54+
dist: int
55+
start: bool
56+
"""Start or end"""
57+
on: bool
1558
time: int
1659

17-
class LightTracker:
18-
def __init__(self):
19-
self.offs = set()
20-
self.ons = set()
21-
22-
def track_event(self, is_start: bool, cuboid: Cuboid):
23-
if cuboid.state == "on":
24-
if is_start:
25-
self.ons.add(cuboid.time)
26-
else:
27-
self.ons.remove(cuboid.time)
28-
elif cuboid.state == "off":
29-
if is_start:
30-
self.offs.add(cuboid.time)
31-
else:
32-
self.offs.remove(cuboid.time)
33-
else:
34-
raise ValueError
35-
36-
def currently_on(self):
37-
last_on = -1 if not self.ons else max(self.ons)
38-
last_off = -1 if not self.offs else max(self.offs)
39-
if last_on == last_off:
40-
if last_on == -1:
60+
def _to_comparable(self):
61+
return (self.dist, self.start, self.on, self.time)
62+
63+
def __lt__(self, other: "CuboidEvent") -> bool:
64+
# return (self.dist, self.start) < (other.dist, other.start)
65+
if self.dist == other.dist:
66+
if self.start == other.start:
4167
return False
42-
raise ValueError
43-
return last_on > last_off
44-
45-
cuboids: List[Cuboid] = []
46-
for time, line in enumerate(sys.stdin):
47-
if line.startswith("#"):
48-
continue
49-
state, *numeric_input = re.search(LINE_REGEX, line.strip()).groups()
50-
x1, x2, y1, y2 = map(int, numeric_input)
51-
cuboid = Cuboid(x1, x2, y1, y2, state, time)
52-
cuboids.append(cuboid)
53-
54-
def create_events(cuboids):
68+
return self.start is False # ending event has precedence
69+
return self.dist < other.dist
70+
71+
72+
def to_axis_events(
73+
cuboids: List[Cuboid], start_prop: str, end_prop: str
74+
) -> List[CuboidEvent]:
75+
"""Convert list of cuboids to list of cuboid events"""
5576
events = []
56-
for cuboid in cuboids:
57-
events.append((True, cuboid))
58-
events.append((False, cuboid))
77+
for c in cuboids:
78+
events.append(
79+
CuboidEvent(dist=getattr(c, start_prop), start=True, on=c.on, time=c.time)
80+
)
81+
events.append(
82+
CuboidEvent(
83+
dist=getattr(c, end_prop) + 1, start=False, on=c.on, time=c.time
84+
)
85+
)
86+
5987
return events
6088

61-
def get_cuboids(events, predicate):
62-
cuboids = set()
63-
for _, cuboid in events:
64-
if predicate(cuboid):
65-
cuboids.add(cuboid)
66-
return cuboids
6789

68-
x_events = create_events(cuboids)
69-
print("DEBUG x_events", x_events)
70-
area = 0
71-
last_x = None
72-
for is_start, x_cuboid in sorted(x_events, key=lambda e: (e[1].x1 if e[0] else e[1].x2, e[0])): # sort using tuple to give precedence to closing events
73-
print("DEBUG is_start, x_cuboid", is_start, x_cuboid)
74-
x = x_cuboid.x1 if is_start else x_cuboid.x2
75-
y_events = create_events(get_cuboids(x_events, lambda c: c.x1 <= x <= c.x2))
76-
# TODO create counter for how many new cuboids at e.g. x==2 (solves case of multiple starting/ending on same line)
77-
last_y = None
78-
y_length = 0
79-
# TODO does x axis have to take care of whether it currently is in light or not?
80-
light_tracker = LightTracker()
81-
for is_start, y_cuboid in sorted(y_events, key=lambda e: (e[1].y1 if e[0] else e[1].y2, e[0])):
82-
y = y_cuboid.y1 if is_start else y_cuboid.y2
83-
if last_y is not None and light_tracker.currently_on():
84-
y_length += y - last_y + 1
85-
print("DEBUG y_length", y_length)
86-
else:
87-
y_length = 0
88-
89-
light_tracker.track_event(is_start, y_cuboid)
90-
last_y = y
91-
92-
if last_x is not None:
93-
x_diff = x - last_x + 1
94-
print("DEBUG x_diff", x_diff)
95-
area += y_length * x_diff
96-
last_x = x
97-
98-
print(area)
90+
def main():
91+
"""Main function"""
92+
93+
cuboids = read_cuboids()
94+
95+
on_count = 0
96+
97+
x_events = sorted(to_axis_events(cuboids, start_prop="x1", end_prop="x2"))
98+
last_y_count = 0
99+
for x_i, x_event in enumerate(x_events):
100+
y_suitable_cuboids = [c for c in cuboids if c.x1 <= x_event.dist <= c.x2]
101+
y_count = 0
102+
y_events = sorted(
103+
to_axis_events(y_suitable_cuboids, start_prop="y1", end_prop="y2")
104+
)
105+
last_z_count = 0
106+
for y_i, y_event in enumerate(y_events):
107+
active_timestamps = SortedList()
108+
z_count = 0
109+
z_suitable_cuboids = [
110+
c for c in y_suitable_cuboids if c.y1 <= y_event.dist <= c.y2
111+
]
112+
z_events = sorted(
113+
to_axis_events(z_suitable_cuboids, start_prop="z1", end_prop="z2")
114+
)
115+
for z_i, z_event in enumerate(z_events):
116+
if active_timestamps:
117+
active_cuboid = cuboids[active_timestamps[-1]]
118+
if active_cuboid.on:
119+
z_count += z_event.dist - z_events[z_i - 1].dist
120+
121+
if z_event.start:
122+
active_timestamps.add(z_event.time)
123+
else:
124+
active_timestamps.remove(z_event.time)
125+
126+
y_count += (y_event.dist - y_events[y_i - 1].dist) * last_z_count
127+
last_z_count = z_count
128+
129+
on_count += (x_event.dist - x_events[x_i - 1].dist) * last_y_count
130+
last_y_count = y_count
131+
132+
print(on_count)
133+
134+
135+
if __name__ == "__main__":
136+
main()

year2021/day22/sol2_intersectional.py

-52
This file was deleted.

0 commit comments

Comments
 (0)