-
Notifications
You must be signed in to change notification settings - Fork 0
/
d07.hs
120 lines (96 loc) · 3.55 KB
/
d07.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
module Main where
import Data.List (foldl', minimumBy)
import Data.Function (on)
import Data.Sequence (Seq(..), singleton, (|>))
import qualified Data.Map.Strict as M
import Data.List.Split (split, whenElt, dropInitBlank)
import Helpers
data Item
= File String Integer -- Files store name and size
| Directory String Integer (M.Map String Item) -- Directory maps from names to items
deriving (Show)
getName :: Item -> String
getName (File name _ ) = name
getName (Directory name _ _) = name
getSize :: Item -> Integer
getSize (File _ size ) = size
getSize (Directory _ size _) = size
listDirs :: Item -> [(String, Integer)]
listDirs (File _ _ ) = []
listDirs (Directory name size ds) = (name, size) : (M.elems ds >>= listDirs)
data Command
= Cd String
| Ls
deriving (Show)
-- Working path and filesystem
type WorkingPath = Seq String
type State = (WorkingPath, Item)
-- Command and its output
type TracePoint = (Command, [Item])
type Trace = [TracePoint]
initial :: State
initial = (singleton "/", Directory "/" 0 M.empty)
mergeTrees :: Item -> WorkingPath -> Item -> Item
mergeTrees _ Empty _ = error "mergeTrees: empty path"
mergeTrees root (_ :<| path) subtree = go root path
where
go :: Item -> WorkingPath -> Item
go (File _ _ ) _ = error "mergeTrees: move into a file"
go (Directory name size ds) Empty = Directory name size' ds'
where
size' = size + getSize subtree
ds' = M.insert (getName subtree) subtree ds
go (Directory name _ ds) (next :<| rest) = Directory name size' ds'
where
ds' = M.mapWithKey updateSubdirs ds
size' = sum . map getSize . M.elems $ ds'
updateSubdirs :: String -> Item -> Item
updateSubdirs n item
| n == next = go subdir rest
| otherwise = item
subdir =
case M.lookup next ds of
Just x -> x
Nothing -> error "mergeTrees: move into a missing directory"
main :: IO ()
main =
do
trace <- loadData "a07.txt"
let (_, fs) = foldl' replayTracePoint initial trace
let dirs = listDirs fs
print . sum . map snd . filter (\(_, size) -> size <= 100000) $ dirs
let unused = 70000000 - getSize fs
let bigEnough (_, size) = unused + size >= 30000000
print . snd . minimumBy (compare `on` snd) . filter bigEnough $ dirs
replayTracePoint :: State -> TracePoint -> State
replayTracePoint (wd, fs) (cmd, res) = (changeDir cmd, changeFs res)
where
changeDir :: Command -> WorkingPath
changeDir Ls = wd
changeDir (Cd "/") = singleton "/"
changeDir (Cd "..") = let (rest :|> _) = wd in rest
changeDir (Cd dir) = wd |> dir
changeFs :: [Item] -> Item
changeFs = foldl' (\fs' item -> mergeTrees fs' wd item) fs
loadData :: FilePath -> IO Trace
loadData input = breakUp <$> readLines input
where
breakUp :: [String] -> Trace
breakUp cmds = toTrace broken
where
broken = split (dropInitBlank $ whenElt (\line -> head line == '$')) cmds
toTrace :: [[String]] -> Trace
toTrace [] = []
toTrace ([x]:y:xs) = (readCommand x, map readItem y) : toTrace xs
readCommand :: String -> Command
readCommand "$ ls" = Ls
readCommand cd = let [_, _, dir] = words cd in Cd dir
readItem :: String -> Item
readItem output =
let
[spec, name] = words output
in
if spec == "dir" then
Directory name 0 M.empty
else
File name $ read spec