119 lines
3.8 KiB
Python
119 lines
3.8 KiB
Python
from __future__ import annotations
|
|
from dataclasses import dataclass, field
|
|
|
|
from typing import Iterator, Self
|
|
|
|
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, eq=False)
|
|
class Directory:
|
|
name: str
|
|
parent: Directory | None
|
|
subdirs: list[Directory] = field(default_factory=list, init=False)
|
|
files: list[tuple[str, int]] = field(default_factory=list, init=False)
|
|
size: int | None = field(default=None, init=False, repr=False)
|
|
|
|
@classmethod
|
|
def create_root(cls) -> Self:
|
|
return cls('/', 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
|
|
"""
|
|
match name:
|
|
case '/':
|
|
current = self
|
|
while current.parent is not None:
|
|
current = current.parent
|
|
return current
|
|
|
|
case '..':
|
|
if self.parent is None:
|
|
raise Exception('Already at root Directory')
|
|
return self.parent
|
|
|
|
case _:
|
|
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, parent=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
|
|
|
|
@classmethod
|
|
def parse(cls, lines: Iterator[str]) -> Self:
|
|
root = cls.create_root()
|
|
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
|