1
- import gleam/deque . { type Deque }
2
1
import gleam/int
3
2
import gleam/list
4
3
import gleam/pair
5
4
import gleam/string
6
5
import vec . { type Vec }
7
6
8
7
pub fn part1 ( input : String ) -> Int {
9
- parse_input ( input )
10
- |> defrag
11
- |> checksum
12
- }
13
-
14
- pub fn part2 ( input : String ) -> Int {
15
- let storage = to_storage ( input )
16
- let max_id = { storage . file_loc |> vec . size } - 1
17
-
18
- storage
19
- |> defrag_2 ( max_id )
20
- |> checksum_2
21
- }
22
-
23
- type Block {
24
- Empty
25
- Filled ( Int )
26
- }
27
-
28
- type Disk {
29
- // defragged part is reversed list of ids, because we only need to operate on the back
30
- Disk ( defragged : List ( Int ) , unsorted : Deque ( Block ) )
31
- }
32
-
33
- fn defrag ( disk : Disk ) -> Disk {
34
- case deque . is_empty ( disk . unsorted ) {
35
- True -> disk
36
- False -> disk |> move_block |> defrag
37
- }
38
- }
39
-
40
- fn move_block ( disk : Disk ) -> Disk {
41
- case deque . pop_front ( disk . unsorted ) {
42
- Ok ( # ( Filled ( f ) , rest1 ) ) -> {
43
- Disk ( defragged : [ f , .. disk . defragged ] , unsorted : rest1 )
44
- }
45
- Ok ( # ( Empty , rest1 ) ) -> {
46
- case deque . pop_back ( rest1 ) {
47
- Ok ( # ( Empty , rest2 ) ) -> {
48
- Disk (
49
- defragged : disk . defragged ,
50
- unsorted : rest2 |> deque . push_front ( Empty ) ,
51
- )
52
- }
53
- Ok ( # ( Filled ( b ) , rest2 ) ) -> {
54
- Disk ( defragged : [ b , .. disk . defragged ] , unsorted : rest2 )
55
- }
56
- Error ( _ ) -> {
57
- Disk ( defragged : disk . defragged , unsorted : rest1 )
58
- }
59
- }
60
- }
61
- Error ( _ ) -> panic as "tried to move empty unsorted"
8
+ to_storage ( input )
9
+ |> split_files
10
+ |> fn ( pair ) {
11
+ let # ( storage , file_id_map ) = pair
12
+ storage
13
+ |> defrag
14
+ |> checksum_3 ( fn ( file_id ) { vec . must_get ( file_id_map , file_id ) } )
62
15
}
63
16
}
64
17
65
- fn checksum ( disk : Disk ) -> Int {
66
- disk . defragged
67
- |> list . reverse
68
- |> list . index_map ( fn ( id , i ) { id * i } )
69
- |> int . sum
70
- }
71
-
72
- fn debug_disk ( disk : Disk ) -> String {
73
- string . concat ( [
74
- disk . defragged
75
- |> list . reverse
76
- |> list . map ( int . to_string )
77
- |> string . join ( "" ) ,
78
- "|" ,
79
- disk . unsorted
80
- |> deque . to_list
81
- |> list . map ( debug_block )
82
- |> string . join ( "" ) ,
83
- ] )
84
- }
85
-
86
- // only makes sense for the example, the real one has ids > 9
87
- fn debug_block ( block : Block ) -> String {
88
- case block {
89
- Empty -> "."
90
- Filled ( id ) -> int . to_string ( id )
91
- }
92
- }
93
-
94
- fn parse_input ( input : String ) -> Disk {
95
- Disk (
96
- defragged : [ ] ,
97
- unsorted : input
98
- |> string . trim_end
99
- |> string . to_graphemes
100
- |> list . index_map ( fn ( char , id ) {
101
- case char_to_int ( char ) {
102
- a if a > 0 -> {
103
- list . range ( 1 , a )
104
- |> list . map ( fn ( _ ) {
105
- case id % 2 == 0 {
106
- True -> Filled ( id / 2 )
107
- False -> Empty
108
- }
109
- } )
110
- }
111
- _ -> [ ]
112
- }
113
- } )
114
- |> list . flatten
115
- |> deque . from_list ,
116
- )
18
+ pub fn part2 ( input : String ) -> Int {
19
+ to_storage ( input )
20
+ |> defrag
21
+ |> checksum_3 ( fn ( file_id ) { file_id } )
117
22
}
118
23
119
- // Storage for part 2 which is very different
120
- // (If this works and is fast, could we reimplement part 1 using this, by
121
- // mapping each block to a 1-sized file in a way that allows us to retain the
122
- // IDs?)
123
24
type Storage {
124
25
Storage (
125
26
// list of free locations #(loc, size)
@@ -165,26 +66,44 @@ fn to_storage(input: String) -> Storage {
165
66
Storage ( free_list : free_list , file_loc : file_loc , file_size : file_size )
166
67
}
167
68
168
- fn defrag_2 ( storage : Storage , file_id : Int ) -> Storage {
169
- //io.println_error("")
170
- //io.println_error("Current free list (loc,size):")
171
- //io.debug(storage.free_list)
172
- //io.println_error("Current file locations:")
173
- //storage.file_loc
174
- //|> vec.to_list
175
- //|> list.index_map(fn(f, i) { #(f, i) })
176
- //|> list.each(fn(x) {
177
- // let #(loc, file_id) = x
178
- // io.println_error(
179
- // " file_id "
180
- // <> int.to_string(file_id)
181
- // //<> " size "
182
- // //<> int.to_string(storage.file_size |> vec.must_get(file_id))
183
- // <> " is at "
184
- // <> int.to_string(loc),
185
- // )
186
- //})
69
+ // So that part 1 can be done using part 2's algorithm:
70
+ // Map each multi-block file into single-block files, eg:
71
+ // ..111.. -> ..123..
72
+ // Free list is unchanged
73
+ //
74
+ // Return a Vec that is a map from the temporary file_id back to the original
75
+ // so that checksum can be calculated
76
+ fn split_files ( storage : Storage ) -> # ( Storage , Vec ( Int ) ) {
77
+ // list of single-block replacement files
78
+ // #(new_location, original_file_id)
79
+ let new_file_locations =
80
+ storage . file_loc
81
+ |> vec . to_list
82
+ |> list . index_map ( fn ( original_location , original_file_id ) {
83
+ let size = storage . file_size |> vec . must_get ( original_file_id )
84
+ list . range ( 0 , size - 1 )
85
+ |> list . map ( fn ( offset ) { # ( original_location + offset , original_file_id ) } )
86
+ } )
87
+ |> list . flatten
88
+
89
+ # (
90
+ Storage (
91
+ free_list : storage . free_list ,
92
+ file_loc : new_file_locations
93
+ |> list . map ( fn ( new_file ) { new_file . 0 } )
94
+ |> vec . from_list ,
95
+ file_size : new_file_locations |> list . map ( fn ( _ ) { 1 } ) |> vec . from_list ,
96
+ ) ,
97
+ new_file_locations |> list . map ( fn ( new_file ) { new_file . 1 } ) |> vec . from_list ,
98
+ )
99
+ }
100
+
101
+ fn defrag ( storage : Storage ) -> Storage {
102
+ let max_id = { storage . file_loc |> vec . size } - 1
103
+ defrag_file ( storage , max_id )
104
+ }
187
105
106
+ fn defrag_file ( storage : Storage , file_id : Int ) -> Storage {
188
107
case file_id {
189
108
- 1 -> storage
190
109
_ -> {
@@ -193,18 +112,7 @@ fn defrag_2(storage: Storage, file_id: Int) -> Storage {
193
112
194
113
case find_space ( storage , current_loc , file_size ) {
195
114
Ok ( # ( free_loc , _free_size ) ) -> {
196
- //io.debug(#(
197
- // "moving file_id",
198
- // file_id,
199
- // "size",
200
- // file_size,
201
- // "from",
202
- // current_loc,
203
- // "to",
204
- // free_loc,
205
- //))
206
-
207
- defrag_2 (
115
+ defrag_file (
208
116
Storage (
209
117
// this is the hard bit
210
118
free_list : storage . free_list
@@ -235,15 +143,14 @@ fn defrag_2(storage: Storage, file_id: Int) -> Storage {
235
143
)
236
144
}
237
145
Error ( _ ) -> {
238
- //io.debug(#("nowhere to move file_id", file_id, "size", file_size))
239
- defrag_2 ( storage , file_id - 1 )
146
+ defrag_file ( storage , file_id - 1 )
240
147
}
241
148
}
242
149
}
243
150
}
244
151
}
245
152
246
- // give a file size, return the #(loc, size) entry from the free list which best matches it.
153
+ // given a file size, return the #(loc, size) entry from the free list which best matches it.
247
154
// Returns Error if no space is available to our left
248
155
fn find_space (
249
156
storage : Storage ,
@@ -254,14 +161,16 @@ fn find_space(
254
161
|> list . find ( fn ( entry ) { entry . 1 >= file_size && entry . 0 < file_loc } )
255
162
}
256
163
257
- fn checksum_2 ( storage : Storage ) -> Int {
164
+ // The callback maps the file_id back to an original. This is used by part 1
165
+ // which renumbers files when they are split into single blocks.
166
+ fn checksum_3 ( storage : Storage , file_id_map_fn : fn ( Int ) -> Int ) {
258
167
list . range ( 0 , { storage . file_loc |> vec . size } - 1 )
259
168
|> list . map ( fn ( file_id ) {
260
169
let loc = storage . file_loc |> vec . must_get ( file_id )
261
170
let size = storage . file_size |> vec . must_get ( file_id )
262
171
263
172
list . range ( loc , loc + size - 1 )
264
- |> list . map ( fn ( pos ) { pos * file_id } )
173
+ |> list . map ( fn ( pos ) { pos * file_id_map_fn ( file_id ) } )
265
174
|> int . sum
266
175
} )
267
176
|> int . sum
0 commit comments