-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdisk_space.go
287 lines (238 loc) · 6.81 KB
/
disk_space.go
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// ___ __ ____
// | \ __ _ _ _ / \__ |
// | |) / _` | || | | () |/ /
// |___/\__,_|\_, | \__//_/
// |__/
//
// "No Space Left On Device"
//
// Given the console log of a directory traversal, capture the file tree
// structure, determine a mechanism for, essentially, a `du -s`. Report the sum
// total of the disk size for each directory of at MOST 100,000 units.
package main
import (
"bufio"
"fmt"
"os"
"regexp"
"sort"
"strconv"
"strings"
)
const filename string = "input.txt"
type dirNode struct {
parent *dirNode
subdirs []*dirNode
files []*fileNode
name string
}
type fileNode struct {
parent *dirNode
name string
size int
}
// Define a tree root (newDir without a parent)
func newRoot(name string) *dirNode {
dir := dirNode{name: name}
return &dir
}
// Add a subdirectory to d and mark parent/child bidirectionally
func (d *dirNode) newDir(name string) *dirNode {
dir := dirNode{parent: d, name: name}
d.subdirs = append(d.subdirs, &dir)
return &dir
}
// Add a file to a directory and mark parent/child bidirectionally
func (d *dirNode) newFile(name string, size int) *fileNode {
file := fileNode{name: name, size: size, parent: d}
d.files = append(d.files, &file)
return &file
}
// Retrieve a child subdirectory pointer by name, within the context of a parent
func (d *dirNode) dir(name string) *dirNode {
for _, dir := range d.subdirs {
if dir.name == name {
return dir
}
}
return nil
}
// Retrieve a child file pointer by name, within the context of a parent
func (d *dirNode) file(name string) *fileNode {
for _, file := range d.files {
if file.name == name {
return file
}
}
return nil
}
// Traverse a directory and pretty-print an indented tree of child dirs/files
// including cumulative directory sizes.
func (d *dirNode) examine(args ...int) int {
var indent int
var size int
if (len(args) > 0) {
indent = args[0]
} else {
indent = 0
}
fmt.Printf("%s- %s/\n", strings.Repeat(" ", indent), d.name)
for _, sub := range(d.subdirs) {
size += sub.examine(indent + 2)
}
for _, file := range(d.files) {
size += file.size
fmt.Printf("%s- %s (%d)\n", strings.Repeat(" ", indent + 2), file.name, file.size)
}
fmt.Printf("%s (%s Total size: %d)\n", strings.Repeat(" ", indent + 2), d.name, size)
return size
}
// Similar to examine() but does the weird Part 1 request of "keep a tally of
// the small directories". In the Part 1 example, both "a" and "e" were counted
// even though "e" is a subdirectory of "a," so this can be simple-ish.
func (d *dirNode) sumSmallDirectorySizes(args ...int) (size int, total int) {
if (len(args) > 0) {
size = args[0]
} else {
size = 0
}
// Get the total size for everything in this level:
for _, sub := range(d.subdirs) {
x, y := sub.sumSmallDirectorySizes()
size += x
total += y
}
for _, file := range(d.files) {
size += file.size
}
if (size <= 100000) {
total += size
}
return size, total
}
func main() {
file, err := os.Open(filename)
if err != nil { panic(err) }
defer file.Close()
scanner := bufio.NewScanner(file)
// Start a tree
root := newRoot("C:")
// `cd` will mark where we are, start it at the root
cd := root
for scanner.Scan() {
line := scanner.Text()
// Process a new command: We need to catch and process a `cd`, and just
// ignore an `ls` so it doesn't get interpreted as a directory member.
if string(line[0]) == "$" {
switch string(line[2:4]) {
case "cd":
dir := string(line[5:])
if dir == "/" {
// Root directory
cd = root
} else if dir == ".." {
// Back up one
cd = cd.parent
} else {
// Go into a directory that...
if cd.dir(dir) != nil {
// ... already exists
cd = cd.dir(dir)
} else {
// ... doesn't exist
cd = cd.newDir(dir)
}
}
case "ls":
// Ignore this case. It's the other command in the text, but doesn't
// require any action because `cd` is set and `ls` output is assumed.
}
} else {
// This is `ls` output denoting a new...
var filename string
if string(line[0:3]) == "dir" {
// ... directory: add it to the tree
filename = string(line[4:])
cd.newDir(filename)
} else {
// ... file: get its size and add it to the tree
rex := `(\d+) (.+)`
captures := regexp.MustCompile(rex).FindStringSubmatch(line)
filename = captures[2]
filesize, err := strconv.Atoi(captures[1])
// Sure why not
if err != nil {
panic(err)
}
cd.newFile(filename, filesize)
}
}
}
// With the full tree assembled, pretty-print it because _darnit_ I'm proud of
// this mess.
sizeUsed := root.examine()
// Part One: Sum of all directories less than 100,000: 1086293
_, sizeLimited := root.sumSmallDirectorySizes()
fmt.Printf("\n\n-- Part One:\n")
fmt.Printf("Sum of all directories less than 100,000: %d\n", sizeLimited)
// ___ _ ___
// | _ \__ _ _ _| |_ |_ )
// | _/ _` | '_| _| / /
// |_| \__,_|_| \__| /___|
//
// Given a max drive capacity of 70,000,000, and a need to have 30,000,000
// free space, find the smallest directory that could be deleted to free up
// the needed space.
sizeCap := 70000000
sizeNeeded := 30000000
sizeFree := sizeCap - sizeUsed
sizeNeedToFree := sizeNeeded - sizeFree
sizeUsedPercent := (float32(sizeUsed) / float32(sizeCap) * 100)
fmt.Printf("\n\n-- Part Two:\n")
fmt.Printf("Currently used: %10d of %10d (%.2f%%)\n", sizeUsed, sizeCap, sizeUsedPercent)
fmt.Printf("Space needed: %10d\n", sizeNeeded)
fmt.Printf("Free space: %10d\n", sizeFree)
fmt.Printf("Need to free: %10d\n", sizeNeedToFree)
_, sizesAll := root.getAllDirectorySizes()
sort.Slice(sizesAll, func(i, j int) bool {
return sizesAll[i].size < sizesAll[j].size
})
for _, report := range sizesAll {
if report.size >= sizeNeedToFree {
fmt.Printf("Delete %-8s(%10d) to free enough space", report.dir.name, report.size)
fmt.Printf("\n")
break
}
}
// -- Part Two:
// Currently used: 40358913 of 70000000 (57.66%)
// Space needed: 30000000
// Free space: 29641087
// Need to free: 358913
// Delete ptgn ( 366028) to free enough space
}
// Need to be able to note sizes of directories, but it felt weird to add that
// to the directory struct itself...
type dirSizeNote struct {
dir *dirNode
size int
}
// Yet another frankenfunction that travels like dirNode.examine() but builds
// a slice of all subdirs and their aggregate sizes.
func (d *dirNode) getAllDirectorySizes(args ...int) (size int, total []dirSizeNote) {
if (len(args) > 0) {
size = args[0]
} else {
size = 0
}
// Collect the sizes for all contained subdirectories
for _, sub := range(d.subdirs) {
x, y := sub.getAllDirectorySizes()
size += x
total = append(total, y...)
}
for _, file := range(d.files) {
size += file.size
}
return size, append(total, dirSizeNote{ dir: d, size: size})
}