44//! The image appears in part two when the positions of all robots are unique.
55//!
66//! The x coordinates repeat every 101 seconds and the y coordinates repeat every 103 seconds.
7- //! Calculating each axis independently then looking it up is twice as fast
8- //! as calculating as needed.
7+ //! First we check for times when the robot x coordinates could form the left and right columns
8+ //! of the tree's bounding box. This gives a time `t` mod 101.
9+ //!
10+ //! Then we check the y coordinates looking for the top and bottom rows of the bounding box,
11+ //! giving a time `u` mod 103.
12+ //!
13+ //! Using the [Chinese Remainder Theorem](https://en.wikipedia.org/wiki/Chinese_remainder_theorem)
14+ //! we combine the two times into a single time mod 10403 that is the answer.
915use crate :: util:: iter:: * ;
10- use crate :: util:: math:: * ;
1116use crate :: util:: parse:: * ;
1217
13- type Robot = [ i32 ; 4 ] ;
18+ type Robot = [ usize ; 4 ] ;
1419
1520pub fn parse ( input : & str ) -> Vec < Robot > {
16- input. iter_signed ( ) . chunk :: < 4 > ( ) . collect ( )
21+ input
22+ . iter_signed :: < i32 > ( )
23+ . chunk :: < 4 > ( )
24+ . map ( |[ x, y, dx, dy] | {
25+ [ x as usize , y as usize , dx. rem_euclid ( 101 ) as usize , dy. rem_euclid ( 103 ) as usize ]
26+ } )
27+ . collect ( )
1728}
1829
1930pub fn part1 ( input : & [ Robot ] ) -> i32 {
@@ -23,8 +34,8 @@ pub fn part1(input: &[Robot]) -> i32 {
2334 let mut q4 = 0 ;
2435
2536 for [ x, y, dx, dy] in input {
26- let x = ( x + 100 * dx) . rem_euclid ( 101 ) ;
27- let y = ( y + 100 * dy) . rem_euclid ( 103 ) ;
37+ let x = ( x + 100 * dx) % 101 ;
38+ let y = ( y + 100 * dy) % 103 ;
2839
2940 if x < 50 {
3041 if y < 51 {
@@ -47,52 +58,61 @@ pub fn part1(input: &[Robot]) -> i32 {
4758 q1 * q2 * q3 * q4
4859}
4960
50- pub fn part2 ( input : & [ Robot ] ) -> usize {
51- let robots : Vec < _ > = input
52- . iter ( )
53- . map ( | & [ x , y , h , v ] | [ x , y , h . rem_euclid ( 101 ) , v . rem_euclid ( 103 ) ] )
54- . collect ( ) ;
61+ pub fn part2 ( robots : & [ Robot ] ) -> usize {
62+ // Search for times mod 101 when the tree could possibly exist using x coordinates only.
63+ // and times mod 103 when the tree could possibly exist using y coordinates only.
64+ let mut rows = Vec :: new ( ) ;
65+ let mut columns = Vec :: new ( ) ;
5566
56- let coefficient1 = 103 * 103 . mod_inv ( 101 ) . unwrap ( ) ;
57- let coefficient2 = 101 * 101 . mod_inv ( 103 ) . unwrap ( ) ;
58- let horizontal: Vec < _ > = ( 0 ..101 ) . map ( |n| n. mod_inv ( 101 ) ) . collect ( ) ;
59- let vertical: Vec < _ > = ( 0 ..103 ) . map ( |n| n. mod_inv ( 103 ) ) . collect ( ) ;
67+ for time in 0 ..103 {
68+ let mut xs = [ 0 ; 101 ] ;
69+ let mut ys = [ 0 ; 103 ] ;
6070
61- let mut unique = vec ! [ true ; 10403 ] ;
71+ for [ x, y, dx, dy] in robots {
72+ let x = ( x + time * dx) % 101 ;
73+ xs[ x] += 1 ;
74+ let y = ( y + time * dy) % 103 ;
75+ ys[ y] += 1 ;
76+ }
6277
63- for ( i, & [ x1, y1, h1, v1] ) in robots. iter ( ) . enumerate ( ) . skip ( 1 ) {
64- for & [ x2, y2, h2, v2] in robots. iter ( ) . take ( i) {
65- if x1 == x2 && h1 == h2 {
66- if let Some ( b) = vertical[ to_index ( v2 - v1, 103 ) ] {
67- let u = to_index ( ( y1 - y2) * b, 103 ) ;
78+ // Tree bounding box is 31x33.
79+ if time < 101 && xs. iter ( ) . filter ( |& & c| c >= 33 ) . count ( ) >= 2 {
80+ columns. push ( time) ;
81+ }
82+ if ys. iter ( ) . filter ( |& & c| c >= 31 ) . count ( ) >= 2 {
83+ rows. push ( time) ;
84+ }
85+ }
6886
69- for n in ( 0 .. 10403 ) . step_by ( 103 ) {
70- unique [ n + u ] = false ;
71- }
72- }
73- } else if y1 == y2 && v1 == v2 {
74- if let Some ( a ) = horizontal [ to_index ( h2 - h1 , 101 ) ] {
75- let t = to_index ( ( x1 - x2 ) * a , 101 ) ;
87+ // If there's only one combination then return answer.
88+ if rows . len ( ) == 1 && columns . len ( ) == 1 {
89+ let t = columns [ 0 ] ;
90+ let u = rows [ 0 ] ;
91+ // Combine indices using the Chinese Remainder Theorem to get index mod 10403.
92+ return ( 5253 * t + 5151 * u ) % 10403 ;
93+ }
7694
77- for n in ( 0 ..10403 ) . step_by ( 101 ) {
78- unique[ n + t] = false ;
79- }
80- }
81- } else if let Some ( a) = horizontal[ to_index ( h2 - h1, 101 ) ] {
82- if let Some ( b) = vertical[ to_index ( v2 - v1, 103 ) ] {
83- let t = ( x1 - x2) * a;
84- let u = ( y1 - y2) * b;
85- let crt = to_index ( t * coefficient1 + u * coefficient2, 10403 ) ;
86- unique[ crt] = false ;
95+ // Backup check looking for time when all robot positions are unique.
96+ let mut floor = vec ! [ 0 ; 10403 ] ;
97+
98+ for & t in & columns {
99+ ' outer: for & u in & rows {
100+ let time = ( 5253 * t + 5151 * u) % 10403 ;
101+
102+ for & [ x, y, dx, dy] in robots {
103+ let x = ( x + time * dx) % 101 ;
104+ let y = ( y + time * dy) % 103 ;
105+
106+ let index = 101 * y + x;
107+ if floor[ index] == time {
108+ continue ' outer;
87109 }
110+ floor[ index] = time;
88111 }
112+
113+ return time;
89114 }
90115 }
91116
92- unique. iter ( ) . position ( |& u| u) . unwrap ( )
93- }
94-
95- #[ inline]
96- fn to_index ( a : i32 , m : i32 ) -> usize {
97- a. rem_euclid ( m) as usize
117+ unreachable ! ( )
98118}
0 commit comments