advent-2022-python/advent/days/day07/solution.py
2022-12-07 07:36:08 +01:00

110 lines
3.5 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Iterator
day_num = 7
def part1(lines: Iterator[str]) -> int:
return Directory.parse(lines).get_maxed_size(100_000)
def part2(lines: Iterator[str]) -> int:
directory = Directory.parse(lines)
return directory.get_min_delete_size(70_000_000, 30_000_000)
@dataclass(slots=True)
class Directory:
name: str
parent: Directory | None
subdirs: list[Directory] = field(default_factory=list)
files: list[tuple[str, int]] = field(default_factory=list)
size: int | None = None
def cd_into(self, name: str) -> Directory:
"""
Returns the named sub directory or .. for parent
May fail if unkown subdirectory - or already in root
"""
if name == "..":
if self.parent is None:
raise Exception('Already at root Directory')
return self.parent
for sub in self.subdirs:
if sub.name == name:
return sub
raise Exception(f"Could not find subdir {name}")
def add_directory(self, name: str):
""" Adds the named directory."""
self.subdirs.append(Directory(name, self))
def add_file(self, name: str, size: int):
""" Adds the given file and size """
self.files.append((name, size))
def get_size(self) -> int:
""" returns the size of this directory including all subdirectories """
if self.size is None:
self.size = (sum(size for _, size in self.files)
+ sum(sub.get_size() for sub in self.subdirs))
return self.size
def get_all_directories(self) -> Iterator[Directory]:
""" Returns an iterator of all subdirectories """
for sub in self.subdirs:
yield from sub.get_all_directories()
yield self
def get_maxed_size(self, threshold: int) -> int:
""" Returns the sum of all sizes of subdirectories, that are below the given threshold"""
return sum(size for size in
(dir.get_size() for dir in self.get_all_directories())
if size <= threshold)
def get_min_delete_size(self, disk_size: int, space_needed: int) -> int:
"""
Returns the size of the smallest directory that must be removed to created the free space
given as a parameter and the given disk size
#"""
unused = disk_size - self.get_size()
minimum: int | None = None
for dir in self.get_all_directories():
size = dir.get_size()
if unused + size >= space_needed and (minimum is None or minimum > size):
minimum = size
if minimum is None:
raise Exception("Could not find large enough directory to remove")
return minimum
@staticmethod
def parse(lines: Iterator[str]) -> Directory:
line = next(lines)
if line != '$ cd /':
raise Exception(f"Illegal first line: {line}")
root = Directory('/', None)
current = root
for line in lines:
match line.split():
case ['$', 'cd', name]:
current = current.cd_into(name)
case ['$', 'ls']:
pass
case ['dir', name]:
current.add_directory(name)
case [size, name]:
current.add_file(name, int(size))
case _:
raise Exception(f"Could not parse line: {line}")
return root