2022-17
This commit is contained in:
parent
c1505bb80f
commit
884a90b551
9 changed files with 326 additions and 18 deletions
1
challenges/2022/17-pyroclasticFlow/README.md
Normal file
1
challenges/2022/17-pyroclasticFlow/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
# [Day 17: Pyroclastic Flow](https://adventofcode.com/2022/day/17)
|
15
challenges/2022/17-pyroclasticFlow/benchmark.json
Normal file
15
challenges/2022/17-pyroclasticFlow/benchmark.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"day": 17,
|
||||
"dir": "challenges/2022/17-pyroclasticFlow",
|
||||
"implementations": {
|
||||
"Python": {
|
||||
"part.1.avg": 0.5983425378799438,
|
||||
"part.1.max": 0.6658821105957031,
|
||||
"part.1.min": 0.5308029651641846,
|
||||
"part.2.avg": 12.261357307434082,
|
||||
"part.2.max": 12.877821207046509,
|
||||
"part.2.min": 11.644893407821655
|
||||
}
|
||||
},
|
||||
"numRuns": 2
|
||||
}
|
17
challenges/2022/17-pyroclasticFlow/info.json
Normal file
17
challenges/2022/17-pyroclasticFlow/info.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"inputFile": "input.txt",
|
||||
"testCases": {
|
||||
"one": [
|
||||
{
|
||||
"input": ">>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>",
|
||||
"expected": "3068"
|
||||
}
|
||||
],
|
||||
"two": [
|
||||
{
|
||||
"input": ">>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>",
|
||||
"expected": "1514285714288"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
233
challenges/2022/17-pyroclasticFlow/py/__init__.py
Normal file
233
challenges/2022/17-pyroclasticFlow/py/__init__.py
Normal file
|
@ -0,0 +1,233 @@
|
|||
from typing import *
|
||||
from aocpy import BaseChallenge, Vector, RepeatingConsumer, foldl
|
||||
|
||||
Tube = Dict[Vector, None]
|
||||
Rock = Tuple[int, int, int, int, int]
|
||||
|
||||
|
||||
TUBE_WIDTH = 7
|
||||
|
||||
# Coordinate system:
|
||||
# ^
|
||||
# |
|
||||
# x --------->
|
||||
# |
|
||||
# y
|
||||
|
||||
|
||||
def parse_rock(x: str) -> Tuple[Tuple[Vector], Tuple[Vector]]:
|
||||
res = []
|
||||
for y, line in enumerate(reversed(x.strip().splitlines())):
|
||||
for x, char in enumerate(line):
|
||||
if char == "#":
|
||||
res.append(Vector(x, y))
|
||||
|
||||
height = y + 1
|
||||
width = x + 1
|
||||
|
||||
left = []
|
||||
right = []
|
||||
for y in range(height):
|
||||
cx = tuple(coord.x for coord in res if coord.y == y)
|
||||
min_x = min(cx)
|
||||
max_x = max(cx)
|
||||
|
||||
left.append(Vector(min_x, y))
|
||||
right.append(Vector(max_x, y))
|
||||
|
||||
below = []
|
||||
for x in range(width):
|
||||
min_y = min(coord.y for coord in res if coord.x == x)
|
||||
below.append(Vector(x, min_y - 1))
|
||||
|
||||
return tuple(res), tuple(left), tuple(right), tuple(below), width
|
||||
|
||||
|
||||
ROCKS: List[Rock] = [
|
||||
parse_rock(lines)
|
||||
for lines in "####\n\n.#.\n###\n.#.\n\n..#\n..#\n###\n\n#\n#\n#\n#\n\n##\n##".split(
|
||||
"\n\n"
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def get_num_rows(tube: Tube) -> int:
|
||||
filled_spaces = tube.keys()
|
||||
return max(x.y for x in filled_spaces) + 1 if len(filled_spaces) != 0 else 0
|
||||
|
||||
|
||||
def get_rock_position(
|
||||
rock: Rock, instructions: RepeatingConsumer, tube: Tube
|
||||
) -> Vector:
|
||||
(_, slots_left, slots_right, slots_below, rock_width) = rock
|
||||
|
||||
filled_spaces = tube.keys()
|
||||
position = Vector(2, get_num_rows(tube) + 3)
|
||||
|
||||
while True:
|
||||
shift_direction = instructions.take()
|
||||
|
||||
delta_x = 0
|
||||
|
||||
if shift_direction == "<":
|
||||
if position.x - 1 >= 0:
|
||||
delta_x = -1
|
||||
elif shift_direction == ">":
|
||||
if position.x + rock_width < TUBE_WIDTH:
|
||||
# rock_width includes a +1 for our needs
|
||||
delta_x = 1
|
||||
else:
|
||||
raise ValueError("invalid shift direction")
|
||||
|
||||
if delta_x != 0:
|
||||
slots = slots_left if delta_x < 0 else slots_right
|
||||
can_move = foldl(
|
||||
lambda x, y: x and y,
|
||||
(
|
||||
((p.x + position.x + delta_x, p.y + position.y) not in tube)
|
||||
for p in slots
|
||||
),
|
||||
True,
|
||||
)
|
||||
|
||||
if can_move:
|
||||
position.x += delta_x
|
||||
|
||||
can_move_down = False
|
||||
if position.y != 0:
|
||||
can_move_down = foldl(
|
||||
lambda x, y: x and y,
|
||||
(
|
||||
((p.x + position.x, p.y + position.y) not in tube)
|
||||
for p in slots_below
|
||||
),
|
||||
True,
|
||||
)
|
||||
|
||||
if not can_move_down:
|
||||
break
|
||||
|
||||
position.y -= 1
|
||||
|
||||
return position
|
||||
|
||||
|
||||
def add_rock(rock: Rock, position: Vector, tube: Tube):
|
||||
rock_shape = rock[0]
|
||||
for p in rock_shape:
|
||||
tube[Vector(position.x + p.x, position.y + p.y)] = None
|
||||
|
||||
|
||||
def get_row_bitmap(tube: Tube, y: int) -> int:
|
||||
n = 0
|
||||
for i in range(TUBE_WIDTH):
|
||||
n = n << 1
|
||||
n += 1 if (i, y) in tube else 0
|
||||
return n
|
||||
|
||||
|
||||
def check_for_sequences(tube: Tube) -> Optional[Tuple[int, int]]:
|
||||
rows = get_num_rows(tube)
|
||||
|
||||
segment_size = rows // 2
|
||||
|
||||
if segment_size % 100 == 0:
|
||||
print(f"starting segment size {segment_size}", flush=True)
|
||||
|
||||
while segment_size > 30:
|
||||
possible_positions = (rows - (segment_size * 2)) + 1
|
||||
|
||||
for i in range(possible_positions):
|
||||
continuous = True
|
||||
for n in range(segment_size):
|
||||
|
||||
for x in range(TUBE_WIDTH):
|
||||
if (x, i + n) in tube != (x, i + n + segment_size) in tube:
|
||||
continuous = False
|
||||
break
|
||||
|
||||
if not continuous:
|
||||
break
|
||||
|
||||
if continuous:
|
||||
return segment_size, i
|
||||
|
||||
segment_size -= 1
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def count_rocks_to_height(
|
||||
tube: Tube, rocks: RepeatingConsumer, instructions: RepeatingConsumer, height: int
|
||||
) -> int:
|
||||
n = 0
|
||||
while get_num_rows(tube) != height:
|
||||
rock = rocks.take()
|
||||
pos = get_rock_position(rock, instructions, tube)
|
||||
add_rock(rock, pos, tube)
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def get_heights(tube: Tube) -> Tuple[Tuple[int], int]:
|
||||
max_height = get_num_rows(tube)
|
||||
res = []
|
||||
for x in range(TUBE_WIDTH):
|
||||
largest = 0
|
||||
for v in (p.y for p in tube if p.x == x):
|
||||
if v > largest:
|
||||
largest = v
|
||||
res.append(max_height - largest)
|
||||
return tuple(res), max_height
|
||||
|
||||
|
||||
class Challenge(BaseChallenge):
|
||||
@staticmethod
|
||||
def one(instr: str) -> int:
|
||||
tube: Tube = {}
|
||||
instructions = RepeatingConsumer(instr.strip())
|
||||
rocks = RepeatingConsumer(ROCKS)
|
||||
|
||||
for _ in range(2022):
|
||||
rock = rocks.take()
|
||||
pos = get_rock_position(rock, instructions, tube)
|
||||
add_rock(rock, pos, tube)
|
||||
|
||||
return get_num_rows(tube)
|
||||
|
||||
@staticmethod
|
||||
def two(instr: str) -> int:
|
||||
tube: Tube = {}
|
||||
instructions = RepeatingConsumer(instr.strip())
|
||||
rocks = RepeatingConsumer(ROCKS)
|
||||
|
||||
seen: Dict[int, int] = {}
|
||||
|
||||
target = 1000000000000
|
||||
|
||||
num_rocks_thrown = 0
|
||||
while num_rocks_thrown < target:
|
||||
rock = rocks.take()
|
||||
pos = get_rock_position(rock, instructions, tube)
|
||||
add_rock(rock, pos, tube)
|
||||
|
||||
col_heights, max_height = get_heights(tube)
|
||||
h = (rocks.i, instructions.i, *col_heights)
|
||||
if h in seen:
|
||||
cycle_begins, height_at_cycle_begin = seen[h]
|
||||
|
||||
rocks_thrown_per_cycle = num_rocks_thrown - cycle_begins
|
||||
height_increase_per_cycle = max_height - height_at_cycle_begin
|
||||
|
||||
rocks_left_to_throw = target - num_rocks_thrown
|
||||
full_cycles_remaining = rocks_left_to_throw // rocks_thrown_per_cycle
|
||||
num_rocks_thrown += rocks_thrown_per_cycle * full_cycles_remaining
|
||||
|
||||
# so we don't get any more cache hit while we run through the last few
|
||||
seen = {}
|
||||
else:
|
||||
seen[h] = (num_rocks_thrown, max_height)
|
||||
|
||||
num_rocks_thrown += 1
|
||||
|
||||
return get_num_rows(tube) + (height_increase_per_cycle * full_cycles_remaining)
|
|
@ -4,6 +4,8 @@ Solutions to the [2022 Advent of Code](https://adventofcode.com/2022).
|
|||
|
||||
---
|
||||
|
||||
The red dotted line denotes 15 seconds.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
@ -14,19 +16,20 @@ Solutions to the [2022 Advent of Code](https://adventofcode.com/2022).
|
|||
|
||||
| Day | Status | Solutions | Notes |
|
||||
| ----------------------------------- | ------------------ | ---------- | ------ |
|
||||
| 01 - Calorie Counting | ★ ★ | [Python](01-calorieCounting/py), [Nim](01-calorieCounting/nim), [Java](01-calorieCounting/java/src) | Summing numbers |
|
||||
| 02 - Rock Paper Scissors | ★ ★ | [Python](02-rockPaperScissors/py), [Nim](02-rockPaperScissors/nim) | Programmatically playing Rock Paper Scissors |
|
||||
| 03 - Rucksack Reorganization | ★ ★ | [Python](03-rucksackReorganization/py), [Nim](03-rucksackReorganization/nim) | Sets and intersections |
|
||||
| 04 - Camp Cleanup | ★ ★ | [Python](04-campCleanup/py), [Nim](04-campCleanup/nim) | More sets and more intersections! |
|
||||
| 05 - Supply Stacks | ★ ★ | [Python](05-supplyStacks/py), [Nim](05-supplyStacks/nim) | Believe it or not, this one involved stacks. |
|
||||
| 06 - Tuning Trouble | ★ ★ | [Python](06-tuningTrouble/py), [Nim](06-tuningTrouble/nim) | This is the first year I've not repeatedly forgotten about the existence of sets, and it's coming in quite handy. |
|
||||
| 07 - No Space Left On Device | ★ ★ | [Python](07-noSpaceLeftOnDevice/py) | Turns out that fake file systems are prone to very subtle and infuriating bugs. |
|
||||
| 08 - Treetop Tree House | ★ ★ | [Python](08-treetopTreeHouse/py) | Magical coordinate dictionary tuple things do be magical. |
|
||||
| 09 - Rope Bridge | ★ ★ | [Python](09-ropeBridge/py), [Nim](09-ropeBridge/nim) | Does this count as this year's first cellular automata? |
|
||||
| 10 - Cathode-Ray Tube | ★ ★ | [Python](10-cathodeRayTube/py) | A nasty problem with a nasty solution and nasty outputs that mess with my framework. |
|
||||
| 11 - Monkey in the Middle | ★ ★ | [Python](11-monkeyInTheMiddle/py) | Return of Advent of Maths! |
|
||||
| 01 - Calorie Counting | ★ ★ | [Python](01-calorieCounting/py/__init__.py), [Nim](01-calorieCounting/nim/challenge.nim), [Java](01-calorieCounting/java/src) | Summing numbers |
|
||||
| 02 - Rock Paper Scissors | ★ ★ | [Python](02-rockPaperScissors/py/__init__.py), [Nim](02-rockPaperScissors/nim/challenge.nim) | Programmatically playing Rock Paper Scissors |
|
||||
| 03 - Rucksack Reorganization | ★ ★ | [Python](03-rucksackReorganization/py/__init__.py), [Nim](03-rucksackReorganization/nim/challenge.nim) | Sets and intersections |
|
||||
| 04 - Camp Cleanup | ★ ★ | [Python](04-campCleanup/py/__init__.py), [Nim](04-campCleanup/nim/challenge.nim) | More sets and more intersections! |
|
||||
| 05 - Supply Stacks | ★ ★ | [Python](05-supplyStacks/py/__init__.py), [Nim](05-supplyStacks/nim/challenge.nim) | Believe it or not, this one involved stacks. |
|
||||
| 06 - Tuning Trouble | ★ ★ | [Python](06-tuningTrouble/py/__init__.py), [Nim](06-tuningTrouble/nim/challenge.nim) | This is the first year I've not repeatedly forgotten about the existence of sets, and it's coming in quite handy. |
|
||||
| 07 - No Space Left On Device | ★ ★ | [Python](07-noSpaceLeftOnDevice/py/__init__.py) | Turns out that fake file systems are prone to very subtle and infuriating bugs. |
|
||||
| 08 - Treetop Tree House | ★ ★ | [Python](08-treetopTreeHouse/py/__init__.py) | Magical coordinate dictionary tuple things do be magical. |
|
||||
| 09 - Rope Bridge | ★ ★ | [Python](09-ropeBridge/py/__init__.py), [Nim](09-ropeBridge/nim/challenge.nim) | Does this count as this year's first cellular automata? |
|
||||
| 10 - Cathode-Ray Tube | ★ ★ | [Python](10-cathodeRayTube/py/__init__.py) | A nasty problem with a nasty solution and nasty outputs that mess with my framework. |
|
||||
| 11 - Monkey in the Middle | ★ ★ | [Python](11-monkeyInTheMiddle/py/__init__.py) | Return of Advent of Maths! |
|
||||
| 12 - Hill Climbing Algorithm | ☆ ☆ | | |
|
||||
| 13 - Distress Signal | ☆ ☆ | | |
|
||||
| 14 - Regolith Reservoir | ★ ★ | [Python](14-regolithReservoir/py) | Simulating falling sand |
|
||||
| 15 - Beacon Exclusion Zone | ★ ★ | [Python](15-beaconExclusionZone/py) | Searching through a 4000000^2 size grid for a literal single empty spot |
|
||||
| 16 - Proboscidea Volcanium | ★ ★ | [Python](16-proboscideaVolcanium/py) | Nasty combinatorics |
|
||||
| 14 - Regolith Reservoir | ★ ★ | [Python](14-regolithReservoir/py/__init__.py) | Simulating falling sand |
|
||||
| 15 - Beacon Exclusion Zone | ★ ★ | [Python](15-beaconExclusionZone/py/__init__.py) | Searching through a 4000000^2 size grid for a literal single empty spot |
|
||||
| 16 - Proboscidea Volcanium | ★ ★ | [Python](16-proboscideaVolcanium/py/__init__.py) | Nasty combinatorics |
|
||||
| 17 - Pyroclastic Flow | ★ ★ | [Python](17-pyroclasticFlow/py/__init__.py) | Detecting cycles in a large amount of knock-off Tetris. |
|
Binary file not shown.
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 54 KiB |
|
@ -58,6 +58,9 @@ figure = plt.figure(figsize=(25/2, 5))
|
|||
axp1 = figure.add_subplot(1, 2, 1)
|
||||
axp2 = figure.add_subplot(1, 2, 2, sharey=axp1)
|
||||
|
||||
axp1.axhline(y=15, color="#fc8080", linestyle="--")
|
||||
axp2.axhline(y=15, color="#fc8080", linestyle="--")
|
||||
|
||||
for i, language in enumerate(benchmark_data):
|
||||
|
||||
data = benchmark_data[language]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
from typing import Any, TypeVar, Callable, Iterable
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
class BaseChallenge:
|
||||
|
@ -34,9 +35,16 @@ class Vector:
|
|||
x: int
|
||||
y: int
|
||||
|
||||
def __init__(self, x: int, y: int):
|
||||
self.x = x
|
||||
self.y = y
|
||||
def __init__(self, *args):
|
||||
if len(args) == 1 and Vector._is_vector_tuple(args[0]):
|
||||
x, y = args[0]
|
||||
elif len(args) != 2:
|
||||
return ValueError("expected integer tuple or pair of integers")
|
||||
else:
|
||||
x, y = args
|
||||
|
||||
self.x = int(x)
|
||||
self.y = int(y)
|
||||
|
||||
@staticmethod
|
||||
def _is_vector_tuple(o: Any) -> bool:
|
||||
|
@ -74,4 +82,30 @@ class Vector:
|
|||
return f"({self.x}, {self.y})"
|
||||
|
||||
def __hash__(self):
|
||||
return hash(f"{self.x},{self.y}")
|
||||
return hash((self.x, self.y))
|
||||
|
||||
class Consumer:
|
||||
x: Sequence[T]
|
||||
i: int
|
||||
|
||||
def __init__(self, x: Sequence[T]):
|
||||
self.x = x
|
||||
self.i = 0
|
||||
|
||||
def take(self) -> T:
|
||||
self.i += 1
|
||||
return self.x[self.i-1]
|
||||
|
||||
def undo(self):
|
||||
self.i -= 1
|
||||
|
||||
class RepeatingConsumer(Consumer):
|
||||
def take(self) -> T:
|
||||
val = super().take()
|
||||
self.i = self.i % len(self.x)
|
||||
return val
|
||||
|
||||
def undo(self):
|
||||
super().undo()
|
||||
if self.i < 0:
|
||||
self.i += len(self.x)
|
|
@ -121,6 +121,8 @@ func run() error {
|
|||
|
||||
} else {
|
||||
|
||||
fmt.Print("Running...\n\n")
|
||||
|
||||
if err := runTests(runner, challengeInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue