139 lines
3.7 KiB
Python
139 lines
3.7 KiB
Python
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)
|
|
)
|