9
9
//!
10
10
//! We build 10 [min heaps](https://en.wikipedia.org/wiki/Heap_(data_structure)) in an array to
11
11
//! store the free space offsets. The index of the array implicitly stores the size of the
12
- //! free block.
12
+ //! free block. The heaps are implemented as a simple reversed `vec`. Usually items are added
13
+ //! directly to the top of the heap, so this is faster than a real heap.
13
14
//!
14
15
//! When moving a file to a free block, the corresponding heap is popped and then any leftover
15
16
//! space is pushed back to the heap at a smaller index. The heap at index zero is not used
16
17
//! but makes the indexing easier.
17
- use crate :: util:: heap:: * ;
18
18
19
19
/// [Triangular numbers](https://en.wikipedia.org/wiki/Triangular_number) offset by two.
20
20
/// Files can be a max size of 9 so we only need the first 10 values, including zero to make
21
21
/// indexing easier.
22
- const EXTRA : [ usize ; 10 ] = [ 0 , 0 , 1 , 3 , 6 , 10 , 15 , 21 , 28 , 36 ] ;
22
+ const TRIANGLE : [ usize ; 10 ] = [ 0 , 0 , 1 , 3 , 6 , 10 , 15 , 21 , 28 , 36 ] ;
23
23
24
24
/// Remove any trailing newlines and convert to `usize`.
25
25
pub fn parse ( input : & str ) -> Vec < usize > {
@@ -63,21 +63,28 @@ pub fn part1(disk: &[usize]) -> usize {
63
63
checksum
64
64
}
65
65
66
+ #[ allow( clippy:: needless_range_loop) ]
66
67
pub fn part2 ( disk : & [ usize ] ) -> usize {
67
68
let mut block = 0 ;
68
69
let mut checksum = 0 ;
69
- let mut free: Vec < _ > = ( 0 ..10 ) . map ( |_| MinHeap :: with_capacity ( 1_000 ) ) . collect ( ) ;
70
+ let mut free: Vec < _ > = ( 0 ..10 ) . map ( |_| Vec :: with_capacity ( 1_100 ) ) . collect ( ) ;
70
71
71
72
// Build a min-heap (leftmost free block first) where the size of each block is
72
73
// implicit in the index of the array.
73
74
for ( index, & size) in disk. iter ( ) . enumerate ( ) {
74
75
if index % 2 == 1 && size > 0 {
75
- free[ size] . push ( block, ( ) ) ;
76
+ free[ size] . push ( block) ;
76
77
}
77
78
78
79
block += size;
79
80
}
80
81
82
+ // Add sentinel value and reverse vecs so that smallest blocks are last.
83
+ for i in 0 ..10 {
84
+ free[ i] . push ( block) ;
85
+ free[ i] . reverse ( ) ;
86
+ }
87
+
81
88
for ( index, & size) in disk. iter ( ) . enumerate ( ) . rev ( ) {
82
89
block -= size;
83
90
@@ -90,38 +97,50 @@ pub fn part2(disk: &[usize]) -> usize {
90
97
let mut next_block = block;
91
98
let mut next_index = usize:: MAX ;
92
99
93
- #[ allow( clippy:: needless_range_loop) ]
94
100
for i in size..free. len ( ) {
95
- if let Some ( ( & first, ( ) ) ) = free[ i] . peek ( ) {
96
- if first < next_block {
97
- next_block = first;
98
- next_index = i;
99
- }
101
+ let top = free[ i] . len ( ) - 1 ;
102
+ let first = free[ i] [ top] ;
103
+
104
+ if first < next_block {
105
+ next_block = first;
106
+ next_index = i;
100
107
}
101
108
}
102
109
103
110
// We can make smaller free block from bigger blocks but not the other way around.
104
111
// As an optimization if all blocks of the biggest size are after our position then
105
112
// we can ignore them.
106
113
if !free. is_empty ( ) {
107
- let last = free. len ( ) - 1 ;
108
- if let Some ( ( & first , ( ) ) ) = free[ last ] . peek ( ) {
109
- if first > block {
110
- free . pop ( ) ;
111
- }
114
+ let biggest = free. len ( ) - 1 ;
115
+ let top = free[ biggest ] . len ( ) - 1 ;
116
+
117
+ if free [ biggest ] [ top ] > block {
118
+ free . pop ( ) ;
112
119
}
113
120
}
114
121
115
122
// Update the checksum with the file's location (possibly unchanged).
116
123
let id = index / 2 ;
117
- let extra = next_block * size + EXTRA [ size] ;
124
+ let extra = next_block * size + TRIANGLE [ size] ;
118
125
checksum += id * extra;
119
126
120
127
// If we used a free block, remove then add back any leftover space.
121
128
if next_index != usize:: MAX {
122
129
free[ next_index] . pop ( ) ;
123
- if size < next_index {
124
- free[ next_index - size] . push ( next_block + size, ( ) ) ;
130
+
131
+ // Insert the new smaller block into the correct location.
132
+ // Most frequently this is directly at the end of the vector so even though this
133
+ // is technically `O(n)`, in practice it's faster than a real heap.
134
+ let to = next_index - size;
135
+ if to > 0 {
136
+ let mut i = free[ to] . len ( ) ;
137
+ let value = next_block + size;
138
+
139
+ while free[ to] [ i - 1 ] < value {
140
+ i -= 1 ;
141
+ }
142
+
143
+ free[ to] . insert ( i, value) ;
125
144
}
126
145
}
127
146
}
@@ -133,6 +152,6 @@ pub fn part2(disk: &[usize]) -> usize {
133
152
#[ inline]
134
153
fn update ( checksum : usize , block : usize , index : usize , size : usize ) -> ( usize , usize ) {
135
154
let id = index / 2 ;
136
- let extra = block * size + EXTRA [ size] ;
155
+ let extra = block * size + TRIANGLE [ size] ;
137
156
( checksum + id * extra, block + size)
138
157
}
0 commit comments