mirror of
https://github.com/cupcakearmy/advent-of-code.git
synced 2024-12-22 08:06:25 +00:00
116 lines
2.9 KiB
Python
116 lines
2.9 KiB
Python
|
#!/usr/bin/env python
|
||
|
|
||
|
from dataclasses import dataclass
|
||
|
from os.path import dirname, join
|
||
|
from typing import Callable, ClassVar, Literal, Union
|
||
|
|
||
|
# Day 07
|
||
|
|
||
|
# Common
|
||
|
|
||
|
|
||
|
def read_input(filename):
|
||
|
data = join(dirname(__file__), '..', filename)
|
||
|
with open(data) as f:
|
||
|
return f.read().strip()
|
||
|
|
||
|
|
||
|
test = read_input('test.txt')
|
||
|
data = read_input('input.txt')
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class File:
|
||
|
size: int
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class Directory:
|
||
|
name: str
|
||
|
contents: dict[str, Union['Directory', File]]
|
||
|
parent: Union[None, 'Directory'] = None
|
||
|
_size: int | None = None
|
||
|
|
||
|
def size(self) -> int:
|
||
|
if self._size is None:
|
||
|
self._size = sum([
|
||
|
entry.size if isinstance(entry, File) else entry.size()
|
||
|
for entry in self.contents.values()
|
||
|
])
|
||
|
return self._size
|
||
|
|
||
|
def walk(self, fn: Callable[[Union[File, 'Directory']], None]):
|
||
|
for entry in self.contents.values():
|
||
|
fn(entry)
|
||
|
if isinstance(entry, Directory):
|
||
|
entry.walk(fn)
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class Command:
|
||
|
command: str
|
||
|
output: list[str]
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class Shell:
|
||
|
commands: list[Command]
|
||
|
root: Directory
|
||
|
cwd: Directory
|
||
|
|
||
|
def execute(self):
|
||
|
for command in self.commands:
|
||
|
cmd, *args = command.command.split()
|
||
|
match cmd:
|
||
|
case 'ls':
|
||
|
for entry in command.output:
|
||
|
size, name = entry.split()
|
||
|
self.cwd.contents[name] = Directory(name, {}, self.cwd) if size == 'dir' else File(int(size))
|
||
|
case 'cd':
|
||
|
match args[0]:
|
||
|
case '/':
|
||
|
self.cwd = self.root
|
||
|
case '..':
|
||
|
self.cwd = self.cwd.parent
|
||
|
case _:
|
||
|
self.cwd = self.cwd.contents[args[0]]
|
||
|
|
||
|
def get_all_sizes(self) -> list[int]:
|
||
|
sizes: list[int] = []
|
||
|
|
||
|
def fn(entry: Directory | File):
|
||
|
if isinstance(entry, Directory):
|
||
|
sizes.append(entry.size())
|
||
|
self.root.walk(fn)
|
||
|
return sizes
|
||
|
|
||
|
def flag_1(self):
|
||
|
directories = self.get_all_sizes()
|
||
|
return sum([size for size in directories if size <= 100_000])
|
||
|
|
||
|
def flag_2(self, total=70_000_000, needed=30_000_000):
|
||
|
to_remove = needed - total + self.root.size()
|
||
|
big_enough = sorted([
|
||
|
size
|
||
|
for size in self.get_all_sizes()
|
||
|
if size > to_remove
|
||
|
])
|
||
|
return big_enough[0]
|
||
|
|
||
|
@staticmethod
|
||
|
def parse(data: str):
|
||
|
commands = [command.strip().split('\n') for command in data.split('$ ')[1:]]
|
||
|
root = Directory('/', {})
|
||
|
return Shell([Command(command[0], command[1:]) for command in commands], root, root)
|
||
|
|
||
|
|
||
|
shell = Shell.parse(test)
|
||
|
shell.execute()
|
||
|
print(shell.flag_1())
|
||
|
print(shell.flag_2())
|
||
|
|
||
|
shell = Shell.parse(data)
|
||
|
shell.execute()
|
||
|
print(shell.flag_1())
|
||
|
print(shell.flag_2())
|