Skip to content

Commit c01f87c

Browse files
committed
Refactor part 1 using part 2's algorithm, tidy up
Speeds up part 1 by 5x
1 parent dafe394 commit c01f87c

File tree

1 file changed

+55
-146
lines changed

1 file changed

+55
-146
lines changed

src/day9.gleam

+55-146
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,26 @@
1-
import gleam/deque.{type Deque}
21
import gleam/int
32
import gleam/list
43
import gleam/pair
54
import gleam/string
65
import vec.{type Vec}
76

87
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) })
6215
}
6316
}
6417

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 })
11722
}
11823

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?)
12324
type Storage {
12425
Storage(
12526
// list of free locations #(loc, size)
@@ -165,26 +66,44 @@ fn to_storage(input: String) -> Storage {
16566
Storage(free_list: free_list, file_loc: file_loc, file_size: file_size)
16667
}
16768

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+
}
187105

106+
fn defrag_file(storage: Storage, file_id: Int) -> Storage {
188107
case file_id {
189108
-1 -> storage
190109
_ -> {
@@ -193,18 +112,7 @@ fn defrag_2(storage: Storage, file_id: Int) -> Storage {
193112

194113
case find_space(storage, current_loc, file_size) {
195114
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(
208116
Storage(
209117
// this is the hard bit
210118
free_list: storage.free_list
@@ -235,15 +143,14 @@ fn defrag_2(storage: Storage, file_id: Int) -> Storage {
235143
)
236144
}
237145
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)
240147
}
241148
}
242149
}
243150
}
244151
}
245152

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.
247154
// Returns Error if no space is available to our left
248155
fn find_space(
249156
storage: Storage,
@@ -254,14 +161,16 @@ fn find_space(
254161
|> list.find(fn(entry) { entry.1 >= file_size && entry.0 < file_loc })
255162
}
256163

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) {
258167
list.range(0, { storage.file_loc |> vec.size } - 1)
259168
|> list.map(fn(file_id) {
260169
let loc = storage.file_loc |> vec.must_get(file_id)
261170
let size = storage.file_size |> vec.must_get(file_id)
262171

263172
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) })
265174
|> int.sum
266175
})
267176
|> int.sum

0 commit comments

Comments
 (0)