Skip to content

Commit feddf26

Browse files
committed
day14
1 parent 21a7a9c commit feddf26

File tree

7 files changed

+229
-10
lines changed

7 files changed

+229
-10
lines changed

.run/Python tests in part1.py.run.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
<option name="PARENT_ENVS" value="true"/>
77
<option name="SDK_HOME" value="D:\DEV\Python\Projects\aoc2022\venv\Scripts\python.exe"/>
88
<option name="SDK_NAME" value="Python 3.10 (aoc2022)"/>
9-
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/day13"/>
9+
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/day14"/>
1010
<option name="IS_MODULE_SDK" value="false"/>
1111
<option name="ADD_CONTENT_ROOTS" value="true"/>
1212
<option name="ADD_SOURCE_ROOTS" value="true"/>
1313
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py"/>
14-
<option name="_new_additionalArguments" value="&quot;&quot;"/>
15-
<option name="_new_target" value="&quot;$PROJECT_DIR$/day13/part1.py&quot;"/>
14+
<option name="_new_additionalArguments" value="&quot;-s&quot;"/>
15+
<option name="_new_target" value="&quot;$PROJECT_DIR$/day14/part1.py&quot;"/>
1616
<option name="_new_targetType" value="&quot;PATH&quot;"/>
1717
<method v="2"/>
1818
</configuration>

.run/Python tests in part2.py.run.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
<option name="PARENT_ENVS" value="true"/>
66
<option name="SDK_HOME" value="D:\DEV\Python\Projects\aoc2022\venv\Scripts\python.exe"/>
77
<option name="SDK_NAME" value="Python 3.10 (aoc2022)"/>
8-
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/day13"/>
8+
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/day14"/>
99
<option name="IS_MODULE_SDK" value="false"/>
1010
<option name="ADD_CONTENT_ROOTS" value="true"/>
1111
<option name="ADD_SOURCE_ROOTS" value="true"/>
1212
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py"/>
13-
<option name="_new_additionalArguments" value="&quot;&quot;"/>
14-
<option name="_new_target" value="&quot;$PROJECT_DIR$/day13/part2.py&quot;"/>
13+
<option name="_new_additionalArguments" value="&quot;-s&quot;"/>
14+
<option name="_new_target" value="&quot;$PROJECT_DIR$/day14/part2.py&quot;"/>
1515
<option name="_new_targetType" value="&quot;PATH&quot;"/>
1616
<method v="2"/>
1717
</configuration>

.run/part1.run.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
</envs>
99
<option name="SDK_HOME" value="D:\DEV\Python\Projects\aoc2022\venv\Scripts\python.exe"/>
1010
<option name="SDK_NAME" value="Python 3.10 (aoc2022)"/>
11-
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/day13"/>
11+
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/day14"/>
1212
<option name="IS_MODULE_SDK" value="false"/>
1313
<option name="ADD_CONTENT_ROOTS" value="true"/>
1414
<option name="ADD_SOURCE_ROOTS" value="true"/>
1515
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py"/>
16-
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/day13/part1.py"/>
16+
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/day14/part1.py"/>
1717
<option name="PARAMETERS" value="input.txt"/>
1818
<option name="SHOW_COMMAND_LINE" value="false"/>
1919
<option name="EMULATE_TERMINAL" value="false"/>

.run/part2.run.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
</envs>
99
<option name="SDK_HOME" value="D:\DEV\Python\Projects\aoc2022\venv\Scripts\python.exe"/>
1010
<option name="SDK_NAME" value="Python 3.10 (aoc2022)"/>
11-
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/day13"/>
11+
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/day14"/>
1212
<option name="IS_MODULE_SDK" value="false"/>
1313
<option name="ADD_CONTENT_ROOTS" value="true"/>
1414
<option name="ADD_SOURCE_ROOTS" value="true"/>
1515
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py"/>
16-
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/day13/part2.py"/>
16+
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/day14/part2.py"/>
1717
<option name="PARAMETERS" value="input.txt"/>
1818
<option name="SHOW_COMMAND_LINE" value="false"/>
1919
<option name="EMULATE_TERMINAL" value="false"/>

day14/__init__.py

Whitespace-only changes.

