1
- #[ derive( Debug , PartialEq ) ]
1
+ #[ derive( Clone , Copy , Debug , PartialEq ) ]
2
2
enum Tile {
3
3
Empty = '.' as isize ,
4
4
FixedRock = '#' as isize ,
@@ -19,17 +19,32 @@ struct Platform {
19
19
20
20
impl Platform {
21
21
pub fn tilt ( & mut self , direction : Direction ) {
22
- assert_eq ! ( direction, Direction :: North ) ;
23
-
24
22
let rows = self . tiles . len ( ) ;
25
- for r in 0 ..rows {
23
+ let cols = self . tiles [ 0 ] . len ( ) ;
24
+ match direction {
25
+ Direction :: North => self . tilt_north_south ( 0 ..=rows-1 , |r| r+1 ..=rows-1 ) ,
26
+ Direction :: West => self . tilt_west_east ( 0 ..=cols-1 , |c| c+1 ..=cols-1 ) ,
27
+ Direction :: South => self . tilt_north_south ( rows-1 ..=0 , |r| r-1 ..=0 ) ,
28
+ Direction :: East => self . tilt_west_east ( cols-1 ..=0 , |c| c-1 ..=0 ) ,
29
+ }
30
+ }
31
+
32
+ pub fn cycle ( & mut self ) {
33
+ self . tilt ( Direction :: North ) ;
34
+ self . tilt ( Direction :: West ) ;
35
+ self . tilt ( Direction :: South ) ;
36
+ self . tilt ( Direction :: East ) ;
37
+ }
38
+
39
+ fn tilt_north_south ( & mut self , rows : std:: ops:: RangeInclusive < usize > , source_rows : impl Fn ( usize ) -> std:: ops:: RangeInclusive < usize > ) {
40
+ for r in rows {
26
41
for c in 0 ..self . tiles [ r] . len ( ) {
27
42
if self . tiles [ r] [ c] != Tile :: Empty {
28
43
continue ;
29
44
}
30
45
31
46
// Move the closest tile in South direction here.
32
- for r2 in r+ 1 ..rows {
47
+ for r2 in source_rows ( r ) {
33
48
let other_tile = & mut self . tiles [ r2] [ c] ;
34
49
match other_tile {
35
50
Tile :: Empty => { } ,
@@ -49,6 +64,34 @@ impl Platform {
49
64
}
50
65
}
51
66
67
+ fn tilt_west_east ( & mut self , cols : std:: ops:: RangeInclusive < usize > , source_cols : impl Fn ( usize ) -> std:: ops:: RangeInclusive < usize > ) {
68
+ for c in cols {
69
+ for r in 0 ..self . tiles . len ( ) {
70
+ if self . tiles [ r] [ c] != Tile :: Empty {
71
+ continue ;
72
+ }
73
+
74
+ // Move the closest tile in South direction here.
75
+ for c2 in source_cols ( c) {
76
+ let other_tile = & mut self . tiles [ r] [ c2] ;
77
+ match other_tile {
78
+ Tile :: Empty => { } ,
79
+ Tile :: FixedRock => {
80
+ // Abort: This rock won't move, and anything further south will get stuck here.
81
+ break ;
82
+ } ,
83
+ Tile :: RoundedRock => {
84
+ // This one!
85
+ self . tiles [ r] [ c] = Tile :: RoundedRock ;
86
+ self . tiles [ r] [ c2] = Tile :: Empty ;
87
+ break ;
88
+ } ,
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+
52
95
pub fn total_load ( & self , beam : Direction ) -> usize {
53
96
assert_eq ! ( beam, Direction :: North ) ;
54
97
@@ -63,6 +106,16 @@ impl Platform {
63
106
}
64
107
result
65
108
}
109
+
110
+ pub fn checksum ( & self ) -> String {
111
+ let mut hasher = crypto:: sha1:: Sha1 :: new ( ) ;
112
+ for row in self . tiles . iter ( ) {
113
+ for tile in row. iter ( ) {
114
+ crypto:: digest:: Digest :: input ( & mut hasher, & [ * tile as u8 ] ) ;
115
+ }
116
+ }
117
+ crypto:: digest:: Digest :: result_str ( & mut hasher)
118
+ }
66
119
}
67
120
68
121
impl std:: fmt:: Display for Platform {
@@ -104,13 +157,36 @@ impl std::str::FromStr for Platform {
104
157
}
105
158
106
159
pub fn main ( ) {
160
+ const CYCLES : usize = 1000000000 ;
107
161
match std:: fs:: read_to_string ( "day14.input" ) {
108
162
Ok ( input) => {
109
163
let mut platform: Platform = input. parse ( ) . unwrap ( ) ;
110
164
println ! ( "initial\n {}" , platform) ;
111
165
platform. tilt ( Direction :: North ) ;
112
166
println ! ( "after tilt\n {}" , platform) ;
113
167
println ! ( "total load = {}" , platform. total_load( Direction :: North ) ) ;
168
+
169
+ // Complete the first cycle.
170
+ platform. tilt ( Direction :: West ) ;
171
+ platform. tilt ( Direction :: South ) ;
172
+ platform. tilt ( Direction :: East ) ;
173
+
174
+ // Complete the remaining cycles
175
+ let mut previous_checksum = platform. checksum ( ) ;
176
+ for cycle in 0 ..CYCLES -1 {
177
+ platform. cycle ( ) ;
178
+
179
+ let checksum = platform. checksum ( ) ;
180
+ println ! ( "cycle {} checksum = {}" , cycle, checksum) ;
181
+ if checksum == previous_checksum {
182
+ break ;
183
+ }
184
+ previous_checksum = checksum;
185
+ if cycle % 1000 == 0 {
186
+ println ! ( "complete {}%" , cycle/CYCLES * 100 ) ;
187
+ }
188
+ }
189
+ println ! ( "total load after {} cycles = {}" , CYCLES , platform. total_load( Direction :: North ) ) ;
114
190
} ,
115
191
Err ( reason) => println ! ( "error = {}" , reason) ,
116
192
}
@@ -120,6 +196,7 @@ pub fn main() {
120
196
mod test {
121
197
use super :: * ;
122
198
199
+ static CYCLES : usize = 1000000000 ;
123
200
static DATA : & str = "O....#....
124
201
O.OO#....#
125
202
.....##...
@@ -139,4 +216,24 @@ O.#..O.#.#
139
216
println ! ( "after tilt\n {}" , platform) ;
140
217
assert_eq ! ( platform. total_load( Direction :: North ) , 136 ) ;
141
218
}
219
+
220
+ #[ test]
221
+ fn test_part2 ( ) {
222
+ let mut platform: Platform = DATA . parse ( ) . unwrap ( ) ;
223
+ println ! ( "initial\n {}" , platform) ;
224
+
225
+ let mut previous_checksum = platform. checksum ( ) ;
226
+ for cycle in 0 ..CYCLES {
227
+ platform. cycle ( ) ;
228
+
229
+ let checksum = platform. checksum ( ) ;
230
+ println ! ( "cycle {} checksum = {}" , cycle, checksum) ;
231
+ if checksum == previous_checksum {
232
+ break ;
233
+ }
234
+ previous_checksum = checksum;
235
+ }
236
+ println ! ( "after tilt\n {}" , platform) ;
237
+ assert_eq ! ( platform. total_load( Direction :: North ) , 64 ) ;
238
+ }
142
239
}
0 commit comments