Implement day 9 solver

This commit is contained in:
2024-12-08 23:22:34 -08:00
parent 569714d227
commit 7c021ab87f
2 changed files with 139 additions and 2 deletions

View File

@@ -1,7 +1,6 @@
from functools import partial from functools import partial
from typing import Callable, Iterator, NamedTuple
from math import log from math import log
from typing import Callable, Iterator, NamedTuple
test_input = """ test_input = """
190: 10 19 190: 10 19

138
puzzles/9.py Normal file
View File

@@ -0,0 +1,138 @@
from itertools import islice, pairwise
from typing import NamedTuple
from more_itertools import chunked, sliced
test_input = """
2333133121414131402
"""
test_solution_p1 = 1928
test_solution_p2 = 2858
def solve_p1(puzzle_input: str) -> int:
blocks = _parse_blocks(puzzle_input)
compacted_blocks = _compact_blocks(blocks)
return _hash_blocks(compacted_blocks)
def solve_p2(puzzle_input: str) -> int:
blocks = _parse_blocks(puzzle_input)
compacted_blocks = _compact_blocks_no_fragmentation(blocks)
return _hash_blocks(compacted_blocks)
class Block(NamedTuple):
id: int
position: int
size: int
def _parse_blocks(puzzle_input: str) -> list[Block]:
blocks: list[Block] = []
cursor_position = 0
current_id = 0
snapshot = map(int, puzzle_input.strip())
for pair in chunked(snapshot, 2):
if data_size := pair[0]:
blocks.append(Block(current_id, cursor_position, data_size))
cursor_position += data_size
current_id += 1
if len(pair) == 2:
cursor_position += pair[1]
return blocks
def _compact_blocks(blocks: list[Block]) -> list[Block]:
assert blocks
blocks = blocks.copy()
compacted_blocks: list[Block] = []
current_position = 0
moving_block = blocks.pop()
while blocks:
next_block = blocks.pop(0)
free_space = next_block.position - current_position
while free_space:
assert moving_block.size > 0
allocation = min(free_space, moving_block.size)
compacted_blocks.append(
Block(
moving_block.id,
current_position,
allocation,
)
)
current_position += allocation
free_space -= allocation
if moving_block.size > allocation:
moving_block = Block(
moving_block.id,
moving_block.position + allocation,
moving_block.size - allocation,
)
elif blocks:
moving_block = blocks.pop()
else:
break
compacted_blocks.append(next_block)
current_position += next_block.size
compacted_blocks.append(
Block(
moving_block.id,
current_position,
moving_block.size,
)
)
return compacted_blocks
def _compact_blocks_no_fragmentation(blocks: list[Block]) -> list[Block]:
assert blocks
blocks = blocks.copy()
compacted_blocks: list[Block] = []
while len(blocks) > 1:
moving_block = blocks[-1]
for (_, a), (i, b) in pairwise(enumerate(blocks)):
free_space = b.position - a.position - a.size
if free_space >= moving_block.size:
modified_block = Block(
moving_block.id,
a.position + a.size,
moving_block.size,
)
blocks.pop()
blocks.insert(i, modified_block)
break
else:
blocks.pop()
compacted_blocks.append(moving_block)
compacted_blocks.append(blocks[0])
return list(sorted(compacted_blocks, key=lambda block: block.position))
def _print_blocks(blocks: list[Block]) -> str:
snapshot = []
for a, b in pairwise(blocks):
snapshot.append(str(a.id) * a.size)
snapshot.append("." * (b.position - a.position - a.size))
snapshot.append(str(blocks[-1].id) * blocks[-1].size)
def _hash_blocks(blocks: list[Block]) -> int:
return sum(
block.id * pos
for block in blocks
for pos in range(block.position, block.position + block.size)
)