with open('input.text', 'r') as file: data: str = file.read().strip() # If block has no ID then it is free class FreeBlock: size: int def __init__(self, s: int): self.size = s def __str__(self) -> str: return '.' * self.size class FileBlock: size: int id: int def __init__(self, size: int, id: int): self.size = size self.id = id def __str__(self) -> str: return str(self.id) * self.size class DiskMap: blocks: list[FreeBlock | FileBlock] def __init__(self, s: str): self.blocks = [] self.parse(s) def parse(self, s: str) -> None: is_file = True id = 0 for char in s: b = FreeBlock(int(char)) if is_file: b = FileBlock(int(char), id) id += 1 is_file = not is_file self.blocks.append(b) # Returns index of first free block or None def findFreeBlock(self) -> int | None: for i, block in enumerate(self.blocks): if type(block) == FreeBlock: return i return None # Returns index of last file block def findLastFile(self) -> int | None: for i, block in reversed(list(enumerate(self.blocks))): if type(block) == FileBlock: return i return None def findOffsetLastFile(self, offset: int) -> int | None: for i, block in reversed(list(enumerate(self.blocks)))[offset:]: if type(block) == FileBlock: return i return None def findFreeBlockOfBestFit(self, size: int) -> int | None: for i, block in enumerate(self.blocks): if type(block) == FreeBlock and block.size >= size: return i return None def moveFile(self, file: FileBlock, dest: FreeBlock) -> bool: if dest.size > file.size: return False def compact(self) -> None: while True: free_index = self.findFreeBlock() file_index = self.findLastFile() if free_index > file_index: break free_size: int = self.blocks[free_index].size file_size: int = self.blocks[file_index].size file_id: int = self.blocks[file_index].id remaining_file_size = file_size - free_size remaining_free_size = 0 if remaining_file_size < 0: remaining_free_size = abs(remaining_file_size) remaining_file_size = 0 if remaining_free_size != 0: self.blocks[free_index] = FileBlock(free_size - remaining_free_size, file_id) self.blocks.insert(free_index + 1, FreeBlock(remaining_free_size)) self.blocks[file_index + 1] = FreeBlock(file_size) else: self.blocks[free_index] = FileBlock(free_size, file_id) self.blocks[file_index] = FreeBlock(free_size) if remaining_file_size: self.blocks.insert(file_index, FileBlock(remaining_file_size, file_id)) def compact2(self) -> None: offset = 0 while True: file_index: int = self.findOffsetLastFile(offset) file_size: int = self.blocks[file_index].size free_index: int = self.findFreeBlockOfBestFit(file_size) free_size: int = self.blocks[free_index].size if file_index == None: break offset = len(self.blocks) - file_index def __str__(self) -> str: s = '' for block in self.blocks: s += str(block) return s # Parse Data diskmap = DiskMap(data) # Part 1 print(f'Input: {diskmap}') diskmap.compact() print(f'Compacted: {diskmap}') total = 0 index = 0 for block in diskmap.blocks: if type(block) == FileBlock: for i in range(block.size): total += block.id * index index += 1 print(f'Part 1: {total}')