day14/part1.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import argparse
2+
import itertools
3+
import os.path
4+
import re
5+
from itertools import pairwise
6+
7+
import pytest
8+
9+
from support import Direction4
10+
from support import timing
11+
12+
INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt')
13+
14+
# NOTE: paste test text here
15+
INPUT_S = '''\
16+
498,4 -> 498,6 -> 496,6
17+
503,4 -> 502,4 -> 502,9 -> 494,9
18+
'''
19+
EXPECTED = 24
20+
21+
BLOCKED = set()
22+
23+
24+
def by_pairs(iterable):
25+
"s -> (s0, s1), (s2, s3), (s4, s5), ..."
26+
a = iter(iterable)
27+
return zip(a, a)
28+
29+
30+
def parse_walls(s):
31+
global BLOCKED
32+
lines = s.splitlines()
33+
for line in lines:
34+
coords = by_pairs(map(int, re.findall(r'\d+', line)))
35+
for (a_x, a_y), (o_x, o_y) in pairwise(coords):
36+
if a_x == o_x:
37+
range_start, range_stop = min(a_y, o_y), max(a_y, o_y)
38+
BLOCKED.update(set((a_x, yy) for yy in range(range_start, range_stop + 1)))
39+
else:
40+
range_start, range_stop = min(a_x, o_x), max(a_x, o_x)
41+
BLOCKED.update(set((xx, a_y) for xx in range(range_start, range_stop + 1)))
42+
43+
44+
def left(pos):
45+
pos = Direction4.DOWN.apply(*pos)
46+
return Direction4.LEFT.apply(*pos)
47+
48+
49+
def right(pos):
50+
pos = Direction4.DOWN.apply(*pos)
51+
return Direction4.RIGHT.apply(*pos)
52+
53+
54+
def blocked(pos):
55+
return pos in BLOCKED
56+
57+
58+
def compute(s: str) -> int:
59+
parse_walls(s)
60+
abbys_level = max(y for _, y in BLOCKED)
61+
dispenser_pos = (500, 0)
62+
num_sand = 1
63+
64+
for num_sand in itertools.count():
65+
pos = dispenser_pos
66+
while True:
67+
68+
if Direction4.DOWN.apply(*pos) not in BLOCKED:
69+
pos = Direction4.DOWN.apply(*pos)
70+
elif left(pos) not in BLOCKED:
71+
pos = left(pos)
72+
elif right(pos) not in BLOCKED:
73+
pos = right(pos)
74+
else:
75+
BLOCKED.add(pos)
76+
break
77+
78+
if pos[1] > abbys_level:
79+
return num_sand
80+
return num_sand
81+
82+
83+
@pytest.mark.solved
84+
@pytest.mark.parametrize(
85+
('input_s', 'expected'),
86+
(
87+
(INPUT_S, EXPECTED),
88+
),
89+
)
90+
def test(input_s: str, expected: int) -> None:
91+
assert compute(input_s) == expected
92+
93+
94+
def main() -> int:
95+
parser = argparse.ArgumentParser()
96+
parser.add_argument('data_file', nargs='?', default=INPUT_TXT)
97+
args = parser.parse_args()
98+
99+
with open(args.data_file) as f, timing():
100+
print(compute(f.read()))
101+
102+
return 0
103+
104+
105+
if __name__ == '__main__':
106+
raise SystemExit(main())

day14/part2.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import argparse
2+
import itertools
3+
import os.path
4+
import re
5+
from itertools import pairwise
6+
7+
import pytest
8+
9+
from support import Direction4
10+
from support import timing
11+
12+
INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt')
13+
14+
# NOTE: paste test text here
15+
INPUT_S = '''\
16+
498,4 -> 498,6 -> 496,6
17+
503,4 -> 502,4 -> 502,9 -> 494,9
18+
'''
19+
EXPECTED = 93
20+
21+
BLOCKED = set()
22+
23+
24+
def by_pairs(iterable):
25+
"s -> (s0, s1), (s2, s3), (s4, s5), ..."
26+
a = iter(iterable)
27+
return zip(a, a)
28+
29+
30+
def parse_walls(s):
31+
global BLOCKED
32+
lines = s.splitlines()
33+
for line in lines:
34+
coords = by_pairs(map(int, re.findall(r'\d+', line)))
35+
for (a_x, a_y), (o_x, o_y) in pairwise(coords):
36+
if a_x == o_x:
37+
range_start, range_stop = min(a_y, o_y), max(a_y, o_y)
38+
BLOCKED.update(set((a_x, yy) for yy in range(range_start, range_stop + 1)))
39+
else:
40+
range_start, range_stop = min(a_x, o_x), max(a_x, o_x)
41+
BLOCKED.update(set((xx, a_y) for xx in range(range_start, range_stop + 1)))
42+
43+
44+
def left(pos):
45+
pos = Direction4.DOWN.apply(*pos)
46+
return Direction4.LEFT.apply(*pos)
47+
48+
49+
def right(pos):
50+
pos = Direction4.DOWN.apply(*pos)
51+
return Direction4.RIGHT.apply(*pos)
52+
53+
54+
def blocked(pos):
55+
return pos in BLOCKED
56+
57+
58+
def compute(s: str) -> int:
59+
parse_walls(s)
60+
abbys_level = max(y for _, y in BLOCKED)
61+
s += f'0,{abbys_level + 2} -> 1000,{abbys_level + 2}'
62+
parse_walls(s)
63+
64+
dispenser_pos = (500, 0)
65+
num_sand = 1
66+
67+
for num_sand in itertools.count():
68+
pos = dispenser_pos
69+
while True:
70+
71+
if pos in BLOCKED:
72+
# now blocked is directly under the dispenser
73+
return num_sand
74+
elif pos[1] == abbys_level + 1:
75+
# effectively draws a line, checks the bottom border
76+
BLOCKED.add(pos)
77+
break
78+
79+
if Direction4.DOWN.apply(*pos) not in BLOCKED:
80+
pos = Direction4.DOWN.apply(*pos)
81+
elif left(pos) not in BLOCKED:
82+
pos = left(pos)
83+
elif right(pos) not in BLOCKED:
84+
pos = right(pos)
85+
else:
86+
BLOCKED.add(pos)
87+
break
88+
89+
90+
@pytest.mark.solved
91+
@pytest.mark.parametrize(
92+
('input_s', 'expected'),
93+
(
94+
(INPUT_S, EXPECTED),
95+
),
96+
)
97+
def test(input_s: str, expected: int) -> None:
98+
assert compute(input_s) == expected
99+
100+
101+
def main() -> int:
102+
parser = argparse.ArgumentParser()
103+
parser.add_argument('data_file', nargs='?', default=INPUT_TXT)
104+
args = parser.parse_args()
105+
106+
with open(args.data_file) as f, timing():
107+
print(compute(f.read()))
108+
109+
return 0
110+
111+
112+
if __name__ == '__main__':
113+
raise SystemExit(main())

0 commit comments

Comments
 (0)