1
+ const { readFileSync } = require ( "fs" ) ;
2
+ const { join : path } = require ( "path" ) ;
3
+
4
+ class FSNode {
5
+ type = "raw"
6
+ name = null
7
+ size = null
8
+ parent = null
9
+
10
+ constructor ( name ) {
11
+ this . name = name
12
+ }
13
+
14
+ getSize = ( ) => this . size
15
+
16
+ setParent = ( parentNode ) => {
17
+ this . parent = parentNode
18
+ }
19
+
20
+ static fromDataLine = ( dataLine ) => {
21
+ const data = dataLine . split ( " " ) ;
22
+ if ( data [ 0 ] == "dir" ) {
23
+ return new Directory ( data [ 1 ] , [ ] ) ;
24
+ } else {
25
+ return new File ( dataLine )
26
+ }
27
+ }
28
+ }
29
+
30
+ class Directory extends FSNode {
31
+ type = "dir"
32
+ children = null
33
+
34
+ constructor ( name , children = [ ] ) {
35
+ super ( name ) ;
36
+ this . children = children ;
37
+ }
38
+
39
+ getSize = ( ) => {
40
+ let total = 0 ;
41
+ this . children . forEach ( ( child ) => {
42
+ total += child . getSize ( ) ;
43
+ } ) ;
44
+ return total ;
45
+ }
46
+
47
+ addChild = ( childNode ) => {
48
+ childNode . setParent ( this ) ;
49
+ this . children . push ( childNode ) ;
50
+ }
51
+
52
+ getChild = ( childName ) => {
53
+ const child = this . children . find ( ( child ) => {
54
+ return child . name === childName ;
55
+ } ) ;
56
+ if ( ! child )
57
+ throw new Error ( `${ this . name } doesn't contain ${ childName } !` ) ;
58
+ else return child ;
59
+ }
60
+ }
61
+
62
+ class File extends FSNode {
63
+ type = "file" ;
64
+
65
+ constructor ( name , size = undefined ) {
66
+ if ( size ) {
67
+ super ( name ) ;
68
+ this . size = size ;
69
+ } else {
70
+ // Allow for passing the input line directly
71
+ const data = name . split ( " " ) ;
72
+ super ( data [ 1 ] ) ;
73
+ this . size = parseInt ( data [ 0 ] )
74
+ }
75
+ }
76
+ }
77
+
78
+ class Shell {
79
+ workingDir = null ;
80
+
81
+ constructor ( startingDir ) {
82
+ this . workingDir = startingDir
83
+ }
84
+
85
+ eval = ( line , getNextLine ) => {
86
+ if ( ! line . startsWith ( "$" ) )
87
+ throw new Error ( "Shell#eval received invalid command (missing $)" ) ;
88
+
89
+ const words = line . split ( " " ) ;
90
+ words . shift ( ) ; // Remove leading $
91
+ const cmd = words . shift ( ) ;
92
+ // `words` now contains the arguments
93
+
94
+ if ( cmd === "cd" ) {
95
+ const target = words . shift ( ) ;
96
+ if ( target === ".." ) this . workingDir = this . workingDir . parent ;
97
+ else if ( target === "/" ) {
98
+ while ( this . workingDir . name !== "/" ) {
99
+ this . eval ( "$ cd .." ) ;
100
+ }
101
+ } else {
102
+ this . workingDir = this . workingDir . getChild ( target ) ;
103
+ }
104
+ } else if ( cmd === "ls" ) {
105
+ let nextLineOffset = 1 ;
106
+ while ( true ) {
107
+ const nextLine = getNextLine ( nextLineOffset ) ;
108
+
109
+ // Stop if the next line doesn't exist or is a new command
110
+ if ( ! nextLine ) break ;
111
+ if ( nextLine . startsWith ( "$" ) ) break ;
112
+
113
+ const newNode = FSNode . fromDataLine ( nextLine ) ;
114
+ this . workingDir . addChild ( newNode ) ;
115
+ nextLineOffset += 1 ;
116
+ }
117
+ } else
118
+ throw new Error ( `Shell#eval received invalid command (${ cmd } )` ) ;
119
+ }
120
+ }
121
+
122
+ const rootNode = new Directory ( "/" ) ;
123
+ const shell = new Shell ( rootNode ) ;
124
+
125
+ const input = readFileSync ( path ( __dirname , "input" ) ) . toString ( ) ;
126
+ const inputLines = input . split ( "\n" )
127
+ for ( let _inputLineInx in inputLines ) {
128
+ inputLineInx = parseInt ( _inputLineInx )
129
+ const inputLine = inputLines [ inputLineInx ]
130
+ if ( inputLine . startsWith ( "$" ) ) {
131
+ shell . eval ( inputLine , ( offset = 1 ) => {
132
+ return inputLines [ inputLineInx + offset ] ;
133
+ } ) ;
134
+ }
135
+ }
136
+
137
+ const getAllDirs = ( root ) => {
138
+ let result = [ root ] ;
139
+ const subDirs = root . children . filter ( ( child ) => child . type === "dir" ) ;
140
+ subDirs . forEach ( ( subDir ) => { result = [ ...result , ...getAllDirs ( subDir ) ] } ) ;
141
+ return result ;
142
+ } ;
143
+
144
+ const allSizes = getAllDirs ( rootNode ) . map ( ( dir ) => dir . getSize ( ) ) ;
145
+
146
+ // PART 1: Combine the sizes of all dirs below 100K in size
147
+ const solution1 = allSizes . reduce ( ( accumulated , nextSolutionSize ) => {
148
+ if ( nextSolutionSize > 100000 ) return accumulated ;
149
+ else return accumulated + nextSolutionSize ;
150
+ } , 0 ) ;
151
+
152
+
153
+ // PART 2: Find the size of the smallest dir that is at least 30M in size
154
+ const fsSize = 70 * 1000000
155
+ const updateSize = 30 * 1000000 ;
156
+
157
+ const usedSpace = rootNode . getSize ( ) ;
158
+ const freeSpace = fsSize - usedSpace ;
159
+ const neededSpace = updateSize - freeSpace ;
160
+
161
+ const solution2Candidates = allSizes . filter ( ( size ) => size >= neededSpace ) ;
162
+ let solution2 = solution2Candidates [ 0 ] ;
163
+ solution2Candidates . forEach ( ( candidate ) => {
164
+ if ( candidate < solution2 ) { solution2 = candidate ; }
165
+ } ) ;
166
+
167
+ console . log ( solution1 ) ;
168
+ console . log ( solution2 ) ;
0 commit comments